Compare commits
472 commits
fix-sample
...
master
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | c56b0a7701 | ||
Alex Janka | 04113b66f7 | ||
cbdbdafecd | |||
ffcb8ba1d1 | |||
941275199b | |||
525c311844 | |||
2fe66d958f | |||
ed8bf637a9 | |||
7555bb9024 | |||
562e1e5f18 | |||
13b44e1639 | |||
240aae1bcf | |||
95a489ee12 | |||
4abd48eb24 | |||
0007bd6d98 | |||
72c72dafa0 | |||
0fde712f3c | |||
2323b18710 | |||
5978f95f76 | |||
3ee5e66c0d | |||
f189468f6d | |||
fc47fb4c5a | |||
fc7739d9ab | |||
4a8a9ee444 | |||
756cbe63d9 | |||
796b03b42b | |||
742a1c8658 | |||
7c03a7e3fe | |||
4ba6effc2f | |||
32c99d9f4a | |||
75b70cc0e6 | |||
828464c351 | |||
c19593e289 | |||
1bbbd784d8 | |||
4273a833e8 | |||
f14f45b3b1 | |||
859d16e64e | |||
b395b94a40 | |||
90a9ee754d | |||
34b50059ca | |||
7a13136f9a | |||
0cb3880d7f | |||
c526b7043a | |||
33d95ac399 | |||
40691cc406 | |||
3c20c83bc9 | |||
b123f63a6e | |||
4285ad2bd1 | |||
c57e502b78 | |||
7edff0ae35 | |||
dedde05c83 | |||
7d483f2e08 | |||
7b7fd99b92 | |||
28931ae50a | |||
2fe7702957 | |||
a5c8fcf4f8 | |||
629070ea2f | |||
da53c3df59 | |||
84e78f4e48 | |||
91f8089277 | |||
3993f57271 | |||
55ff7a93f2 | |||
227757a221 | |||
a72a593029 | |||
1537c1bcd7 | |||
5573f13227 | |||
bac09ad2a3 | |||
31ece05246 | |||
5ede061975 | |||
1676150858 | |||
eeda0d02d0 | |||
f7a938a00d | |||
6394b28d40 | |||
443fa20d22 | |||
5c726efe21 | |||
d33c2a84b2 | |||
5a35a2bd1e | |||
41034330a7 | |||
a7836923d7 | |||
20039b9347 | |||
799d409ddb | |||
79513a301e | |||
6de2de8d12 | |||
2904a2ac10 | |||
341fbceb82 | |||
c54747d398 | |||
3888b56c83 | |||
b0df631651 | |||
dbfa822f7c | |||
e7fe96520e | |||
97ad0d64bf | |||
feaebc5f44 | |||
ef35e2a620 | |||
987e967269 | |||
c3469520f9 | |||
45b98a2bdd | |||
2ee9eca854 | |||
59937aced5 | |||
e4eed34c10 | |||
7abd679bd7 | |||
41353ac9c4 | |||
4d790e7a7b | |||
aeb0a16cfb | |||
16108838b1 | |||
7cfbca7755 | |||
8467e5cd97 | |||
1e33b4cc03 | |||
eb53699590 | |||
0cbf8024a7 | |||
7e354a56a1 | |||
0a87bcc657 | |||
f0a7970b44 | |||
e7e6ed8fb8 | |||
61fdaeea14 | |||
2552d4321b | |||
eaf939c861 | |||
14abb0362b | |||
e064f8d0be | |||
f18c22a95a | |||
763c05755d | |||
e6d23f1d8f | |||
2f9df143cf | |||
1bdadaa449 | |||
77b957bf5e | |||
1e0727f89d | |||
60d2c3f177 | |||
72a98272f3 | |||
7d0b135710 | |||
4ba5aefafc | |||
6edfaed91f | |||
cd877d7883 | |||
d56d92ea0f | |||
f49e3b58d8 | |||
1f4f1b6c12 | |||
57f2dabf0c | |||
81840a9e9c | |||
87e0405675 | |||
e0c97f77b4 | |||
6e60dd6fa0 | |||
d55d0e4839 | |||
6cdd9247de | |||
cbe6510f76 | |||
d6f8950bdc | |||
0fe5bbd57b | |||
eace595ebb | |||
66561ad2ed | |||
05d48841ad | |||
927740433c | |||
316e92dc09 | |||
e930f90a9c | |||
2f988d5b1d | |||
e0a5c90103 | |||
4cc3c875bf | |||
805854b94b | |||
c291d9d85f | |||
8b2ff57ee8 | |||
3d9139b4e0 | |||
b432a1e02d | |||
ab9ab6fe68 | |||
0ba4c482b3 | |||
57f9a13ee7 | |||
2661effab4 | |||
02288554b9 | |||
98d8d91c66 | |||
6f5b342c1b | |||
bec0482513 | |||
e24beede0b | |||
22b2118e97 | |||
856f69113d | |||
477d0ae67c | |||
e68da7b984 | |||
666588ef0d | |||
336f540ce9 | |||
894d19eb81 | |||
1bf5d7efca | |||
c9205bc922 | |||
cebc7a939a | |||
fe48fd03a5 | |||
50580cfc3b | |||
075a2981e1 | |||
a7dd40a79f | |||
fd8d6f6ab8 | |||
c5fdffa6aa | |||
090e268c4a | |||
8856a78eb8 | |||
ec3add1616 | |||
f83fd1e98f | |||
c3033cfbbf | |||
820fb69328 | |||
c9a6411394 | |||
40a56bf165 | |||
c447e40583 | |||
ae76bf9cc1 | |||
e934f175ef | |||
2e7c3b3273 | |||
e90c27ebbd | |||
8fb5d48a1e | |||
b7fd3bc507 | |||
af05cc5bd8 | |||
2f0a3356d9 | |||
0efaf2b8a0 | |||
436d1fe3cd | |||
0ce11bac71 | |||
bc269c270c | |||
db3d5d05a6 | |||
1957f576ca | |||
5560c1ed09 | |||
f8c055524b | |||
fee92113f1 | |||
45c3c876f4 | |||
28f5674a80 | |||
bffad12ea2 | |||
321e30a0f1 | |||
9b40c10466 | |||
cd14bca23a | |||
1ac78695c6 | |||
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 | |||
70aa4091e0 | |||
3e144bbdff | |||
31235971db | |||
650aecbe10 | |||
ac1494bee1 | |||
ded8851e16 |
140
.github/workflows/build.yml
vendored
140
.github/workflows/build.yml
vendored
|
@ -14,34 +14,138 @@ 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
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
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) }}
|
||||
- name: Install Ubuntu librashader CLI build dependencies
|
||||
if: matrix.profile == 'release' && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update || true
|
||||
sudo apt-get -y install xorg-dev
|
||||
- name: Build librashader CLI
|
||||
if: matrix.profile == 'release'
|
||||
run: cargo build -p librashader-cli --release
|
||||
- name: Upload librashader-cli
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
if: matrix.profile == 'release'
|
||||
with:
|
||||
name: ${{ format('librashader-cli-{0}-{1}', matrix.output, github.sha) }}
|
||||
path: ${{ format('target/{0}/librashader-cli*', 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 xorg-dev libx11-dev:arm64 libxrandr-dev:arm64
|
||||
- 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@v4.4.0
|
||||
with:
|
||||
name: ${{ format('librashader-aarch64-ubuntu-{0}-{1}', github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', matrix.profile) }}
|
||||
- name: Build librashader CLI
|
||||
if: matrix.profile == 'release'
|
||||
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo build -p librashader-cli --release --target aarch64-unknown-linux-gnu
|
||||
- name: Upload librashader-cli
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
if: matrix.profile == 'release'
|
||||
with:
|
||||
name: ${{ format('librashader-cli-aarch64-ubuntu-{0}', github.sha) }}
|
||||
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader-cli', 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
|
||||
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@v4.4.0
|
||||
with:
|
||||
name: ${{ format('librashader-aarch64-windows-{0}-{1}', github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader.*', matrix.profile) }}
|
||||
- name: Build librashader CLI
|
||||
if: matrix.profile == 'release'
|
||||
run: cargo build -p librashader-cli --release --target aarch64-pc-windows-msvc
|
||||
- name: Upload librashader-cli
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
if: matrix.profile == 'release'
|
||||
with:
|
||||
name: ${{ format('librashader-cli-aarch64-pc-windows-msvc-{0}', github.sha) }}
|
||||
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader-cli.exe', matrix.profile) }}
|
||||
build-windows-7:
|
||||
strategy:
|
||||
matrix:
|
||||
profile: ['release', 'optimized']
|
||||
fail-fast: false
|
||||
runs-on: windows-latest
|
||||
name: x86_64-win7-windows
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-src
|
||||
- name: Build dynamic library
|
||||
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target x86_64-win7-windows-msvc -- -Zbuild-std
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
with:
|
||||
name: ${{ format('librashader-x86_64-win7-windows-{0}-{1}', github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/x86_64-win7-windows-msvc/{0}/librashader.*', matrix.profile) }}
|
||||
|
|
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@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/
|
||||
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
|
||||
|
2
.gitignore
vendored
2
.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/
|
||||
|
@ -12,3 +13,4 @@ librashader_runtime_*.exe
|
|||
/test/capi-tests/librashader-capi-tests/**/*.so
|
||||
/test/capi-tests/librashader-capi-tests/**/*.out
|
||||
/test/Mega_Bezel_Packs/Duimon-Mega-Bezel/
|
||||
.DS_Store
|
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>
|
|
@ -3,38 +3,13 @@
|
|||
|
||||
The following shaders are known to be broken due to various issues.
|
||||
|
||||
This list is updated as of [slang-shaders@`356678e`](https://github.com/libretro/slang-shaders/commit/356678ec53ca940a53fa509eff0b65bb63a403bb)
|
||||
This list is updated as of [slang-shaders@`33876b3`](https://github.com/libretro/slang-shaders/commit/33876b3578baac8302b6189ac7acbb052013919e)
|
||||
|
||||
## Broken due to parsing errors
|
||||
librashader's preset parser is somewhat stricter than RetroArch in what it accepts. All shaders and textures in a preset must
|
||||
resolve to a fully canonical path to properly parse. The following shaders have broken paths.
|
||||
|
||||
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: Missing `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/shaders/linearize.slang`
|
||||
* `crt/crt-maximus-royale-fast-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
|
||||
* `crt/crt-maximus-royale-half-res-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
|
||||
* `crt/crt-maximus-royale.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
|
||||
* `crt/mame_hlsl.slangp`: Missing `crt/shaders/mame_hlsl/shaders/lut.slang`
|
||||
* `denoisers/fast-bilateral-super-2xbr-3d-3p.slangp`: Missing `xbr/shaders/super-xbr/super-2xbr-3d-pass0.slang`
|
||||
* `presets/tvout/tvout+ntsc-256px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-256px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-2phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-2phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-320px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-320px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-3phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-3phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
|
||||
* `presets/tvout/tvout+ntsc-nes.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-256px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-256px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-2phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-2phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-320px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-320px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-3phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-3phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
|
||||
* `presets/tvout+interlacing/tvout+ntsc-nes+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
|
||||
* `scalefx/shaders/old/scalefx-9x.slangp`: Missing `../stock.slang`
|
||||
* `scalefx/shaders/old/scalefx.slangp`: Missing `../stock.slang`
|
||||
* No known broken presets.
|
||||
|
||||
librashader's parser is fuzzed with slang-shaders and will accept invalid keys like `mipmap1` or `filter_texture = linear`
|
||||
to account for shader presets that use these invalid constructs. No known shader presets fail to parse due to syntax errors
|
||||
|
@ -44,5 +19,4 @@ that haven't already been accounted for.
|
|||
|
||||
The preprocessor resolves `#include` pragmas in each `.slang` shader and recursively flattens files into a single compute unit.
|
||||
|
||||
* `misc/shaders/glass.slang`: Missing `misc/include/img/param_floats.h`.
|
||||
* Looks like this was moved too deep, it references `../include/img/param_floats.h`, but the shader lives in the `misc/shaders/` folder.
|
||||
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: GLSL error: `ERROR: common-functions-bezel.inc:63: 'HSM_GetInverseScaledCoord' : no matching overloaded function found `
|
||||
|
|
3613
Cargo.lock
generated
3613
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
31
Cargo.toml
31
Cargo.toml
|
@ -8,12 +8,41 @@ members = [
|
|||
"librashader-runtime",
|
||||
"librashader-runtime-d3d11",
|
||||
"librashader-runtime-d3d12",
|
||||
"librashader-runtime-d3d9",
|
||||
"librashader-runtime-gl",
|
||||
"librashader-runtime-vk",
|
||||
"librashader-runtime-mtl",
|
||||
"librashader-runtime-wgpu",
|
||||
"librashader-cache",
|
||||
"librashader-capi",
|
||||
"librashader-build-script"
|
||||
"librashader-build-script",
|
||||
"librashader-cli", "librashader-pack"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
windows = "0.58.0"
|
||||
ash = "0.38"
|
||||
spirv-cross2 = { version = "0.4", default-features = false }
|
||||
objc2-metal = { version = "0.2" }
|
||||
objc2 = { version = "0.5.0" }
|
||||
glow = { version = "0.14.1" }
|
||||
glfw = { version = "0.58.0"}
|
||||
|
||||
wgpu = { version = "22", default-features = false }
|
||||
wgpu-types = { version = "22" }
|
||||
|
||||
clap = { version = "=4.3.0", features = ["derive"] }
|
||||
rayon = { version = "1.10.0"}
|
||||
|
||||
|
||||
[workspace.dependencies.image]
|
||||
version = "0.25.2"
|
||||
features = [
|
||||
"gif", "jpeg", "png",
|
||||
"tga", "pnm", "tiff",
|
||||
"webp", "bmp", "dds",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
||||
|
|
120
MIGRATION-ABI2.md
Normal file
120
MIGRATION-ABI2.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Migrating to librashader ABI 2
|
||||
|
||||
librashader version 0.5.0 introduces an ABI change to the C ABI that is incompatible with prior versions.
|
||||
|
||||
If a version of librashader prior to 0.5.0 was linked via `libashader_ld.h`, it should _correctly_ fail to load when attempting
|
||||
to load an instance of librashader 0.5.0 or later.
|
||||
|
||||
This document is only relevant to consumers of the librashader C ABI. Rust users should consult [docs.rs/librashader](https://docs.rs/librashader/latest/librashader/).
|
||||
|
||||
## `LIBRASHADER_CURRENT_ABI` change
|
||||
|
||||
`LIBRASHADER_CURRENT_ABI` has changed from `1` to `2`. There is no change to `LIBRASHADER_CURRENT_API`, as new features
|
||||
have not been added. Option structs should continue to pass `LIBRASHADER_CURRENT_API` for the latest available feature set.
|
||||
|
||||
## `SONAME` change
|
||||
|
||||
On Linux, the canonical `SONAME` of `librashader.so` has changed from `librashader.so.1` to `librashader.so.2`. This will
|
||||
affect consumers that link via the linkage table with `-lrashader` or equivalent rather than through `libashader_ld.h`.
|
||||
|
||||
## `libra_preset` changes
|
||||
|
||||
* The `_internal_alloc` field from `libra_preset_param_list_t` has been removed. This change was made to reduce the potential
|
||||
surface area for undefined behaviour to occur. If librashader was used correctly, there is no code change required.
|
||||
|
||||
## `LIBRA_RUNTIME_VULKAN` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_VULKAN` is defined.
|
||||
|
||||
* The `libra_output_image_vk_t` and `libra_source_image_vk_t` structs have been replaced with `libra_image_vk_t`.
|
||||
* `libra_image_vk_t`has the same layout and semantics for `libra_source_image_vk_t`.
|
||||
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_vk_t`. The `handle` and `format`
|
||||
fields retain the same semantics as `libra_output_image_vk_t`.
|
||||
* A field `queue` of type `VkQueue` was added to `libra_device_vk_t`. This field can be `NULL`. If not `NULL`, it is the handle to the `VkQueue` graphics queue
|
||||
to use for the filter chain. If `NULL`, a suitable queue will be chosen.
|
||||
* The `image` and `out` parameters of `libra_vk_filter_chain_frame` has changed from `libra_source_image_vk_t` and `libra_output_image_vk_t`, to `libra_image_vk_t`.
|
||||
* In `libra_vk_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_vk_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_vk_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `LIBRA_RUNTIME_OPENGL` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_OPENGL` is defined.
|
||||
* The `libra_gl_init_context` function has been removed.
|
||||
* The function `libra_gl_filter_chain_create` now accepts a `loader` parameter of type `libra_gl_loader_t`. This will be the OpenGL loader used to create the filter chain, previously passed to `libra_gl_init_context`
|
||||
The filter chain will be created against the current OpenGL context.
|
||||
* The `libra_output_framebuffer_gl_t` and `libra_source_image_gl_t` structs have been replaced with `libra_image_gl_t`.
|
||||
* `libra_image_gl_t`has the same layout and semantics for `libra_source_image_gl_t`.
|
||||
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_gl_t`. The `handle` and `format`
|
||||
fields retain the same semantics as `libra_output_image_gl_t`.
|
||||
* The `fbo` field previously in `libra_output_image_gl_t` is no longer necessary. librashader will now internally manage a framebuffer object to write to the provided texture.
|
||||
* In `libra_gl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_gl_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_gl_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `LIBRA_RUNTIME_D3D11` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_D3D11` is defined.
|
||||
|
||||
* The `image` parameter of `libra_d3d11_filter_chain_frame` has changed from `libra_source_image_d3d11_t` to `ID3D11ShaderResourceView *`.
|
||||
* You should now pass what was previously the `.handle` field of `libra_source_image_d3d11_t` field directly as `image` to `libra_d3d11_filter_chain_frame`.
|
||||
* The `libra_source_image_d3d11_t` struct has been removed.
|
||||
* In `libra_d3d11_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_d3d11_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_d3d11_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `LIBRA_RUNTIME_D3D12` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_D3D12` is defined.
|
||||
|
||||
* The lifetime of resources will not be extended past the call to the `libra_d3d12_filter_chain_frame` function. In other words, the refcount for any resources passed into the function
|
||||
will no longer be changed, and it is explicitly the responsibility of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is submitted.
|
||||
* The fields `format`, `width`, and `height` have been removed from `libra_source_image_d3d12_t`.
|
||||
* The field `descriptor` now comes before the field `resource` in the layout of `libra_source_image_d3d12_t`.
|
||||
* The fields `width` and `height` have been added to `libra_output_image_d3d12_t`.
|
||||
* You should now pass what was previously `.width` and `.height` of `libra_viewport_t` to these new fields in `libra_output_image_d3d12_t`.
|
||||
* The `image` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_source_image_d3d12_t` to `libra_image_d3d12_t`.
|
||||
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_SOURCE_IMAGE`, and `.handle.source` should be the `libra_source_image_d3d12_t`struct.
|
||||
* The `out` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_output_image_d3d11_t` to `libra_image_d3d12_t`.
|
||||
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_OUTPUT_IMAGE`, and `.handle.output` should be the `libra_output_image_d3d12_t`struct.
|
||||
* Any `libra_image_d3d12_t` can now optionally pass only the `ID3D12Resource *` for the texture by setting `.image_type` to `LIBRA_D3D12_IMAGE_TYPE_RESOURCE` and setting `.handle.resource` to the resource pointer.
|
||||
* If using `LIBRA_D3D12_IMAGE_TYPE_RESOURCE`, shader resource view and render target view descriptors for the input and output images will be internally allocated by the filter chain. This may result in marginally worse performance.
|
||||
* In `libra_d3d12_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_d3d12_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_d3d12_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `LIBRA_RUNTIME_D3D9` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_D3D9` is defined.
|
||||
|
||||
* In `libra_d3d9_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_d3d9_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_d3d9_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `LIBRA_RUNTIME_METAL` changes
|
||||
The following changes are applicable if `LIBRA_RUNTIME_METAL` is defined.
|
||||
|
||||
* In `libra_mtl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
|
||||
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
|
||||
* The `chain` parameter of `libra_mtl_filter_chain_get_param` has been made `const`.
|
||||
* It is always thread safe to call `libra_mtl_filter_chain_set_param` from any thread [^1].
|
||||
|
||||
## `libra_viewport_t` changes
|
||||
|
||||
All `viewport` parameters for `libra_*_filter_chain_frame` now take a *pointer* to a `libra_viewport_t` struct. In ABI 1, the semantics of `libra_viewport_t`
|
||||
was unspecified (but not undefined behaviour) if `width` and `height` did not match the width and height of the output texture.
|
||||
|
||||
This caused confusion as to what the actual purpose of the `width` and `height` fields were. The behaviour differed across runtimes:
|
||||
In some runtimes, it specified the size of the output texture, in others they were used to set the clipping rect for the render target.
|
||||
|
||||
In ABI 2, the semantics of `viewport` as a parameter in `libra_*_filter_chain_frame` are as specified.
|
||||
|
||||
* If `viewport` is `NULL`, then this will be the same as the **specified** behaviour in ABI 1—that is, as if the behaviour where `width` and `height` of `libra_viewport_t` were **equal** to that of the output texture.
|
||||
* In other words, if `viewport` is `NULL`, then it will set the render viewport to be equal to the width and height of the output texture.
|
||||
* If `viewport` is not `NULL`, then the following occurs.
|
||||
* The width and height of the viewport rectangle for the output quad will be set to `viewport.width` and `viewport.height` respectively.
|
||||
* The origin point of the viewport rectangle will be set to (`x`, `y`), **in the native coordinate system of the runtime**.
|
||||
* This behaviour may change over an _API_ version bump. **API version 1 will always retain the behaviour specified here.**
|
||||
* The scissor rectangle will be set to the same size and origin as the viewport rectangle.
|
||||
|
||||
[^1]: This has been the case since librashader 0.4.0 on ABI 1. ABI 2 codifies this guarantee: any loosening in the thread-safety guarantees of `libra_*_filter_chain_set_param` in the future may only change across an _API_ version bump. **API version 1 will always retain the behaviour specified here.**
|
186
README.md
186
README.md
|
@ -1,30 +1,43 @@
|
|||
# librashader
|
||||
|
||||
![Mega Bezel SMOOTH-ADV](shader_triangle.png)
|
||||
![Mega Bezel SMOOTH-ADV](https://raw.githubusercontent.com/SnowflakePowered/librashader/master/shader_triangle.png)
|
||||
|
||||
<small>*Mega Bezel SMOOTH-ADV on DirectX 11*</small>
|
||||
|
||||
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)
|
||||
![Stable rust](https://img.shields.io/badge/rust-1.78-blue.svg) ![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 limited
|
||||
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
|
||||
|
||||
Shader compatibility is not guaranteed on render APIs with secondary support.
|
||||
|
||||
wgpu has restrictions on shaders that can not be converted to WGSL, such as those that use `inverse`. Direct3D 9 does not support
|
||||
shaders that need Direct3D 10+ only features, or shaders that can not be compiled to [Shader Model 3.0](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/shader-model-3).
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -33,20 +46,20 @@ 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,13 @@ 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.
|
||||
|
||||
The Direct3D 9 API is not thread safe, unless `D3DCREATE_MULTITHREADED` is enabled at device creation.
|
||||
|
||||
### 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,27 +92,39 @@ 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.
|
||||
### Writing a librashader Runtime
|
||||
|
||||
```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,
|
||||
];
|
||||
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
|
||||
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
|
||||
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
|
||||
|
||||
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
|
||||
the runtime.
|
||||
|
||||
## Command-line interface
|
||||
librashader provides a command-line interface to reflect and debug 'slang' shaders and presets.
|
||||
|
||||
```
|
||||
Usage: librashader-cli <COMMAND>
|
||||
|
||||
Commands:
|
||||
render Render a shader preset against an image
|
||||
compare Compare two runtimes and get a similarity score between the two runtimes rendering the same frame
|
||||
parse Parse a preset and get a JSON representation of the data
|
||||
pack Create a serialized preset pack from a shader preset
|
||||
preprocess Get the raw GLSL output of a preprocessed shader
|
||||
transpile Transpile a shader in a given preset to the given format
|
||||
reflect Reflect the shader relative to a preset, giving information about semantics used in a slang shader
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
### Building
|
||||
For more information, see [`CLI.md`](https://github.com/SnowflakePowered/librashader/blob/master/CLI.md).
|
||||
|
||||
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/)
|
||||
|
||||
---
|
||||
## Building
|
||||
|
||||
For Rust projects, simply add the crate to your `Cargo.toml`.
|
||||
|
||||
|
@ -111,14 +141,31 @@ 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.
|
||||
|
||||
### Writing a librashader Runtime
|
||||
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
|
||||
the relevant runtime graphics API.
|
||||
|
||||
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
|
||||
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
|
||||
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
|
||||
|
||||
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
|
||||
the runtime.
|
||||
### Building against stable Rust
|
||||
While librashader is intended to be used with nightly Rust until [required features](https://github.com/SnowflakePowered/librashader/issues/55) are stabilized, it supports being
|
||||
built with stable Rust with the `stable` feature.
|
||||
|
||||
```toml
|
||||
librashader = { features = ["stable"] }
|
||||
```
|
||||
|
||||
If building the C API, the `--stable` flag in the build script will enable the `stable` feature.
|
||||
|
||||
```
|
||||
cargo +stable run -p librashader-build-script -- --profile optimized --stable
|
||||
```
|
||||
|
||||
There are some caveats when building against stable Rust, such that building librashader against nightly Rust is still highly encouraged.
|
||||
|
||||
* C headers will not be regenerated when building with the `stable` feature.
|
||||
* There is a minor performance hit in initial shader compilation when building against stable Rust. This is due to boxed trait objects being used instead of `impl Trait`.
|
||||
* A higher MSRV is required when building against stable Rust.
|
||||
|
||||
When the `trait_alias_impl_trait` feature is stabilized, the `stable` feature will be removed.
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -127,9 +174,13 @@ 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)
|
||||
* [Direct3D 9](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d9/tests/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
|
||||
|
||||
|
@ -149,20 +200,25 @@ Please report an issue if you run into a shader that works in RetroArch, but not
|
|||
`mipmap_input0 = "true"`.
|
||||
* The preset parser is a substantially stricter implementation that the one in RetroArch. Not all shader presets may be
|
||||
compatible. If you find this is the case, please file an issue so a workaround can be added.
|
||||
* Shaders are [pre-linked at the SPIR-V level](https://github.com/SnowflakePowered/librashader/blob/master/librashader-reflect/src/front/spirv_passes/link_input_outputs.rs) before being
|
||||
passed to the driver. Unused inputs in the fragment shader are removed, and the corresponding input in the vertex shader
|
||||
is downgraded to a global variable.
|
||||
### Runtime specific differences
|
||||
* OpenGL
|
||||
* Copying of in-flight framebuffer contents to history is done via `glBlitFramebuffer` rather than drawing a quad into an intermediate FBO.
|
||||
* 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,15 +227,17 @@ 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.
|
||||
|
||||
## 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 ABI](https://img.shields.io/badge/ABI%20version-2-yellowgreen)
|
||||
![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 +264,29 @@ 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`.
|
||||
|
||||
The ABI version was bumped from `1` to `2` with librashader `0.5.0`. See [`MIGRATION-ABI2.md`](https://github.com/SnowflakePowered/librashader/blob/master/MIGRATION-ABI2.md)
|
||||
for migration instructions.
|
||||
|
||||
### MSRV Policy
|
||||
|
||||
Building against stable Rust requires the following MSRV.
|
||||
|
||||
* All platforms: **1.78**
|
||||
|
||||
When building against nightly Rust, the following MSRV policy is enforced for unstable library features.
|
||||
|
||||
* All platforms: **1.78**
|
||||
|
||||
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 when building against nightly Rust or with `RUSTC_BOOTSTRAP=1`, 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 +296,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/).
|
||||
|
||||
|
|
|
@ -34,14 +34,8 @@ libra_gl_filter_chain_t load_gl_filter_chain(libra_gl_loader_t opengl, const cha
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// OpenGL runtime needs to be initialized.
|
||||
if (librashader.gl_init_context(opengl) != NULL) {
|
||||
std::cout << "Could not initialize OpenGL context\n";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
libra_gl_filter_chain_t chain;
|
||||
if (librashader.gl_filter_chain_create(&preset, NULL, &chain) {
|
||||
if (librashader.gl_filter_chain_create(&preset, opengl, NULL, &chain) {
|
||||
std::cout << "Could not create OpenGL filter chain\n";
|
||||
}
|
||||
return chain;
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -6,9 +6,9 @@ publish = false
|
|||
|
||||
|
||||
[dependencies]
|
||||
cbindgen = { git = "https://github.com/eqrion/cbindgen" }
|
||||
clap = { version = "4.1.0", features = ["derive"] }
|
||||
|
||||
cbindgen = "0.27.0"
|
||||
clap = { workspace = true }
|
||||
carlog = "0.1.0"
|
||||
|
||||
[package.metadata.release]
|
||||
release = false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use carlog::*;
|
||||
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 +11,18 @@ use std::{env, fs};
|
|||
struct Args {
|
||||
#[arg(long, default_value = "debug", global = true)]
|
||||
profile: String,
|
||||
#[arg(long, global = true)]
|
||||
target: Option<String>,
|
||||
#[arg(long, default_value_t = false, global = true)]
|
||||
stable: bool,
|
||||
#[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();
|
||||
|
@ -23,43 +30,122 @@ pub fn main() {
|
|||
let profile = args.profile;
|
||||
|
||||
let crate_dir = Path::new("librashader-capi");
|
||||
println!("Building librashader C API...");
|
||||
carlog_info!("Building", "librashader C API");
|
||||
|
||||
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
|
||||
let mut cmd = Command::new(&cargo);
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
cmd.args(["--package", "librashader-capi"]);
|
||||
cmd.arg(format!(
|
||||
"--profile={}",
|
||||
if profile == "debug" { "dev" } else { &profile }
|
||||
));
|
||||
Some(cmd.status().expect("Failed to build librashader-capi"));
|
||||
|
||||
let output_dir = PathBuf::from(format!("target/{}", profile))
|
||||
.canonicalize()
|
||||
.expect("Could not find output directory.");
|
||||
// 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");
|
||||
}
|
||||
|
||||
println!("Generating C headers...");
|
||||
if let Some(target) = &args.target {
|
||||
cmd.arg(format!("--target={}", &target));
|
||||
}
|
||||
|
||||
// Create headers.
|
||||
let mut buf = BufWriter::new(Vec::new());
|
||||
cbindgen::generate(crate_dir)
|
||||
.expect("Unable to generate bindings")
|
||||
.write(&mut buf);
|
||||
if args.stable {
|
||||
carlog_warning!("building librashader with stable Rust compatibility");
|
||||
carlog_warning!("C headers will not be generated");
|
||||
cmd.args(["--features", "stable"]);
|
||||
}
|
||||
if !args.cargoflags.is_empty() {
|
||||
cmd.args(args.cargoflags);
|
||||
}
|
||||
|
||||
let bytes = buf.into_inner().expect("Unable to extract bytes");
|
||||
let string = String::from_utf8(bytes).expect("Unable to create string");
|
||||
File::create(output_dir.join("librashader.h"))
|
||||
.expect("Unable to open file")
|
||||
.write_all(string.as_bytes())
|
||||
.expect("Unable to write bindings.");
|
||||
let Ok(status) = cmd.status().inspect_err(|err| {
|
||||
carlog_error!("failed to build librashader-capi");
|
||||
carlog_error!(format!("{err}"));
|
||||
}) else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
|
||||
println!("Moving artifacts...");
|
||||
if cfg!(target_os = "linux") {
|
||||
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 Ok(output_dir) = output_dir.canonicalize() else {
|
||||
carlog_error!("could not find output directory");
|
||||
println!("help: are you running the build script from the repository root?");
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
|
||||
if args.stable {
|
||||
carlog_warning!("generating C headers is not supported when building for stable Rust");
|
||||
} else {
|
||||
carlog_info!("Generating", "librashader C API headers");
|
||||
|
||||
// Create headers.
|
||||
let mut buf = BufWriter::new(Vec::new());
|
||||
let Ok(bindings) = cbindgen::generate(crate_dir).inspect_err(|err| {
|
||||
carlog_error!("unable to generate C API headers");
|
||||
carlog_error!(format!("{err}"));
|
||||
}) else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
|
||||
bindings.write(&mut buf);
|
||||
let bytes = buf.into_inner().expect("Unable to extract bytes");
|
||||
let string = String::from_utf8(bytes).expect("Unable to create string");
|
||||
|
||||
let Ok(mut file) = File::create(output_dir.join("librashader.h")).inspect_err(|err| {
|
||||
carlog_error!("unable to open librashader.h");
|
||||
carlog_error!(format!("{err}"));
|
||||
}) else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
|
||||
let Ok(_) = file.write_all(string.as_bytes()).inspect_err(|err| {
|
||||
carlog_error!("unable to write to librashader.h");
|
||||
carlog_error!(format!("{err}"));
|
||||
}) else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
}
|
||||
|
||||
carlog_info!("Moving", "built artifacts");
|
||||
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", "");
|
||||
|
||||
let Ok(_) =
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
|
||||
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
|
||||
carlog_error!(format!("{err}"));
|
||||
})
|
||||
else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
|
||||
}
|
||||
} 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();
|
||||
let ext = ext.replace("_capi", "");
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
|
||||
let Ok(_) =
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
|
||||
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
|
||||
carlog_error!(format!("{err}"));
|
||||
})
|
||||
else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,11 +156,29 @@ 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", "");
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
|
||||
let Ok(_) =
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
|
||||
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
|
||||
carlog_error!(format!("{err}"));
|
||||
})
|
||||
else {
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
|
||||
}
|
||||
|
||||
if output_dir.join("librashader_capi.pdb").exists() {
|
||||
fs::rename(
|
||||
output_dir.join("librashader_capi.pdb"),
|
||||
output_dir.join("librashader.pdb"),
|
||||
)
|
||||
.unwrap();
|
||||
carlog_ok!("Renamed", "librashader_capi.pdb to librashader.pdb");
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "librashader-cache"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -12,18 +12,22 @@ description = "RetroArch shaders for all."
|
|||
|
||||
[dependencies]
|
||||
serde = { version = "1.0" }
|
||||
librashader-reflect = { path = "../librashader-reflect", version = "0.1.1", features = ["serialize", "dxil"] }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.1" }
|
||||
librashader-reflect = { path = "../librashader-reflect", version = "0.5.1", features = ["serde"] }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
|
||||
platform-dirs = "0.3.0"
|
||||
blake3 = { version = "1.3.3" }
|
||||
blake3 = { version = "1.5.4" }
|
||||
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.x86_64-win7-windows-msvc.dependencies.blake3]
|
||||
version = "1.5.4"
|
||||
features = ["pure"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.44.0"
|
||||
workspace = true
|
||||
features = [
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Direct3D_Fxc",
|
||||
|
@ -32,10 +36,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.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -16,25 +16,55 @@ crate-type = [ "cdylib", "staticlib" ]
|
|||
|
||||
[features]
|
||||
default = ["runtime-all" ]
|
||||
runtime-all = ["runtime-opengl", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan"]
|
||||
runtime-opengl = ["gl", "librashader/runtime-gl"]
|
||||
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
|
||||
runtime-opengl = ["glow", "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 = []
|
||||
stable = ["librashader/stable"]
|
||||
docsrs = []
|
||||
|
||||
__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.1", 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"
|
||||
|
||||
sptr = "0.3.2"
|
||||
|
||||
glow = { workspace = true, optional = true }
|
||||
ash = { workspace = true, optional = true }
|
||||
|
||||
[dependencies.librashader]
|
||||
path = "../librashader"
|
||||
version = "0.5.1"
|
||||
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"]
|
||||
features = ["librashader/docsrs"]
|
||||
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 = ["docsrs", "librashader/docsrs"]
|
||||
|
|
|
@ -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,38 @@ 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",
|
||||
"Option_PFN_vkGetInstanceProcAddr",
|
||||
"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"
|
||||
|
@ -166,6 +199,7 @@ exclude = ["Option_ID3D11DeviceContext"]
|
|||
"CommandBuffer" = "VkCommandBuffer"
|
||||
"Format" = "VkFormat"
|
||||
"Image" = "VkImage"
|
||||
"Queue" = "VkQueue"
|
||||
|
||||
# hack to get proper pointer indirection for COM pointers
|
||||
# we don't need one for ID3D11DeviceContext.
|
||||
|
@ -174,10 +208,22 @@ 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 force cbindgen to not generate option type for nullable PFN_vkGetInstanceProcAddr.
|
||||
"Option_PFN_vkGetInstanceProcAddr" = "PFN_vkGetInstanceProcAddr"
|
||||
|
||||
# hack to get proper pointer indirection for COM pointers
|
||||
"ID3D12Device" = "ID3D12Device *"
|
||||
"ID3D12Resource" = "ID3D12Resource *"
|
||||
"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,42 +8,157 @@ 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 {
|
||||
/// Context parameter for vertical orientation.
|
||||
Vertical = 0,
|
||||
/// Context parameter for horizontal orientation.
|
||||
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 {
|
||||
/// No runtime.
|
||||
None = 0,
|
||||
/// OpenGL 3.3+
|
||||
GlCore,
|
||||
/// Vulkan
|
||||
Vulkan,
|
||||
/// Direct3D 11
|
||||
D3D11,
|
||||
/// Direct3D 12
|
||||
D3D12,
|
||||
/// Metal
|
||||
Metal,
|
||||
/// Direct3D 9
|
||||
D3D9_HLSL,
|
||||
}
|
||||
|
||||
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,
|
||||
LIBRA_PRESET_CTX_RUNTIME::D3D9_HLSL => VideoDriver::Direct3D9Hlsl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>>;
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
|
||||
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
|
||||
|
||||
/// 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")
|
||||
))]
|
||||
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
|
||||
|
||||
/// A handle to a Direct3D 11 filter chain.
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
|
||||
)]
|
||||
#[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.
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
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>>;
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
|
||||
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
|
||||
|
||||
/// Defines the output viewport for a rendered frame.
|
||||
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
|
||||
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
|
||||
|
||||
/// A handle to a Vulkan filter chain.
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
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 origin for a rendered frame.
|
||||
#[repr(C)]
|
||||
pub struct libra_viewport_t {
|
||||
/// The x offset in the viewport framebuffer to begin rendering from.
|
||||
pub x: f32,
|
||||
/// The y offset in the viewport framebuffer to begin rendering from.
|
||||
pub y: f32,
|
||||
/// The width of the viewport framebuffer.
|
||||
/// The width extent of the viewport framebuffer to end rendering, relative to
|
||||
/// the origin specified by x.
|
||||
pub width: u32,
|
||||
/// The height of the viewport framebuffer.
|
||||
/// The height extent of the viewport framebuffer to end rendering, relative to
|
||||
/// the origin specified by y.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
|
@ -54,29 +170,85 @@ where
|
|||
}
|
||||
|
||||
macro_rules! config_set_field {
|
||||
($options:ident.$field:ident <- $ptr:ident) => {
|
||||
(@POINTER $options:ident.$field:ident <- $ptr:ident) => {
|
||||
$options.$field = unsafe { ::std::ptr::addr_of!((*$ptr).$field).read() };
|
||||
};
|
||||
(@POINTER @NEGATIVE $options:ident.$field:ident <- $ptr:ident) => {
|
||||
$options.$field = unsafe { !::std::ptr::addr_of!((*$ptr).$field).read() };
|
||||
};
|
||||
(@LITERAL $options:ident.$field:ident <- $value:literal) => {
|
||||
$options.$field = $value;
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! config_version_set {
|
||||
($version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
|
||||
let version = unsafe { ::std::ptr::addr_of!((*$ptr).version).read() };
|
||||
// "optimized" version for normal behaviour
|
||||
(@ROOT $realver:ident $version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
|
||||
#[allow(unused_comparisons)]
|
||||
if version >= $version {
|
||||
$($crate::ctypes::config_set_field!($options.$field <- $ptr);)+
|
||||
if $realver >= $version {
|
||||
$($crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);)+
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Repeater
|
||||
(@ROOT $realver:ident $version:literal => [$($field:tt),+ $(,)?] ($options:ident <- $ptr:ident)) => {
|
||||
$(crate::ctypes::config_version_set!(@SINGLE $realver $version => [$field] ($options <- $ptr));)+
|
||||
};
|
||||
|
||||
// Allow overriding default value with a literal for older versions
|
||||
(@SINGLE $realver:ident $version:literal => [($field:ident: $value:literal)] ($options:ident <- $ptr:ident)) => {
|
||||
#[allow(unused_comparisons)]
|
||||
if $realver >= $version {
|
||||
$crate::ctypes::config_set_field!(@LITERAL $options.$field <- $value);
|
||||
}
|
||||
};
|
||||
|
||||
// Allow negation of prior variables that is version dependent.
|
||||
(@SINGLE $realver:ident $version:literal => [(!$field:ident)] ($options:ident <- $ptr:ident)) => {
|
||||
#[allow(unused_comparisons)]
|
||||
if $realver >= $version {
|
||||
$crate::ctypes::config_set_field!(@POINTER @NEGATIVE $options.$field <- $ptr);
|
||||
}
|
||||
};
|
||||
|
||||
(@SINGLE $realver:ident $version:literal => [$field:ident] ($options:ident <- $ptr:ident)) => {
|
||||
#[allow(unused_comparisons)]
|
||||
if $realver >= $version {
|
||||
$crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to declare a configuration struct, with options to change behaviour based on
|
||||
/// API version.
|
||||
///
|
||||
/// For example following declaration does the following
|
||||
///
|
||||
/// * Declare `frames_in_flight`, `use_dynamic_rendering` for API version 0, with the following forward compatibility statements
|
||||
/// * Inverts the behaviour of `use_dynamic_rendering` compared to API version 1.
|
||||
/// * `disable_cache` is defaulted to `true` for API version 0, regardless of `Default::default`
|
||||
/// but is not declared for API 0.
|
||||
/// * Declare `use_dynamic_rendering` with normal behaviour, and `disable_cache` for API version 1.
|
||||
/// * All fields that are undeclared inherit `Default::default`
|
||||
///
|
||||
/// ```rust
|
||||
/// config_struct! {
|
||||
/// impl FilterChainOptions => filter_chain_vk_opt_t {
|
||||
/// 0 => [frames_in_flight, (!use_dynamic_rendering), (disable_cache: true)];
|
||||
/// 1 => [use_dynamic_rendering, disable_cache];
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! config_struct {
|
||||
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:ident),+ $(,)?]);+ $(;)?}) => {
|
||||
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:tt),+]);+ $(;)?}) => {
|
||||
impl $crate::ctypes::FromUninit<$rust> for $capi {
|
||||
fn from_uninit(value: ::std::mem::MaybeUninit<Self>) -> $rust {
|
||||
let ptr = value.as_ptr();
|
||||
let version = unsafe { ::std::ptr::addr_of!((*ptr).version).read() };
|
||||
|
||||
let mut options = <$rust>::default();
|
||||
$(
|
||||
$crate::ctypes::config_version_set!($version => [$($field),+] (options <- ptr));
|
||||
$crate::ctypes::config_version_set!(@ROOT version $version => [$($field),+] (options <- ptr));
|
||||
)+
|
||||
options
|
||||
}
|
||||
|
@ -87,3 +259,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,54 +5,120 @@ 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 {
|
||||
/// An unknown error or panic occurred.
|
||||
#[error("There was an unknown error.")]
|
||||
UnknownError(Box<dyn Any + Send + 'static>),
|
||||
|
||||
/// An invalid parameter (likely null), was passed.
|
||||
#[error("The parameter was null or invalid.")]
|
||||
InvalidParameter(&'static str),
|
||||
|
||||
/// The string provided was not valid UTF-8.
|
||||
#[error("The provided string was not valid UTF8.")]
|
||||
InvalidString(#[from] std::str::Utf8Error),
|
||||
|
||||
/// An error occurred in the preset parser.
|
||||
#[error("There was an error parsing the preset.")]
|
||||
PresetError(#[from] librashader::presets::ParsePresetError),
|
||||
|
||||
/// An error occurred in the shader preprocessor.
|
||||
#[error("There was an error preprocessing the shader source.")]
|
||||
PreprocessError(#[from] librashader::preprocess::PreprocessError),
|
||||
|
||||
/// An error occurred in the shader compiler.
|
||||
#[error("There was an error compiling the shader source.")]
|
||||
ShaderCompileError(#[from] librashader::reflect::ShaderCompileError),
|
||||
|
||||
/// An error occrred when validating and reflecting the shader.
|
||||
#[error("There was an error reflecting the shader source.")]
|
||||
ShaderReflectError(#[from] librashader::reflect::ShaderReflectError),
|
||||
|
||||
/// An invalid shader parameter name was provided.
|
||||
#[error("The provided parameter name was invalid.")]
|
||||
UnknownShaderParameter(*const c_char),
|
||||
|
||||
/// An error occurred with the OpenGL filter chain.
|
||||
#[cfg(feature = "runtime-opengl")]
|
||||
#[doc(cfg(feature = "runtime-opengl"))]
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
|
||||
#[error("There was an error in the OpenGL filter chain.")]
|
||||
OpenGlFilterError(#[from] librashader::runtime::gl::error::FilterChainError),
|
||||
|
||||
/// An error occurred with the Direct3D 11 filter chain.
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
|
||||
)]
|
||||
#[error("There was an error in the D3D11 filter chain.")]
|
||||
D3D11FilterError(#[from] librashader::runtime::d3d11::error::FilterChainError),
|
||||
|
||||
/// An error occurred with the Direct3D 12 filter chain.
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
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),
|
||||
|
||||
/// An error occurred with the Direct3D 9 filter chain.
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
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),
|
||||
|
||||
/// An error occurred with the Vulkan filter chain.
|
||||
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
#[doc(cfg(feature = "runtime-vulkan"))]
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
|
||||
#[error("There was an error in the Vulkan filter chain.")]
|
||||
VulkanFilterError(#[from] librashader::runtime::vk::error::FilterChainError),
|
||||
|
||||
/// An error occurred with the Metal filter chain.
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
|
||||
)]
|
||||
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
|
||||
#[error("There was an error in the Metal filter chain.")]
|
||||
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
|
||||
/// This error is unreachable.
|
||||
#[error("This error is not reachable")]
|
||||
Infallible(#[from] std::convert::Infallible),
|
||||
}
|
||||
|
||||
/// Error codes for librashader error types.
|
||||
#[repr(i32)]
|
||||
pub enum LIBRA_ERRNO {
|
||||
/// Error code for an unknown error.
|
||||
UNKNOWN_ERROR = 0,
|
||||
|
||||
/// Error code for an invalid parameter.
|
||||
INVALID_PARAMETER = 1,
|
||||
|
||||
/// Error code for an invalid (non-UTF8) string.
|
||||
INVALID_STRING = 2,
|
||||
|
||||
/// Error code for a preset parser error.
|
||||
PRESET_ERROR = 3,
|
||||
|
||||
/// Error code for a preprocessor error.
|
||||
PREPROCESS_ERROR = 4,
|
||||
|
||||
/// Error code for a shader parameter error.
|
||||
SHADER_PARAMETER_ERROR = 5,
|
||||
|
||||
/// Error code for a reflection error.
|
||||
REFLECT_ERROR = 6,
|
||||
|
||||
/// Error code for a runtime error.
|
||||
RUNTIME_ERROR = 7,
|
||||
}
|
||||
|
||||
|
@ -67,7 +133,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 +148,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 +194,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 +202,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 +251,13 @@ 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,
|
||||
LibrashaderError::Infallible(_) => LIBRA_ERRNO::UNKNOWN_ERROR,
|
||||
}
|
||||
}
|
||||
pub(crate) const fn ok() -> libra_error_t {
|
||||
|
@ -203,13 +270,13 @@ impl LibrashaderError {
|
|||
}
|
||||
|
||||
macro_rules! assert_non_null {
|
||||
($value:ident) => {
|
||||
if $value.is_null() || !$value.is_aligned() {
|
||||
(@EXPORT $value:ident) => {
|
||||
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() {
|
||||
($value:ident) => {
|
||||
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
|
||||
return Err($crate::error::LibrashaderError::InvalidParameter(
|
||||
stringify!($value),
|
||||
));
|
||||
|
@ -220,14 +287,18 @@ macro_rules! assert_non_null {
|
|||
macro_rules! assert_some_ptr {
|
||||
($value:ident) => {
|
||||
if $value.is_none() {
|
||||
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
|
||||
return Err($crate::error::LibrashaderError::InvalidParameter(
|
||||
stringify!($value),
|
||||
));
|
||||
}
|
||||
|
||||
let $value = unsafe { $value.as_ref().unwrap_unchecked().as_ref() };
|
||||
};
|
||||
(mut $value:ident) => {
|
||||
if $value.is_none() {
|
||||
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
|
||||
return Err($crate::error::LibrashaderError::InvalidParameter(
|
||||
stringify!($value),
|
||||
));
|
||||
}
|
||||
|
||||
let $value = unsafe { $value.as_mut().unwrap_unchecked().as_mut() };
|
||||
|
|
|
@ -1,9 +1,40 @@
|
|||
macro_rules! wrap_ok {
|
||||
($e:expr) => {
|
||||
::core::iter::empty().try_fold($e, |_, __x: ::core::convert::Infallible| match __x {})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ffi_body {
|
||||
(nopanic $body:block) => {
|
||||
{
|
||||
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
|
||||
$body
|
||||
}))();
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
};
|
||||
e.export()
|
||||
}
|
||||
};
|
||||
($body:block) => {
|
||||
{
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
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!(@EXPORT $ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
|
||||
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
|
||||
$body
|
||||
};
|
||||
}))();
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
|
@ -13,13 +44,20 @@ 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 };)*
|
||||
$($crate::error::assert_non_null!($mut_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!(@EXPORT $mut_capture);)*
|
||||
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
|
||||
$body
|
||||
};
|
||||
}))();
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
|
@ -28,12 +66,21 @@ macro_rules! ffi_body {
|
|||
}
|
||||
};
|
||||
(mut |$($mut_capture:ident),*| $body:block) => {
|
||||
{
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic mut |$($mut_capture),*| $body)
|
||||
}));
|
||||
|
||||
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
|
||||
}
|
||||
};
|
||||
(nopanic |$($ref_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 {
|
||||
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
|
||||
$body
|
||||
};
|
||||
}))();
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
|
@ -43,35 +90,17 @@ macro_rules! ffi_body {
|
|||
};
|
||||
(|$($ref_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
$body
|
||||
};
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic |$($ref_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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 +115,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 +170,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)) }
|
||||
}
|
||||
|
||||
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
|
||||
let align = std::mem::align_of::<T>();
|
||||
if !align.is_power_of_two() {
|
||||
panic!("is_aligned_to: align is not a power-of-two");
|
||||
}
|
||||
sptr::Strict::addr(ptr) & (align - 1) == 0
|
||||
}
|
||||
|
||||
pub(crate) use extern_fn;
|
||||
pub(crate) use ffi_body;
|
||||
pub(crate) use wrap_ok;
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#![feature(doc_cfg)]
|
||||
#![forbid(missing_docs)]
|
||||
//! The C API for [librashader](https://docs.rs/librashader/).
|
||||
//!
|
||||
//! The librashader C API is designed to be loaded dynamically via `librashader_ld.h`, but static usage is also
|
||||
//! 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,36 @@
|
|||
//!
|
||||
//! ## 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.
|
||||
//!
|
||||
//! Setting and retrieving filter parameters from any thread, regardless of the lack of other thread safety-guarantees
|
||||
//! of the runtime, is always thread safe.
|
||||
//!
|
||||
//! 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.
|
||||
|
||||
#![cfg_attr(feature = "docsrs", feature(doc_cfg))]
|
||||
#![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;
|
||||
|
@ -14,11 +14,10 @@ const _: () = crate::assert_thread_safe::<ShaderPreset>();
|
|||
pub struct libra_preset_param_list_t {
|
||||
/// A pointer to the parameter
|
||||
pub parameters: *const libra_preset_param_t,
|
||||
/// The number of parameters in the list.
|
||||
/// The number of parameters in the list. This field
|
||||
/// is readonly, and changing it will lead to undefined
|
||||
/// behaviour on free.
|
||||
pub length: u64,
|
||||
/// For internal use only.
|
||||
/// Changing this causes immediate undefined behaviour on freeing this parameter list.
|
||||
pub _internal_alloc: u64,
|
||||
}
|
||||
|
||||
/// A preset parameter.
|
||||
|
@ -55,7 +54,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 +64,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 +114,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 +129,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,
|
||||
|
@ -116,7 +157,7 @@ extern_fn! {
|
|||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
/// - `value` may be a pointer to a uninitialized `float`.
|
||||
fn libra_preset_get_param(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
preset: *const libra_shader_preset_t,
|
||||
name: *const c_char,
|
||||
value: *mut MaybeUninit<f32>
|
||||
) |name, preset| {
|
||||
|
@ -135,7 +176,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 +187,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
|
||||
|
@ -154,7 +195,7 @@ extern_fn! {
|
|||
/// - It is safe to call `libra_preset_get_runtime_params` multiple times, however
|
||||
/// the output struct must only be freed once per call.
|
||||
fn libra_preset_get_runtime_params(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
preset: *const libra_shader_preset_t,
|
||||
out: *mut MaybeUninit<libra_preset_param_list_t>
|
||||
) |preset| {
|
||||
assert_some_ptr!(preset);
|
||||
|
@ -163,7 +204,7 @@ extern_fn! {
|
|||
let iter = librashader::presets::get_parameter_meta(preset)?;
|
||||
let mut values = Vec::new();
|
||||
for param in iter {
|
||||
let name = CString::new(param.id)
|
||||
let name = CString::new(param.id.to_string())
|
||||
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
|
||||
let description = CString::new(param.description)
|
||||
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
|
||||
|
@ -176,12 +217,14 @@ 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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -209,9 +252,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());
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::error;
|
||||
|
||||
use librashader::presets::{ShaderPassConfig, ShaderPreset};
|
||||
use librashader::presets::{PassConfig, ShaderPreset};
|
||||
use librashader::reflect::semantics::ShaderSemantics;
|
||||
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,
|
||||
|
@ -21,7 +21,7 @@ pub(crate) struct LookupTexture {
|
|||
|
||||
pub(crate) struct PassReflection {
|
||||
reflection: ShaderReflection,
|
||||
config: ShaderPassConfig,
|
||||
config: PassConfig,
|
||||
spirv: ShaderCompilerOutput<Vec<u32>>,
|
||||
}
|
||||
pub(crate) struct FilterReflection {
|
||||
|
@ -35,11 +35,12 @@ impl FilterReflection {
|
|||
preset: ShaderPreset,
|
||||
direction: UVDirection,
|
||||
) -> Result<FilterReflection, error::LibrashaderError> {
|
||||
let (passes, textures) = (preset.shaders, preset.textures);
|
||||
let (passes, textures) = (preset.passes, preset.textures);
|
||||
|
||||
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
|
||||
Glslang,
|
||||
SPIRV,
|
||||
GlslangCompilation,
|
||||
SpirvCompilation,
|
||||
error::LibrashaderError,
|
||||
>(passes, &textures)?;
|
||||
|
||||
|
|
|
@ -3,46 +3,21 @@ 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::d3d11::error::FilterChainError;
|
||||
use librashader::runtime::{FilterChainParameters, Size, 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.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
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 +33,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 +49,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, 3 = 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 +96,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 +156,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,
|
||||
|
@ -197,17 +179,33 @@ const _: () = assert!(
|
|||
extern_fn! {
|
||||
/// Draw a frame with the given parameters for the given filter chain.
|
||||
///
|
||||
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
|
||||
/// it will record commands onto the provided context. If the context is deferred, librashader
|
||||
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
|
||||
// the filter chain was created with.
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `device_context` is the ID3D11DeviceContext used to record draw commands to.
|
||||
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
|
||||
/// it will record commands onto the provided context. If the context is deferred, librashader
|
||||
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
|
||||
/// the filter chain was created with.
|
||||
///
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a pointer to a `ID3D11ShaderResourceView` that will serve as the source image for the frame.
|
||||
/// - `out` is a pointer to a `ID3D11RenderTargetView` that will serve as the render target for the frame.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## 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_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,15 +214,15 @@ 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.
|
||||
device_context: Option<ManuallyDrop<ID3D11DeviceContext>>,
|
||||
frame_count: usize,
|
||||
image: libra_source_image_d3d11_t,
|
||||
viewport: libra_viewport_t,
|
||||
image: ManuallyDrop<ID3D11ShaderResourceView>,
|
||||
out: ManuallyDrop<ID3D11RenderTargetView>,
|
||||
viewport: *const libra_viewport_t,
|
||||
mvp: *const f32,
|
||||
options: *const MaybeUninit<frame_d3d11_opt_t>
|
||||
) mut |chain| {
|
||||
|
@ -242,22 +240,27 @@ extern_fn! {
|
|||
Some(unsafe { options.read() })
|
||||
};
|
||||
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: D3D11OutputView {
|
||||
size: Size::new(viewport.width, viewport.height),
|
||||
handle: ManuallyDrop::into_inner(out.clone()),
|
||||
},
|
||||
mvp,
|
||||
let viewport = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(out.deref(), mvp)
|
||||
.map_err(|e| LibrashaderError::D3D11FilterError(FilterChainError::Direct3DError(e)))?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: out.deref(),
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
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.deref(), &viewport, frame_count, options.as_ref())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,15 +276,15 @@ extern_fn! {
|
|||
chain: *mut libra_d3d11_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,18 +298,18 @@ extern_fn! {
|
|||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_d3d11_filter_chain_get_param(
|
||||
chain: *mut libra_d3d11_filter_chain_t,
|
||||
chain: *const libra_d3d11_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
|
@ -322,9 +325,9 @@ extern_fn! {
|
|||
fn libra_d3d11_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_d3d11_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,12 +337,12 @@ extern_fn! {
|
|||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
|
||||
fn libra_d3d11_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_d3d11_filter_chain_t,
|
||||
chain: *const libra_d3d11_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
unsafe {
|
||||
let value = chain.get_enabled_pass_count();
|
||||
let value = chain.parameters().passes_enabled();
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,26 +13,53 @@ 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};
|
||||
|
||||
/// Tagged union for a Direct3D 12 image
|
||||
#[repr(C)]
|
||||
pub struct libra_image_d3d12_t {
|
||||
/// The type of the image.
|
||||
pub image_type: LIBRA_D3D12_IMAGE_TYPE,
|
||||
/// The handle to the image.
|
||||
pub handle: libra_image_d3d12_handle_t,
|
||||
}
|
||||
|
||||
/// A handle to a Direct3D 12 image.
|
||||
///
|
||||
/// This must be either a pointer to a `ID3D12Resource`,
|
||||
/// or a valid source or output image type.
|
||||
#[repr(C)]
|
||||
pub union libra_image_d3d12_handle_t {
|
||||
/// A pointer to an `ID3D12Resource`, with descriptors managed by the filter chain.
|
||||
pub resource: ManuallyDrop<ID3D12Resource>,
|
||||
/// A source image with externally managed descriptors.
|
||||
pub source: ManuallyDrop<libra_source_image_d3d12_t>,
|
||||
/// An output image with externally managed descriptors.
|
||||
pub output: ManuallyDrop<libra_output_image_d3d12_t>,
|
||||
}
|
||||
|
||||
/// The type of image passed to the image.
|
||||
#[repr(i32)]
|
||||
pub enum LIBRA_D3D12_IMAGE_TYPE {
|
||||
/// The image handle is a pointer to a `ID3D12Resource`.
|
||||
RESOURCE = 0,
|
||||
/// The image handle is a `libra_source_image_d3d12_t`
|
||||
SOURCE_IMAGE = 1,
|
||||
/// The image handle is a `libra_output_image_d3d12_t`
|
||||
OUTPUT_IMAGE = 2,
|
||||
}
|
||||
|
||||
/// Direct3D 12 parameters for the source image.
|
||||
#[repr(C)]
|
||||
pub struct libra_source_image_d3d12_t {
|
||||
/// The resource containing the image.
|
||||
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.
|
||||
pub format: DXGI_FORMAT,
|
||||
/// The width of the source image.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
pub height: u32,
|
||||
/// The resource containing the image.
|
||||
pub resource: ManuallyDrop<ID3D12Resource>,
|
||||
}
|
||||
|
||||
/// Direct3D 12 parameters for the output image.
|
||||
|
@ -42,6 +69,10 @@ pub struct libra_output_image_d3d12_t {
|
|||
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
|
||||
/// The format of the image.
|
||||
pub format: DXGI_FORMAT,
|
||||
/// The width of the output image.
|
||||
pub width: u32,
|
||||
/// The height of the output image.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// Options for each Direct3D 12 shader frame.
|
||||
|
@ -55,11 +86,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, 3 = 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,26 +122,11 @@ 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];
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<libra_source_image_d3d12_t> for D3D12InputImage {
|
||||
type Error = LibrashaderError;
|
||||
|
||||
fn try_from(value: libra_source_image_d3d12_t) -> Result<Self, Self::Error> {
|
||||
let resource = value.resource.clone();
|
||||
|
||||
Ok(D3D12InputImage {
|
||||
resource: ManuallyDrop::into_inner(resource),
|
||||
descriptor: value.descriptor,
|
||||
size: Size::new(value.width, value.height),
|
||||
format: value.format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Create the filter chain given the shader preset.
|
||||
///
|
||||
|
@ -136,7 +159,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 +211,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,
|
||||
|
@ -206,33 +229,64 @@ extern_fn! {
|
|||
/// Records rendering commands for a frame with the given parameters for the given filter chain
|
||||
/// to the input command list.
|
||||
///
|
||||
/// * The input image must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state.
|
||||
/// * The output image must be in `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state.
|
||||
///
|
||||
/// librashader **will not** create a resource barrier for the final pass. The output image will
|
||||
/// A resource barrier **will not** be created for the final pass. The output image will
|
||||
/// remain in `D3D12_RESOURCE_STATE_RENDER_TARGET` after all shader passes. The caller must transition
|
||||
/// the output image to the final resource state.
|
||||
///
|
||||
/// The refcount of any COM pointers passed into this frame will not be changed. It is the responsibility
|
||||
/// of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is
|
||||
/// submitted.
|
||||
///
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `command_list` is a `ID3D12GraphicsCommandList` to record draw commands to.
|
||||
/// The provided command list must be open and associated with the `ID3D12Device` this filter chain was created with.
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a `libra_image_d3d12_t` with `image_type` set to `IMAGE_TYPE_SOURCE_IMAGE` or `IMAGE_TYPE_RESOURCE`,
|
||||
/// with `image_handle` either a `ID3D12Resource*` or an `libra_source_image_d3d12_t` containing a CPU descriptor to a shader resource view,
|
||||
/// and the image resource the view is of, which will serve as the source image for the frame.
|
||||
/// The input image resource must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state
|
||||
/// or equivalent barrier layout. The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
|
||||
/// - `out` is a `libra_image_d3d12_t`, with `image_type` set to `IMAGE_TYPE_OUTPUT_IMAGE` or `IMAGE_TYPE_RESOURCE`,
|
||||
/// with `image_handle` being either a `ID3D12Resource*` or an `libra_output_image_d3d12_t`, containing a CPU descriptor handle,
|
||||
/// format, and size information for the render target of the frame. The output image must be in
|
||||
/// `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state or equivalent barrier layout.
|
||||
/// The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## 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_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.
|
||||
/// - Any resource pointers contained within a `libra_image_d3d12_t` must be non-null.
|
||||
/// - The `handle` field of any `libra_image_d3d12_t` must be valid for it's `image_type`.
|
||||
/// - If `image_type` is `IMAGE_TYPE_RESOURCE`, then `handle` must be `ID3D12Resource *`.
|
||||
/// - If `image_type` is `IMAGE_TYPE_SOURCE`, then `handle` must be `libra_source_image_d3d12_t`.
|
||||
/// - If `image_type` is `IMAGE_TYPE_OUTPUT`, then `handle` must be `libra_output_image_d3d12_t`.
|
||||
/// - `command_list` must be a non-null pointer to a `ID3D12GraphicsCommandList` that is open,
|
||||
/// and must be associated with the `ID3D12Device` this filter chain was created with.
|
||||
/// - All resource pointers contained within a `libra_image_d3d12_t` must remain valid until the `ID3D12GraphicsCommandList *`
|
||||
/// provided is submitted after the call to this function.
|
||||
/// - 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,
|
||||
image: libra_source_image_d3d12_t,
|
||||
viewport: libra_viewport_t,
|
||||
out: libra_output_image_d3d12_t,
|
||||
image: libra_image_d3d12_t,
|
||||
out: libra_image_d3d12_t,
|
||||
viewport: *const libra_viewport_t,
|
||||
mvp: *const f32,
|
||||
options: *const MaybeUninit<frame_d3d12_opt_t>
|
||||
) mut |chain| {
|
||||
|
@ -251,14 +305,65 @@ extern_fn! {
|
|||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: unsafe { D3D12OutputView::new_from_raw(out.descriptor, Size::new(viewport.width, viewport.height), out.format) },
|
||||
mvp,
|
||||
|
||||
let output = unsafe {
|
||||
match out.image_type {
|
||||
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
|
||||
let out = out.handle.resource;
|
||||
D3D12OutputView::new_from_resource(
|
||||
out,
|
||||
chain,
|
||||
)?
|
||||
}
|
||||
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
|
||||
let out = out.handle.output;
|
||||
D3D12OutputView::new_from_raw(
|
||||
out.descriptor,
|
||||
Size::new(out.width, out.height),
|
||||
out.format,
|
||||
)
|
||||
}
|
||||
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
|
||||
return Err(LibrashaderError::InvalidParameter("out"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let viewport = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(output, mvp)?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output,
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
mvp,
|
||||
}
|
||||
};
|
||||
|
||||
let image = unsafe {
|
||||
match image.image_type {
|
||||
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
|
||||
let image = image.handle.resource;
|
||||
D3D12InputImage::Managed(image)
|
||||
}
|
||||
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
|
||||
let image = ManuallyDrop::into_inner(image.handle.source);
|
||||
D3D12InputImage::External {
|
||||
resource: image.resource,
|
||||
descriptor: image.descriptor,
|
||||
}
|
||||
}
|
||||
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
|
||||
return Err(LibrashaderError::InvalidParameter("image"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let image = image.try_into()?;
|
||||
unsafe {
|
||||
chain.frame(&command_list, image, &viewport, frame_count, options.as_ref())?;
|
||||
}
|
||||
|
@ -276,15 +381,15 @@ extern_fn! {
|
|||
chain: *mut libra_d3d12_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,18 +403,18 @@ extern_fn! {
|
|||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_d3d12_filter_chain_get_param(
|
||||
chain: *mut libra_d3d12_filter_chain_t,
|
||||
chain: *const libra_d3d12_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
|
@ -325,9 +430,9 @@ extern_fn! {
|
|||
fn libra_d3d12_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_d3d12_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,12 +442,12 @@ extern_fn! {
|
|||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
|
||||
fn libra_d3d12_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_d3d12_filter_chain_t,
|
||||
chain: *const libra_d3d12_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
unsafe {
|
||||
let value = chain.get_enabled_pass_count();
|
||||
let value = chain.parameters().passes_enabled();
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
}
|
||||
|
|
285
librashader-capi/src/runtime/d3d9/filter_chain.rs
Normal file
285
librashader-capi/src/runtime/d3d9/filter_chain.rs
Normal file
|
@ -0,0 +1,285 @@
|
|||
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::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
use librashader::runtime::d3d9::error::FilterChainError;
|
||||
use librashader::runtime::{FilterChainParameters, Size, 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, 3 = 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.
|
||||
///
|
||||
/// ## Parameters
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a pointer to a `IDirect3DTexture9` that will serve as the source image for the frame.
|
||||
/// - `out` is a pointer to a `IDirect3DSurface9` that will serve as the render target for the frame.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
|
||||
/// function will return an error.
|
||||
/// - `viewport` may be null, or if it is not null, must be an aligned pointer to an instance of `libra_viewport_t`.
|
||||
/// - `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>,
|
||||
out: ManuallyDrop<IDirect3DSurface9>,
|
||||
viewport: *const libra_viewport_t,
|
||||
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 = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(out.deref(), mvp)
|
||||
.map_err(|e| LibrashaderError::D3D9FilterError(FilterChainError::Direct3DError(e)))?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: out.deref(),
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
mvp,
|
||||
}
|
||||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
|
||||
unsafe {
|
||||
chain.frame(image.deref(), &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
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
assert_non_null!(param_name);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: *const libra_d3d9_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
assert_non_null!(param_name);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
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
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(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: *const libra_d3d9_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
unsafe {
|
||||
let value = chain.parameters().passes_enabled();
|
||||
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,50 +3,42 @@ 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 std::ffi::CStr;
|
||||
use std::ffi::{c_char, c_void, CString};
|
||||
use std::mem::MaybeUninit;
|
||||
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::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
|
||||
use librashader::runtime::FilterChainParameters;
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A GL function loader that librashader needs to be initialized with.
|
||||
pub type libra_gl_loader_t = unsafe extern "system" fn(*const c_char) -> *const c_void;
|
||||
|
||||
/// OpenGL parameters for the source image.
|
||||
/// OpenGL parameters for an image.
|
||||
#[repr(C)]
|
||||
pub struct libra_source_image_gl_t {
|
||||
/// A texture GLuint to the source image.
|
||||
pub struct libra_image_gl_t {
|
||||
/// A texture GLuint to the texture.
|
||||
pub handle: u32,
|
||||
/// The format of the source image.
|
||||
/// The format of the texture.
|
||||
pub format: u32,
|
||||
/// The width of the source image.
|
||||
/// The width of the texture.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
/// The height of the texture.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// OpenGL parameters for the output framebuffer.
|
||||
#[repr(C)]
|
||||
pub struct libra_output_framebuffer_gl_t {
|
||||
/// A framebuffer GLuint to the output framebuffer.
|
||||
pub fbo: u32,
|
||||
/// A texture GLuint to the logical buffer of the output framebuffer.
|
||||
pub texture: u32,
|
||||
/// The format of the output framebuffer.
|
||||
pub format: u32,
|
||||
}
|
||||
impl From<libra_image_gl_t> for GLImage {
|
||||
fn from(value: libra_image_gl_t) -> Self {
|
||||
let handle = NonZeroU32::try_from(value.handle)
|
||||
.ok()
|
||||
.map(glow::NativeTexture);
|
||||
|
||||
impl From<libra_source_image_gl_t> for GLImage {
|
||||
fn from(value: libra_source_image_gl_t) -> Self {
|
||||
GLImage {
|
||||
handle: value.handle,
|
||||
handle,
|
||||
format: value.format,
|
||||
size: Size::new(value.width, value.height),
|
||||
}
|
||||
|
@ -64,11 +56,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, 3 = 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,32 +91,11 @@ 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];
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Initialize the OpenGL Context for librashader.
|
||||
///
|
||||
/// This only has to be done once throughout the lifetime of the application,
|
||||
/// unless for whatever reason you switch OpenGL loaders mid-flight.
|
||||
///
|
||||
/// ## Safety
|
||||
/// Attempting to create a filter chain will fail if the GL context is not initialized.
|
||||
///
|
||||
/// Reinitializing the OpenGL context with a different loader immediately invalidates previous filter
|
||||
/// chain objects, and drawing with them causes immediate undefined behaviour.
|
||||
raw fn libra_gl_init_context(loader: libra_gl_loader_t) {
|
||||
gl::load_with(|s| unsafe {
|
||||
let proc_name = CString::new(s).unwrap_unchecked();
|
||||
loader(proc_name.as_ptr())
|
||||
});
|
||||
|
||||
LibrashaderError::ok()
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Create the filter chain given the shader preset.
|
||||
///
|
||||
|
@ -130,6 +108,7 @@ extern_fn! {
|
|||
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
|
||||
fn libra_gl_filter_chain_create(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
loader: libra_gl_loader_t,
|
||||
options: *const MaybeUninit<filter_chain_gl_opt_t>,
|
||||
out: *mut MaybeUninit<libra_gl_filter_chain_t>
|
||||
) {
|
||||
|
@ -149,7 +128,11 @@ 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 context = glow::Context::from_loader_function_cstr(
|
||||
|proc_name| loader(proc_name.as_ptr()));
|
||||
|
||||
let chain = FilterChain::load_from_preset(*preset,
|
||||
Arc::new(context), options.as_ref())?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
|
@ -161,6 +144,23 @@ extern_fn! {
|
|||
extern_fn! {
|
||||
/// Draw a frame with the given parameters for the given filter chain.
|
||||
///
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a `libra_image_gl_t`, containing the name of a Texture, format, and size information to
|
||||
/// to an image that will serve as the source image for the frame.
|
||||
/// - `out` is a `libra_output_framebuffer_gl_t`, containing the name of a Framebuffer, the name of a Texture, format,
|
||||
/// and size information for the render target of the frame.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
|
||||
/// function will return an error.
|
||||
|
@ -172,17 +172,19 @@ 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,
|
||||
viewport: libra_viewport_t,
|
||||
out: libra_output_framebuffer_gl_t,
|
||||
image: libra_image_gl_t,
|
||||
out: libra_image_gl_t,
|
||||
viewport: *const libra_viewport_t,
|
||||
mvp: *const f32,
|
||||
opt: *const MaybeUninit<frame_gl_opt_t>,
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
let image: GLImage = image.into();
|
||||
let out: GLImage = out.into();
|
||||
|
||||
let mvp = if mvp.is_null() {
|
||||
None
|
||||
} else {
|
||||
|
@ -195,12 +197,21 @@ extern_fn! {
|
|||
};
|
||||
|
||||
let opt = opt.map(FromUninit::from_uninit);
|
||||
let framebuffer = GLFramebuffer::new_from_raw(out.texture, out.fbo, out.format, Size::new(viewport.width, viewport.height), 1);
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: &framebuffer,
|
||||
mvp,
|
||||
|
||||
let viewport = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(&out, mvp)?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: &out,
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
mvp,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
|
@ -220,15 +231,15 @@ extern_fn! {
|
|||
chain: *mut libra_gl_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,18 +253,18 @@ extern_fn! {
|
|||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_gl_filter_chain_get_param(
|
||||
chain: *mut libra_gl_filter_chain_t,
|
||||
chain: *const libra_gl_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
|
@ -269,9 +280,9 @@ extern_fn! {
|
|||
fn libra_gl_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_gl_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,11 +292,11 @@ extern_fn! {
|
|||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
|
||||
fn libra_gl_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_gl_filter_chain_t,
|
||||
chain: *const libra_gl_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
let value = chain.get_enabled_pass_count();
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
let value = chain.parameters().passes_enabled();
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
|
@ -298,6 +309,7 @@ extern_fn! {
|
|||
/// The resulting value in `chain` then becomes null.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
|
||||
/// - The context that the filter chain was initialized with **must be current** before freeing the filter chain.
|
||||
fn libra_gl_filter_chain_free(
|
||||
chain: *mut libra_gl_filter_chain_t
|
||||
) {
|
||||
|
|
|
@ -1,16 +1,52 @@
|
|||
//! librashader runtime C APIs.
|
||||
#[doc(cfg(feature = "runtime-opengl"))]
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
|
||||
#[cfg(feature = "runtime-opengl")]
|
||||
pub mod gl;
|
||||
|
||||
#[doc(cfg(feature = "runtime-vulkan"))]
|
||||
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
pub mod vk;
|
||||
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(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-d3d12")))]
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
|
||||
)]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d9")
|
||||
))]
|
||||
pub mod d3d9;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))
|
||||
)]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d12")
|
||||
))]
|
||||
pub mod d3d12;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "docsrs",
|
||||
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;
|
||||
|
|
345
librashader-capi/src/runtime/mtl/filter_chain.rs
Normal file
345
librashader-capi/src/runtime/mtl/filter_chain.rs
Normal file
|
@ -0,0 +1,345 @@
|
|||
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, 3 = 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.
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `command_buffer` is a `MTLCommandBuffer` handle to record draw commands to.
|
||||
/// The provided command buffer must be ready for encoding and contain no prior commands
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a `id<MTLTexture>` that will serve as the source image for the frame.
|
||||
/// - `out` is a `id<MTLTexture>` that is the render target of the frame.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## 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,
|
||||
output: PMTLTexture,
|
||||
viewport: *const libra_viewport_t,
|
||||
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 = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(output, mvp)?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output,
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
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
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: *const libra_mtl_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
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
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(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: *const libra_mtl_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
let value = chain.parameters().passes_enabled();
|
||||
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,48 +3,38 @@ 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::c_char;
|
||||
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 crate::LIBRASHADER_API_VERSION;
|
||||
use ash::vk;
|
||||
use ash::vk::Handle;
|
||||
|
||||
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
|
||||
pub use ash::vk::PFN_vkGetInstanceProcAddr;
|
||||
|
||||
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
|
||||
pub type libra_PFN_vkGetInstanceProcAddr =
|
||||
unsafe extern "system" fn(instance: *mut c_void, p_name: *const c_char);
|
||||
|
||||
/// Vulkan parameters for the source image.
|
||||
/// Vulkan parameters for an image.
|
||||
#[repr(C)]
|
||||
pub struct libra_source_image_vk_t {
|
||||
/// A raw `VkImage` handle to the source image.
|
||||
pub struct libra_image_vk_t {
|
||||
/// A raw `VkImage` handle.
|
||||
pub handle: vk::Image,
|
||||
/// The `VkFormat` of the source image.
|
||||
/// The `VkFormat` of the `VkImage`.
|
||||
pub format: vk::Format,
|
||||
/// The width of the source image.
|
||||
/// The width of the `VkImage`.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
/// The height of the `VkImage`.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// Vulkan parameters for the output image.
|
||||
#[repr(C)]
|
||||
pub struct libra_output_image_vk_t {
|
||||
/// A raw `VkImage` handle to the output image.
|
||||
pub handle: vk::Image,
|
||||
/// The `VkFormat` of the output image.
|
||||
pub format: vk::Format,
|
||||
}
|
||||
|
||||
/// Handles required to instantiate vulkan
|
||||
#[repr(C)]
|
||||
pub struct libra_device_vk_t {
|
||||
|
@ -57,12 +47,15 @@ pub struct libra_device_vk_t {
|
|||
/// A raw `VkDevice` handle
|
||||
/// for the device attached to the instance that will perform rendering.
|
||||
pub device: vk::Device,
|
||||
/// The queue to use, if this is `NULL`, then
|
||||
/// a suitable queue will be chosen. This must be a graphics queue.
|
||||
pub queue: vk::Queue,
|
||||
/// The entry loader for the Vulkan library.
|
||||
pub entry: vk::PFN_vkGetInstanceProcAddr,
|
||||
pub entry: Option<vk::PFN_vkGetInstanceProcAddr>,
|
||||
}
|
||||
|
||||
impl From<libra_source_image_vk_t> for VulkanImage {
|
||||
fn from(value: libra_source_image_vk_t) -> Self {
|
||||
impl From<libra_image_vk_t> for VulkanImage {
|
||||
fn from(value: libra_image_vk_t) -> Self {
|
||||
VulkanImage {
|
||||
size: Size::new(value.width, value.height),
|
||||
image: value.handle,
|
||||
|
@ -73,31 +66,45 @@ impl From<libra_source_image_vk_t> for VulkanImage {
|
|||
|
||||
impl From<libra_device_vk_t> for VulkanInstance {
|
||||
fn from(value: libra_device_vk_t) -> Self {
|
||||
let queue = if value.queue.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(value.queue)
|
||||
};
|
||||
|
||||
VulkanInstance {
|
||||
device: value.device,
|
||||
instance: value.instance,
|
||||
physical_device: value.physical_device,
|
||||
get_instance_proc_addr: value.entry,
|
||||
queue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, 3 = 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 +113,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 +141,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 +169,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 +187,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 +219,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())?;
|
||||
|
@ -228,13 +235,31 @@ extern_fn! {
|
|||
/// Records rendering commands for a frame with the given parameters for the given filter chain
|
||||
/// to the input command buffer.
|
||||
///
|
||||
/// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
|
||||
/// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
|
||||
///
|
||||
/// librashader **will not** create a pipeline barrier for the final pass. The output image will
|
||||
/// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition
|
||||
/// A pipeline barrier **will not** be created for the final pass. The output image must be
|
||||
/// in `VK_COLOR_ATTACHMENT_OPTIMAL`, and will remain so after all shader passes. The caller must transition
|
||||
/// the output image to the final layout.
|
||||
///
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `chain` is a handle to the filter chain.
|
||||
/// - `command_buffer` is a `VkCommandBuffer` handle to record draw commands to.
|
||||
/// The provided command buffer must be ready for recording and contain no prior commands
|
||||
/// - `frame_count` is the number of frames passed to the shader
|
||||
/// - `image` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
|
||||
/// to an image that will serve as the source image for the frame. The input image must be in
|
||||
/// the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
|
||||
/// - `out` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
|
||||
/// for the render target of the frame. The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
|
||||
/// The output image will remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes.
|
||||
///
|
||||
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
|
||||
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
|
||||
/// entire render target will be used.
|
||||
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
|
||||
/// be passed to the shader.
|
||||
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
|
||||
/// passed in. It may be null, in which case default options for the filter chain are used.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `libra_vk_filter_chain_frame` **must not be called within a RenderPass**.
|
||||
/// - `command_buffer` must be a valid handle to a `VkCommandBuffer` that is ready for recording.
|
||||
|
@ -246,13 +271,13 @@ 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,
|
||||
image: libra_source_image_vk_t,
|
||||
viewport: libra_viewport_t,
|
||||
out: libra_output_image_vk_t,
|
||||
image: libra_image_vk_t,
|
||||
out: libra_image_vk_t,
|
||||
viewport: *const libra_viewport_t,
|
||||
mvp: *const f32,
|
||||
opt: *const MaybeUninit<frame_vk_opt_t>
|
||||
) mut |chain| {
|
||||
|
@ -260,7 +285,7 @@ extern_fn! {
|
|||
let image: VulkanImage = image.into();
|
||||
let output = VulkanImage {
|
||||
image: out.handle,
|
||||
size: Size::new(viewport.width, viewport.height),
|
||||
size: Size::new(out.width, out.height),
|
||||
format: out.format
|
||||
};
|
||||
let mvp = if mvp.is_null() {
|
||||
|
@ -274,11 +299,21 @@ extern_fn! {
|
|||
Some(unsafe { opt.read() })
|
||||
};
|
||||
let opt = opt.map(FromUninit::from_uninit);
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output,
|
||||
mvp,
|
||||
|
||||
let viewport = if viewport.is_null() {
|
||||
Viewport::new_render_target_sized_origin(output, mvp)?
|
||||
} else {
|
||||
let viewport = unsafe { viewport.read() };
|
||||
Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output,
|
||||
size: Size {
|
||||
height: viewport.height,
|
||||
width: viewport.width
|
||||
},
|
||||
mvp,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
|
@ -298,15 +333,15 @@ extern_fn! {
|
|||
chain: *mut libra_vk_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
if chain.parameters().set_parameter_value(name, value).is_none() {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -320,18 +355,18 @@ extern_fn! {
|
|||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_vk_filter_chain_get_param(
|
||||
chain: *mut libra_vk_filter_chain_t,
|
||||
chain: *const libra_vk_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
) |chain| {
|
||||
assert_some_ptr!(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()
|
||||
let Some(value) = chain.parameters().parameter_value(name) else {
|
||||
return Err(LibrashaderError::UnknownShaderParameter(param_name))
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
|
@ -347,9 +382,9 @@ extern_fn! {
|
|||
fn libra_vk_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_vk_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
chain.parameters().set_passes_enabled(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,11 +394,11 @@ extern_fn! {
|
|||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
|
||||
fn libra_vk_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_vk_filter_chain_t,
|
||||
chain: *const libra_vk_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
let value = chain.get_enabled_pass_count();
|
||||
) |chain| {
|
||||
assert_some_ptr!(chain);
|
||||
let value = chain.parameters().passes_enabled();
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
/// API version type alias.
|
||||
pub type LIBRASHADER_API_VERSION = usize;
|
||||
/// ABI version type alias.
|
||||
pub type LIBRASHADER_ABI_VERSION = usize;
|
||||
|
||||
/// The current version of the librashader API.
|
||||
|
@ -13,7 +14,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.
|
||||
|
@ -23,10 +28,17 @@ pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
|
|||
/// ABI versions are not backwards compatible. It is not
|
||||
/// valid to load a librashader C API instance for any ABI
|
||||
/// version not equal to LIBRASHADER_CURRENT_ABI.
|
||||
///
|
||||
/// ## ABI Versions
|
||||
/// - ABI version 0: null instance (unloaded)
|
||||
/// - ABI version 1: 0.1.0
|
||||
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 1;
|
||||
/// - ABI version 2: 0.5.0
|
||||
/// - Reduced texture size information needed for some runtimes.
|
||||
/// - Removed wrapper structs for Direct3D 11 SRV and RTV handles.
|
||||
/// - Removed `gl_context_init`.
|
||||
/// - Make viewport handling consistent across runtimes, which are now
|
||||
/// span the output render target if omitted.
|
||||
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 2;
|
||||
|
||||
/// Function pointer definition for libra_abi_version
|
||||
pub type PFN_libra_instance_abi_version = extern "C" fn() -> LIBRASHADER_ABI_VERSION;
|
||||
|
|
265
librashader-capi/src/wildcard.rs
Normal file
265
librashader-capi/src/wildcard.rs
Normal file
|
@ -0,0 +1,265 @@
|
|||
//! 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
|
||||
/// - Metal
|
||||
/// - Direct3D9 (HLSL)
|
||||
///
|
||||
/// 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()))
|
||||
}
|
||||
}
|
91
librashader-cli/Cargo.toml
Normal file
91
librashader-cli/Cargo.toml
Normal file
|
@ -0,0 +1,91 @@
|
|||
[package]
|
||||
name = "librashader-cli"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
categories = ["emulators", "compilers", "graphics"]
|
||||
keywords = ["shader", "retroarch", "SPIR-V"]
|
||||
description = "RetroArch shaders for all."
|
||||
|
||||
[lib]
|
||||
name = "librashader_test"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "librashader-cli"
|
||||
path = "src/cli/main.rs"
|
||||
|
||||
[dependencies]
|
||||
librashader = { version = "0.5.1", path = "../librashader", features = [
|
||||
"presets",
|
||||
"preprocess",
|
||||
"serde",
|
||||
], default-features = false }
|
||||
librashader-runtime = { version = "0.5.1", path = "../librashader-runtime" }
|
||||
librashader-common = { version = "0.5.1", path = "../librashader-common" }
|
||||
wgpu = { version = "22", default-features = false, optional = true }
|
||||
wgpu-types = { version = "22", optional = true }
|
||||
anyhow = "1.0.86"
|
||||
image = { workspace = true }
|
||||
gfx-maths = "0.2.8"
|
||||
|
||||
pollster = "0.3.0"
|
||||
parking_lot = "0.12.3"
|
||||
image-compare = "0.4.1"
|
||||
gpu-allocator = "0.27.0"
|
||||
bitvec = "1.0.1"
|
||||
d3d12-descriptor-heap = { version = "0.2", optional = true }
|
||||
glow = { workspace = true, optional = true }
|
||||
glfw = { workspace = true, optional = true }
|
||||
ash = { workspace = true, optional = true }
|
||||
|
||||
clap = { workspace = true }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
spq-spvasm = "0.1.4"
|
||||
|
||||
rmp-serde = "1.3.0"
|
||||
|
||||
[features]
|
||||
default = ["full"]
|
||||
|
||||
full = ["vulkan", "opengl", "wgpu", "d3d9", "d3d11", "d3d12", "metal"]
|
||||
vulkan = ["librashader/runtime-vk", "dep:ash"]
|
||||
opengl = ["librashader/runtime-gl", "dep:glow", "dep:glfw"]
|
||||
wgpu = ["librashader/runtime-wgpu", "dep:wgpu", "dep:wgpu-types"]
|
||||
|
||||
d3d11 = ["librashader/runtime-d3d11", "dep:windows"]
|
||||
d3d12 = [
|
||||
"librashader/runtime-d3d12",
|
||||
"dep:windows",
|
||||
"dep:d3d12-descriptor-heap",
|
||||
]
|
||||
d3d9 = ["librashader/runtime-d3d9", "dep:windows"]
|
||||
|
||||
metal = ["librashader/runtime-metal", "dep:objc2", "dep:objc2-metal"]
|
||||
|
||||
vulkan-debug = ["vulkan"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
workspace = true
|
||||
optional = true
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Direct3D11",
|
||||
"Win32_Graphics_Direct3D_Fxc",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Security",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI",
|
||||
]
|
||||
|
||||
[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 }
|
804
librashader-cli/src/cli/main.rs
Normal file
804
librashader-cli/src/cli/main.rs
Normal file
|
@ -0,0 +1,804 @@
|
|||
use anyhow::anyhow;
|
||||
use clap::{Parser, Subcommand};
|
||||
use image::codecs::png::PngEncoder;
|
||||
use librashader::presets::context::ContextItem;
|
||||
use librashader::presets::{ShaderPreset, ShaderPresetPack, WildcardContext};
|
||||
use librashader::reflect::cross::{GlslVersion, HlslShaderModel, MslVersion, SpirvCross};
|
||||
use librashader::reflect::naga::{Naga, NagaLoweringOptions};
|
||||
use librashader::reflect::semantics::ShaderSemantics;
|
||||
use librashader::reflect::{CompileShader, FromCompilation, ReflectShader, SpirvCompilation};
|
||||
use librashader::runtime::Size;
|
||||
use librashader::{FastHashMap, ShortString};
|
||||
use librashader_common::StorageType;
|
||||
use librashader_runtime::parameters::RuntimeParameters;
|
||||
use librashader_test::render::{CommonFrameOptions, RenderTest};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Helpers and utilities to reflect and debug 'slang' shaders and presets.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
struct PresetArgs {
|
||||
/// The path to the shader preset to load.
|
||||
#[arg(short, long)]
|
||||
preset: PathBuf,
|
||||
/// Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR
|
||||
/// wildcards are always added to the preset parsing context.
|
||||
///
|
||||
/// For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
|
||||
#[arg(short, long, value_delimiter = ',', num_args = 1..)]
|
||||
wildcards: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
struct RenderArgs {
|
||||
/// The frame to render.
|
||||
///
|
||||
/// The renderer will run up to the number of frames specified here
|
||||
/// to ensure feedback and history.
|
||||
#[arg(short, long, default_value_t = 0)]
|
||||
frame: usize,
|
||||
/// The dimensions of the image.
|
||||
///
|
||||
/// This is given in either explicit dimensions `WIDTHxHEIGHT`, or a
|
||||
/// percentage of the input image in `SCALE%`.
|
||||
#[arg(short, long)]
|
||||
dimensions: Option<String>,
|
||||
/// Parameters to pass to the shader preset, comma separated with equals signs.
|
||||
///
|
||||
/// For example, crt_gamma=2.5,halation_weight=0.001
|
||||
#[arg(long, value_delimiter = ',', num_args = 1..)]
|
||||
params: Option<Vec<String>>,
|
||||
/// Set the number of passes enabled for the preset.
|
||||
#[arg(long)]
|
||||
passes_enabled: Option<usize>,
|
||||
/// The path to the input image.
|
||||
#[arg(short, long)]
|
||||
image: PathBuf,
|
||||
#[clap(flatten)]
|
||||
options: Option<FrameOptionsArgs>,
|
||||
}
|
||||
|
||||
impl From<FrameOptionsArgs> for CommonFrameOptions {
|
||||
fn from(value: FrameOptionsArgs) -> Self {
|
||||
Self {
|
||||
clear_history: false,
|
||||
frame_direction: value.frame_direction,
|
||||
rotation: value.rotation,
|
||||
total_subframes: value.total_subframes,
|
||||
current_subframe: value.current_subframe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
struct FrameOptionsArgs {
|
||||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
#[arg(long, default_value_t = 1, allow_hyphen_values = true)]
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
|
||||
#[arg(long, default_value_t = 0)]
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
#[arg(long, default_value_t = 1)]
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
#[arg(long, default_value_t = 1)]
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Render a shader preset against an image
|
||||
Render {
|
||||
#[clap(flatten)]
|
||||
preset: PresetArgs,
|
||||
#[clap(flatten)]
|
||||
render: RenderArgs,
|
||||
/// The path to the output image
|
||||
///
|
||||
/// If `-`, writes the image in PNG format to stdout.
|
||||
#[arg(short, long)]
|
||||
out: PathBuf,
|
||||
/// The runtime to use to render the shader preset.
|
||||
#[arg(value_enum, short, long)]
|
||||
runtime: Runtime,
|
||||
},
|
||||
/// Compare two runtimes and get a similarity score between the two
|
||||
/// runtimes rendering the same frame
|
||||
Compare {
|
||||
#[clap(flatten)]
|
||||
preset: PresetArgs,
|
||||
#[clap(flatten)]
|
||||
render: RenderArgs,
|
||||
/// The runtime to compare against
|
||||
#[arg(value_enum, short, long)]
|
||||
left: Runtime,
|
||||
/// The runtime to compare to
|
||||
#[arg(value_enum, short, long)]
|
||||
right: Runtime,
|
||||
/// The path to write the similarity image.
|
||||
///
|
||||
/// If `-`, writes the image to stdout.
|
||||
#[arg(short, long)]
|
||||
out: Option<PathBuf>,
|
||||
},
|
||||
/// Parse a preset and get a JSON representation of the data.
|
||||
Parse {
|
||||
#[clap(flatten)]
|
||||
preset: PresetArgs,
|
||||
},
|
||||
/// Create a serialized preset pack from a shader preset.
|
||||
Pack {
|
||||
#[clap(flatten)]
|
||||
preset: PresetArgs,
|
||||
/// The path to write the output
|
||||
///
|
||||
/// If `-`, writes the output to stdout
|
||||
#[arg(short, long)]
|
||||
out: PathBuf,
|
||||
/// The file format to output.
|
||||
#[arg(value_enum, short, long)]
|
||||
format: PackFormat,
|
||||
},
|
||||
/// Get the raw GLSL output of a preprocessed shader.
|
||||
Preprocess {
|
||||
/// The path to the slang shader.
|
||||
#[arg(short, long)]
|
||||
shader: PathBuf,
|
||||
/// The item to output.
|
||||
///
|
||||
/// `json` will print a JSON representation of the preprocessed shader.
|
||||
#[arg(value_enum, short, long)]
|
||||
output: PreprocessOutput,
|
||||
},
|
||||
/// Transpile a shader to the given format.
|
||||
Transpile {
|
||||
/// The path to the slang shader.
|
||||
#[arg(short, long)]
|
||||
shader: PathBuf,
|
||||
|
||||
/// The shader stage to output
|
||||
#[arg(value_enum, short = 'o', long)]
|
||||
stage: TranspileStage,
|
||||
|
||||
/// The output format.
|
||||
#[arg(value_enum, short, long)]
|
||||
format: TranspileFormat,
|
||||
|
||||
/// The version of the output format to parse as, if applicable
|
||||
///
|
||||
/// For GLSL, this should be an string corresponding to a GLSL version (e.g. '330', or '300es', or '300 es').
|
||||
///
|
||||
/// For HLSL, this is a shader model version as an integer (50), or a version in the format MAJ_MIN (5_0), or MAJ.MIN (5.0).
|
||||
///
|
||||
/// For MSL, this is the shader language version as an integer in format
|
||||
/// <MMmmpp>(30100), or a version in the format MAJ_MIN (3_1), or MAJ.MIN (3.1).
|
||||
///
|
||||
/// For SPIR-V, if this is the string "raw-id", then shows raw ID values instead of friendly names.
|
||||
#[arg(short, long)]
|
||||
version: Option<String>,
|
||||
},
|
||||
/// Reflect the shader relative to a preset, giving information about semantics used in a slang shader.
|
||||
Reflect {
|
||||
#[clap(flatten)]
|
||||
preset: PresetArgs,
|
||||
|
||||
/// The pass index to use.
|
||||
#[arg(short, long)]
|
||||
index: usize,
|
||||
|
||||
#[arg(value_enum, short, long, default_value = "cross")]
|
||||
backend: ReflectionBackend,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum PreprocessOutput {
|
||||
#[clap(name = "fragment")]
|
||||
Fragment,
|
||||
#[clap(name = "vertex")]
|
||||
Vertex,
|
||||
#[clap(name = "params")]
|
||||
Params,
|
||||
#[clap(name = "passformat")]
|
||||
Format,
|
||||
#[clap(name = "json")]
|
||||
Json,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum TranspileStage {
|
||||
#[clap(name = "fragment")]
|
||||
Fragment,
|
||||
#[clap(name = "vertex")]
|
||||
Vertex,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum TranspileFormat {
|
||||
#[clap(name = "glsl")]
|
||||
GLSL,
|
||||
#[clap(name = "hlsl")]
|
||||
HLSL,
|
||||
#[clap(name = "wgsl")]
|
||||
WGSL,
|
||||
#[clap(name = "msl")]
|
||||
MSL,
|
||||
#[clap(name = "spirv")]
|
||||
SPIRV,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum PackFormat {
|
||||
#[clap(name = "json")]
|
||||
JSON,
|
||||
#[clap(name = "msgpack")]
|
||||
MsgPack,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum Runtime {
|
||||
#[cfg(feature = "opengl")]
|
||||
#[clap(name = "opengl3")]
|
||||
OpenGL3,
|
||||
#[cfg(feature = "opengl")]
|
||||
#[clap(name = "opengl4")]
|
||||
OpenGL4,
|
||||
#[cfg(feature = "vulkan")]
|
||||
#[clap(name = "vulkan")]
|
||||
Vulkan,
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[clap(name = "wgpu")]
|
||||
Wgpu,
|
||||
#[cfg(all(windows, feature = "d3d9"))]
|
||||
#[clap(name = "d3d9")]
|
||||
Direct3D9,
|
||||
#[cfg(all(windows, feature = "d3d11"))]
|
||||
#[clap(name = "d3d11")]
|
||||
Direct3D11,
|
||||
#[cfg(all(windows, feature = "d3d12"))]
|
||||
#[clap(name = "d3d12")]
|
||||
Direct3D12,
|
||||
#[cfg(all(target_vendor = "apple", feature = "metal"))]
|
||||
#[clap(name = "metal")]
|
||||
Metal,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum ReflectionBackend {
|
||||
#[clap(name = "cross")]
|
||||
SpirvCross,
|
||||
#[clap(name = "naga")]
|
||||
Naga,
|
||||
}
|
||||
|
||||
macro_rules! get_runtime {
|
||||
($rt:ident, $image:ident) => {
|
||||
match $rt {
|
||||
#[cfg(feature = "opengl")]
|
||||
Runtime::OpenGL3 => &mut librashader_test::render::gl::OpenGl3::new($image.as_path())?,
|
||||
#[cfg(feature = "opengl")]
|
||||
Runtime::OpenGL4 => &mut librashader_test::render::gl::OpenGl4::new($image.as_path())?,
|
||||
#[cfg(feature = "vulkan")]
|
||||
Runtime::Vulkan => &mut librashader_test::render::vk::Vulkan::new($image.as_path())?,
|
||||
#[cfg(feature = "wgpu")]
|
||||
Runtime::Wgpu => &mut librashader_test::render::wgpu::Wgpu::new($image.as_path())?,
|
||||
#[cfg(all(windows, feature = "d3d9"))]
|
||||
Runtime::Direct3D9 => {
|
||||
&mut librashader_test::render::d3d9::Direct3D9::new($image.as_path())?
|
||||
}
|
||||
#[cfg(all(windows, feature = "d3d11"))]
|
||||
Runtime::Direct3D11 => {
|
||||
&mut librashader_test::render::d3d11::Direct3D11::new($image.as_path())?
|
||||
}
|
||||
#[cfg(all(windows, feature = "d3d12"))]
|
||||
Runtime::Direct3D12 => {
|
||||
&mut librashader_test::render::d3d12::Direct3D12::new($image.as_path())?
|
||||
}
|
||||
#[cfg(all(target_vendor = "apple", feature = "metal"))]
|
||||
Runtime::Metal => &mut librashader_test::render::mtl::Metal::new($image.as_path())?,
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn main() -> Result<(), anyhow::Error> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.command {
|
||||
Commands::Render {
|
||||
preset,
|
||||
render,
|
||||
out,
|
||||
runtime,
|
||||
} => {
|
||||
let PresetArgs { preset, wildcards } = preset;
|
||||
let RenderArgs {
|
||||
frame,
|
||||
dimensions,
|
||||
params,
|
||||
passes_enabled,
|
||||
image,
|
||||
options,
|
||||
} = render;
|
||||
|
||||
let test: &mut dyn RenderTest = get_runtime!(runtime, image);
|
||||
let dimensions = parse_dimension(dimensions, test.image_size())?;
|
||||
let preset = get_shader_preset(preset, wildcards)?;
|
||||
let params = parse_params(params)?;
|
||||
|
||||
let image = test.render_with_preset_and_params(
|
||||
preset,
|
||||
frame,
|
||||
Some(dimensions),
|
||||
Some(&|rp| set_params(rp, ¶ms, passes_enabled)),
|
||||
options.map(CommonFrameOptions::from),
|
||||
)?;
|
||||
|
||||
if out.as_path() == Path::new("-") {
|
||||
let out = std::io::stdout();
|
||||
image.write_with_encoder(PngEncoder::new(out))?;
|
||||
} else {
|
||||
image.save(out)?;
|
||||
}
|
||||
}
|
||||
Commands::Compare {
|
||||
preset,
|
||||
render,
|
||||
left,
|
||||
right,
|
||||
out,
|
||||
} => {
|
||||
let PresetArgs { preset, wildcards } = preset;
|
||||
let RenderArgs {
|
||||
frame,
|
||||
dimensions,
|
||||
params,
|
||||
passes_enabled,
|
||||
image,
|
||||
options,
|
||||
} = render;
|
||||
|
||||
let left: &mut dyn RenderTest = get_runtime!(left, image);
|
||||
let right: &mut dyn RenderTest = get_runtime!(right, image);
|
||||
|
||||
let dimensions = parse_dimension(dimensions, left.image_size())?;
|
||||
let params = parse_params(params)?;
|
||||
|
||||
let left_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
|
||||
let left_image = left.render_with_preset_and_params(
|
||||
left_preset,
|
||||
frame,
|
||||
Some(dimensions),
|
||||
Some(&|rp| set_params(rp, ¶ms, passes_enabled)),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let right_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
|
||||
let right_image = right.render_with_preset_and_params(
|
||||
right_preset,
|
||||
frame,
|
||||
Some(dimensions),
|
||||
Some(&|rp| set_params(rp, ¶ms, passes_enabled)),
|
||||
options.map(CommonFrameOptions::from),
|
||||
)?;
|
||||
|
||||
let similarity = image_compare::rgba_hybrid_compare(&left_image, &right_image)?;
|
||||
print!("{}", similarity.score);
|
||||
|
||||
if let Some(out) = out {
|
||||
let image = similarity.image.to_color_map();
|
||||
if out.as_path() == Path::new("-") {
|
||||
let out = std::io::stdout();
|
||||
image.write_with_encoder(PngEncoder::new(out))?;
|
||||
} else {
|
||||
image.save(out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Parse { preset } => {
|
||||
let PresetArgs { preset, wildcards } = preset;
|
||||
|
||||
let preset = get_shader_preset(preset, wildcards)?;
|
||||
let out = serde_json::to_string_pretty(&preset)?;
|
||||
print!("{out:}");
|
||||
}
|
||||
Commands::Preprocess { shader, output } => {
|
||||
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
|
||||
match output {
|
||||
PreprocessOutput::Fragment => print!("{}", source.fragment),
|
||||
PreprocessOutput::Vertex => print!("{}", source.vertex),
|
||||
PreprocessOutput::Params => {
|
||||
print!("{}", serde_json::to_string_pretty(&source.parameters)?)
|
||||
}
|
||||
PreprocessOutput::Format => print!("{:?}", source.format),
|
||||
PreprocessOutput::Json => print!("{}", serde_json::to_string_pretty(&source)?),
|
||||
}
|
||||
}
|
||||
Commands::Transpile {
|
||||
shader,
|
||||
stage,
|
||||
format,
|
||||
version,
|
||||
} => {
|
||||
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
|
||||
let compilation = SpirvCompilation::try_from(&source)?;
|
||||
let output = match format {
|
||||
TranspileFormat::GLSL => {
|
||||
let mut compilation =
|
||||
librashader::reflect::targets::GLSL::from_compilation(compilation)?;
|
||||
compilation.validate()?;
|
||||
|
||||
let version = version
|
||||
.map(|s| parse_glsl_version(&s))
|
||||
.unwrap_or(Ok(GlslVersion::Glsl330))?;
|
||||
|
||||
let output = compilation.compile(version)?;
|
||||
TranspileOutput {
|
||||
vertex: output.vertex,
|
||||
fragment: output.fragment,
|
||||
}
|
||||
}
|
||||
TranspileFormat::HLSL => {
|
||||
let mut compilation =
|
||||
librashader::reflect::targets::HLSL::from_compilation(compilation)?;
|
||||
compilation.validate()?;
|
||||
|
||||
let shader_model = version
|
||||
.map(|s| parse_hlsl_version(&s))
|
||||
.unwrap_or(Ok(HlslShaderModel::ShaderModel5_0))?;
|
||||
|
||||
let output = compilation.compile(Some(shader_model))?;
|
||||
TranspileOutput {
|
||||
vertex: output.vertex,
|
||||
fragment: output.fragment,
|
||||
}
|
||||
}
|
||||
TranspileFormat::WGSL => {
|
||||
let mut compilation =
|
||||
librashader::reflect::targets::WGSL::from_compilation(compilation)?;
|
||||
compilation.validate()?;
|
||||
let output = compilation.compile(NagaLoweringOptions {
|
||||
write_pcb_as_ubo: true,
|
||||
sampler_bind_group: 1,
|
||||
})?;
|
||||
TranspileOutput {
|
||||
vertex: output.vertex,
|
||||
fragment: output.fragment,
|
||||
}
|
||||
}
|
||||
TranspileFormat::MSL => {
|
||||
let mut compilation =
|
||||
<librashader::reflect::targets::MSL as FromCompilation<
|
||||
SpirvCompilation,
|
||||
SpirvCross,
|
||||
>>::from_compilation(compilation)?;
|
||||
compilation.validate()?;
|
||||
|
||||
let version = version
|
||||
.map(|s| parse_msl_version(&s))
|
||||
.unwrap_or(Ok(MslVersion::new(1, 2, 0)))?;
|
||||
|
||||
let output = compilation.compile(Some(version))?;
|
||||
|
||||
TranspileOutput {
|
||||
vertex: output.vertex,
|
||||
fragment: output.fragment,
|
||||
}
|
||||
}
|
||||
TranspileFormat::SPIRV => {
|
||||
let mut compilation =
|
||||
<librashader::reflect::targets::SPIRV as FromCompilation<
|
||||
SpirvCompilation,
|
||||
SpirvCross,
|
||||
>>::from_compilation(compilation)?;
|
||||
compilation.validate()?;
|
||||
let output = compilation.compile(None)?;
|
||||
|
||||
let raw = version.is_some_and(|s| s == "raw-id");
|
||||
TranspileOutput {
|
||||
vertex: spirv_to_dis(output.vertex, raw)?,
|
||||
fragment: spirv_to_dis(output.fragment, raw)?,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let print = match stage {
|
||||
TranspileStage::Fragment => output.fragment,
|
||||
TranspileStage::Vertex => output.vertex,
|
||||
};
|
||||
|
||||
print!("{print}")
|
||||
}
|
||||
Commands::Reflect {
|
||||
preset,
|
||||
index,
|
||||
backend,
|
||||
} => {
|
||||
let PresetArgs { preset, wildcards } = preset;
|
||||
|
||||
let preset = get_shader_preset(preset, wildcards)?;
|
||||
let Some(shader) = preset.passes.get(index) else {
|
||||
return Err(anyhow!("Invalid pass index for the preset"));
|
||||
};
|
||||
|
||||
let source = librashader::preprocess::ShaderSource::load(&shader.storage)?;
|
||||
let compilation = SpirvCompilation::try_from(&source)?;
|
||||
|
||||
let semantics =
|
||||
ShaderSemantics::create_pass_semantics::<anyhow::Error>(&preset, index)?;
|
||||
|
||||
let reflection = match backend {
|
||||
ReflectionBackend::SpirvCross => {
|
||||
let mut compilation =
|
||||
<librashader::reflect::targets::SPIRV as FromCompilation<
|
||||
SpirvCompilation,
|
||||
SpirvCross,
|
||||
>>::from_compilation(compilation)?;
|
||||
compilation.reflect(index, &semantics)?
|
||||
}
|
||||
ReflectionBackend::Naga => {
|
||||
let mut compilation =
|
||||
<librashader::reflect::targets::SPIRV as FromCompilation<
|
||||
SpirvCompilation,
|
||||
Naga,
|
||||
>>::from_compilation(compilation)?;
|
||||
compilation.reflect(index, &semantics)?
|
||||
}
|
||||
};
|
||||
|
||||
print!("{}", serde_json::to_string_pretty(&reflection)?);
|
||||
}
|
||||
Commands::Pack {
|
||||
preset,
|
||||
out,
|
||||
format,
|
||||
} => {
|
||||
let PresetArgs { preset, wildcards } = preset;
|
||||
let preset = get_shader_preset(preset, wildcards)?;
|
||||
let preset = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset)?;
|
||||
let output_bytes = match format {
|
||||
PackFormat::JSON => serde_json::to_vec_pretty(&preset)?,
|
||||
PackFormat::MsgPack => rmp_serde::to_vec(&preset)?,
|
||||
};
|
||||
|
||||
if out.as_path() == Path::new("-") {
|
||||
let mut out = std::io::stdout();
|
||||
out.write_all(output_bytes.as_slice())?;
|
||||
} else {
|
||||
let mut file = File::create(out.as_path())?;
|
||||
file.write_all(output_bytes.as_slice())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TranspileOutput {
|
||||
vertex: String,
|
||||
fragment: String,
|
||||
}
|
||||
|
||||
fn get_shader_preset(
|
||||
preset: PathBuf,
|
||||
wildcards: Option<Vec<String>>,
|
||||
) -> anyhow::Result<ShaderPreset> {
|
||||
let mut context = WildcardContext::new();
|
||||
context.add_path_defaults(preset.as_path());
|
||||
if let Some(wildcards) = wildcards {
|
||||
for string in wildcards {
|
||||
let Some((left, right)) = string.split_once("=") else {
|
||||
return Err(anyhow!("Encountered invalid context string {string}"));
|
||||
};
|
||||
|
||||
context.append_item(ContextItem::ExternContext(
|
||||
left.to_string(),
|
||||
right.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
let preset = ShaderPreset::try_parse_with_context(preset, context)?;
|
||||
Ok(preset)
|
||||
}
|
||||
|
||||
fn parse_params(
|
||||
assignments: Option<Vec<String>>,
|
||||
) -> anyhow::Result<Option<FastHashMap<ShortString, f32>>> {
|
||||
let Some(assignments) = assignments else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut map = FastHashMap::default();
|
||||
for string in assignments {
|
||||
let Some((left, right)) = string.split_once("=") else {
|
||||
return Err(anyhow!("Encountered invalid parameter string {string}"));
|
||||
};
|
||||
|
||||
let value = right
|
||||
.parse::<f32>()
|
||||
.map_err(|_| anyhow!("Encountered invalid parameter value: {right}"))?;
|
||||
|
||||
map.insert(ShortString::from(left), value);
|
||||
}
|
||||
|
||||
Ok(Some(map))
|
||||
}
|
||||
|
||||
fn set_params(
|
||||
params: &RuntimeParameters,
|
||||
assignments: &Option<FastHashMap<ShortString, f32>>,
|
||||
passes_enabled: Option<usize>,
|
||||
) {
|
||||
if let Some(passes_enabled) = passes_enabled {
|
||||
params.set_passes_enabled(passes_enabled)
|
||||
};
|
||||
|
||||
let Some(assignments) = assignments else {
|
||||
return;
|
||||
};
|
||||
|
||||
params.update_parameters(|params| {
|
||||
for (key, param) in assignments {
|
||||
params.insert(key.clone(), *param);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spirv_to_dis(spirv: Vec<u32>, raw: bool) -> anyhow::Result<String> {
|
||||
let binary = spq_spvasm::SpirvBinary::from(spirv);
|
||||
spq_spvasm::Disassembler::new()
|
||||
.print_header(true)
|
||||
.name_ids(!raw)
|
||||
.name_type_ids(!raw)
|
||||
.name_const_ids(!raw)
|
||||
.indent(true)
|
||||
.disassemble(&binary)
|
||||
}
|
||||
|
||||
fn parse_glsl_version(version_str: &str) -> anyhow::Result<GlslVersion> {
|
||||
if version_str.contains("es") {
|
||||
let Some(version) = version_str.strip_suffix("es").map(|s| s.trim()) else {
|
||||
return Err(anyhow!("Unknown GLSL version"));
|
||||
};
|
||||
|
||||
Ok(match version {
|
||||
"100" => GlslVersion::Glsl100Es,
|
||||
"300" => GlslVersion::Glsl300Es,
|
||||
"310" => GlslVersion::Glsl310Es,
|
||||
"320" => GlslVersion::Glsl320Es,
|
||||
_ => return Err(anyhow!("Unknown GLSL version")),
|
||||
})
|
||||
} else {
|
||||
Ok(match version_str {
|
||||
"100" => GlslVersion::Glsl100Es,
|
||||
"110" => GlslVersion::Glsl110,
|
||||
"120" => GlslVersion::Glsl120,
|
||||
"130" => GlslVersion::Glsl130,
|
||||
"140" => GlslVersion::Glsl140,
|
||||
"150" => GlslVersion::Glsl150,
|
||||
"300" => GlslVersion::Glsl300Es,
|
||||
"330" => GlslVersion::Glsl330,
|
||||
"310" => GlslVersion::Glsl310Es,
|
||||
"320" => GlslVersion::Glsl320Es,
|
||||
"400" => GlslVersion::Glsl400,
|
||||
"410" => GlslVersion::Glsl410,
|
||||
"420" => GlslVersion::Glsl420,
|
||||
"430" => GlslVersion::Glsl430,
|
||||
"440" => GlslVersion::Glsl440,
|
||||
"450" => GlslVersion::Glsl450,
|
||||
"460" => GlslVersion::Glsl460,
|
||||
_ => return Err(anyhow!("Unknown GLSL version")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn version_to_usize(version_str: &str) -> anyhow::Result<usize> {
|
||||
let version: &str = if version_str.contains("_") {
|
||||
&version_str.replace("_", "")
|
||||
} else if version_str.contains(".") {
|
||||
&version_str.replace(".", "")
|
||||
} else {
|
||||
version_str
|
||||
};
|
||||
|
||||
let version = version
|
||||
.parse::<usize>()
|
||||
.map_err(|_| anyhow!("Invalid version string"))?;
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
fn parse_hlsl_version(version_str: &str) -> anyhow::Result<HlslShaderModel> {
|
||||
let version = version_to_usize(version_str)?;
|
||||
Ok(match version {
|
||||
30 => HlslShaderModel::ShaderModel3_0,
|
||||
40 => HlslShaderModel::ShaderModel4_0,
|
||||
50 => HlslShaderModel::ShaderModel5_0,
|
||||
51 => HlslShaderModel::ShaderModel5_1,
|
||||
60 => HlslShaderModel::ShaderModel6_0,
|
||||
61 => HlslShaderModel::ShaderModel6_1,
|
||||
62 => HlslShaderModel::ShaderModel6_2,
|
||||
63 => HlslShaderModel::ShaderModel6_3,
|
||||
64 => HlslShaderModel::ShaderModel6_4,
|
||||
65 => HlslShaderModel::ShaderModel6_5,
|
||||
66 => HlslShaderModel::ShaderModel6_6,
|
||||
67 => HlslShaderModel::ShaderModel6_7,
|
||||
68 => HlslShaderModel::ShaderModel6_8,
|
||||
_ => return Err(anyhow!("Unknown Shader Model")),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_msl_version(version_str: &str) -> anyhow::Result<MslVersion> {
|
||||
let version = version_to_usize(version_str)?;
|
||||
Ok(match version {
|
||||
10 => MslVersion::new(1, 0, 0),
|
||||
11 => MslVersion::new(1, 1, 0),
|
||||
12 => MslVersion::new(1, 2, 0),
|
||||
20 => MslVersion::new(2, 0, 0),
|
||||
21 => MslVersion::new(2, 1, 0),
|
||||
22 => MslVersion::new(2, 2, 0),
|
||||
23 => MslVersion::new(2, 3, 0),
|
||||
24 => MslVersion::new(2, 4, 0),
|
||||
30 => MslVersion::new(3, 0, 0),
|
||||
31 => MslVersion::new(3, 1, 0),
|
||||
32 => MslVersion::new(3, 2, 0),
|
||||
n if n >= 10000 => {
|
||||
let major = n / 10000;
|
||||
let minor = (n - (major * 10000)) / 100;
|
||||
let patch = n - ((major * 10000) + (minor * 100));
|
||||
MslVersion::new(major as u32, minor as u32, patch as u32)
|
||||
}
|
||||
_ => return Err(anyhow!("Unknown MSL version")),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_dimension(dimstr: Option<String>, image_dim: Size<u32>) -> anyhow::Result<Size<u32>> {
|
||||
let Some(dimstr) = dimstr else {
|
||||
return Ok(image_dim);
|
||||
};
|
||||
|
||||
if dimstr.contains("x") {
|
||||
if let Some((Ok(width), Ok(height))) = dimstr
|
||||
.split_once("x")
|
||||
.map(|(width, height)| (width.parse::<u32>(), height.parse::<u32>()))
|
||||
{
|
||||
if width < 1 || height < 1 {
|
||||
return Err(anyhow!("Dimensions must be larger than 1x1"));
|
||||
}
|
||||
|
||||
if width > 16384 || height > 16384 {
|
||||
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
|
||||
}
|
||||
return Ok(Size::new(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
if dimstr.ends_with("%") && dimstr.len() > 1 {
|
||||
if let Ok(percent) = dimstr.trim_end_matches("%").parse::<u32>() {
|
||||
let percent = percent as f32 / 100f32;
|
||||
let width = (image_dim.width as f32 * percent) as u32;
|
||||
let height = (image_dim.height as f32 * percent) as u32;
|
||||
|
||||
if width < 1 || height < 1 {
|
||||
return Err(anyhow!("Dimensions must be larger than 1x1"));
|
||||
}
|
||||
|
||||
if width > 16384 || height > 16384 {
|
||||
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
|
||||
}
|
||||
|
||||
return Ok(Size { width, height });
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"Invalid dimension syntax, must either in form WIDTHxHEIGHT or SCALE%"
|
||||
))
|
||||
}
|
2
librashader-cli/src/lib.rs
Normal file
2
librashader-cli/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
/// Render tests
|
||||
pub mod render;
|
258
librashader-cli/src/render/d3d11.rs
Normal file
258
librashader-cli/src/render/d3d11.rs
Normal file
|
@ -0,0 +1,258 @@
|
|||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use image::RgbaImage;
|
||||
use librashader::runtime::d3d11::*;
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use std::io::{Cursor, Write};
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
|
||||
impl RenderTest for Direct3D11 {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Direct3D11::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image_bytes.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let output_size = output_size.unwrap_or(self.image_bytes.size);
|
||||
let (renderbuffer, rtv) = self.create_renderbuffer(output_size)?;
|
||||
|
||||
unsafe {
|
||||
let mut filter_chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
&self.device,
|
||||
Some(&FilterChainOptions {
|
||||
force_no_mipmaps: false,
|
||||
disable_cache: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
let viewport = Viewport::new_render_target_sized_origin(&rtv, None)?;
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
filter_chain.frame(None, &self.image_srv, &viewport, frame, options.as_ref())?;
|
||||
}
|
||||
|
||||
let mut renderbuffer_desc = Default::default();
|
||||
self.immediate_context.Flush();
|
||||
renderbuffer.GetDesc(&mut renderbuffer_desc);
|
||||
|
||||
eprintln!("{:?}", renderbuffer_desc);
|
||||
let mut staging = None;
|
||||
self.device.CreateTexture2D(
|
||||
&D3D11_TEXTURE2D_DESC {
|
||||
MipLevels: 1,
|
||||
BindFlags: 0,
|
||||
MiscFlags: 0,
|
||||
Usage: D3D11_USAGE_STAGING,
|
||||
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
|
||||
..renderbuffer_desc
|
||||
},
|
||||
None,
|
||||
Some(&mut staging),
|
||||
)?;
|
||||
|
||||
let staging = staging.ok_or(anyhow!("Unable to create staging texture"))?;
|
||||
|
||||
self.immediate_context.CopyResource(&staging, &renderbuffer);
|
||||
|
||||
let mut pixels: Vec<u8> = Vec::new();
|
||||
|
||||
let mut map_info = Default::default();
|
||||
self.immediate_context
|
||||
.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut map_info))?;
|
||||
|
||||
let slice = std::slice::from_raw_parts(
|
||||
map_info.pData as *const u8,
|
||||
(renderbuffer_desc.Height * map_info.RowPitch) as usize,
|
||||
);
|
||||
pixels.resize(
|
||||
(renderbuffer_desc.Height * renderbuffer_desc.Width * 4) as usize,
|
||||
0,
|
||||
);
|
||||
|
||||
let mut cursor = Cursor::new(pixels.deref_mut());
|
||||
for chunk in slice.chunks(map_info.RowPitch as usize) {
|
||||
cursor.write_all(&chunk[..(renderbuffer_desc.Width * 4) as usize])?
|
||||
}
|
||||
|
||||
let image = RgbaImage::from_raw(output_size.width, output_size.height, pixels)
|
||||
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||
self.immediate_context.Unmap(&staging, 0);
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader_runtime::image::{Image, UVDirection};
|
||||
use windows::{
|
||||
Win32::Foundation::*, Win32::Graphics::Direct3D::*, Win32::Graphics::Direct3D11::*,
|
||||
Win32::Graphics::Dxgi::Common::*, Win32::Graphics::Dxgi::*,
|
||||
};
|
||||
|
||||
pub struct Direct3D11 {
|
||||
device: ID3D11Device,
|
||||
immediate_context: ID3D11DeviceContext,
|
||||
_image_tex: ID3D11Texture2D,
|
||||
image_srv: ID3D11ShaderResourceView,
|
||||
image_bytes: Image,
|
||||
}
|
||||
|
||||
impl Direct3D11 {
|
||||
fn create_device() -> anyhow::Result<(IDXGIFactory4, ID3D11Device, ID3D11DeviceContext)> {
|
||||
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }?;
|
||||
let feature_levels = vec![D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1];
|
||||
|
||||
let mut out_device = None;
|
||||
let mut out_context = None;
|
||||
let mut _out_feature_level = D3D_FEATURE_LEVEL_11_0;
|
||||
|
||||
unsafe {
|
||||
D3D11CreateDevice(
|
||||
None,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
HMODULE::default(),
|
||||
D3D11_CREATE_DEVICE_DEBUG,
|
||||
Some(&feature_levels),
|
||||
D3D11_SDK_VERSION,
|
||||
Some(&mut out_device),
|
||||
Some(&mut _out_feature_level),
|
||||
Some(&mut out_context),
|
||||
)
|
||||
}?;
|
||||
Ok((dxgi_factory, out_device.unwrap(), out_context.unwrap()))
|
||||
}
|
||||
|
||||
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
|
||||
let (_factory, device, imm_context) = Self::create_device()?;
|
||||
|
||||
let (image, image_tex, srv) = Self::load_image(&device, image_path)?;
|
||||
Ok(Self {
|
||||
device,
|
||||
immediate_context: imm_context,
|
||||
image_bytes: image,
|
||||
_image_tex: image_tex,
|
||||
image_srv: srv,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_image(
|
||||
device: &ID3D11Device,
|
||||
image_path: &Path,
|
||||
) -> anyhow::Result<(Image, ID3D11Texture2D, ID3D11ShaderResourceView)> {
|
||||
let image = Image::load(image_path, UVDirection::TopLeft)?;
|
||||
let desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: image.size.width,
|
||||
Height: image.size.height,
|
||||
// todo: set this to 0
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
CPUAccessFlags: 0,
|
||||
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
Usage: D3D11_USAGE_DEFAULT,
|
||||
BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut resource = None;
|
||||
device.CreateTexture2D(
|
||||
&desc,
|
||||
Some(&D3D11_SUBRESOURCE_DATA {
|
||||
pSysMem: image.bytes.as_ptr().cast(),
|
||||
SysMemPitch: image.pitch as u32,
|
||||
SysMemSlicePitch: 0,
|
||||
}),
|
||||
Some(&mut resource),
|
||||
)?;
|
||||
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
|
||||
|
||||
let mut srv = None;
|
||||
device.CreateShaderResourceView(
|
||||
&resource,
|
||||
Some(&D3D11_SHADER_RESOURCE_VIEW_DESC {
|
||||
Format: desc.Format,
|
||||
ViewDimension: D3D_SRV_DIMENSION_TEXTURE2D,
|
||||
Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 {
|
||||
Texture2D: D3D11_TEX2D_SRV {
|
||||
MostDetailedMip: 0,
|
||||
MipLevels: u32::MAX,
|
||||
},
|
||||
},
|
||||
}),
|
||||
Some(&mut srv),
|
||||
)?;
|
||||
|
||||
let srv = srv.ok_or(anyhow!("Failed to create SRV"))?;
|
||||
|
||||
Ok((image, resource, srv))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_renderbuffer(
|
||||
&self,
|
||||
size: Size<u32>,
|
||||
) -> anyhow::Result<(ID3D11Texture2D, ID3D11RenderTargetView)> {
|
||||
let desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: size.width,
|
||||
Height: size.height,
|
||||
// todo: set this to 0
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
CPUAccessFlags: 0,
|
||||
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
Usage: D3D11_USAGE_DEFAULT,
|
||||
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut resource = None;
|
||||
self.device
|
||||
.CreateTexture2D(&desc, None, Some(&mut resource))?;
|
||||
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
|
||||
|
||||
let mut rtv = None;
|
||||
self.device
|
||||
.CreateRenderTargetView(&resource, None, Some(&mut rtv))?;
|
||||
|
||||
let rtv = rtv.ok_or(anyhow!("Failed to create RTV"))?;
|
||||
|
||||
Ok((resource, rtv))
|
||||
}
|
||||
}
|
||||
}
|
35
librashader-cli/src/render/d3d12/descriptor_heap.rs
Normal file
35
librashader-cli/src/render/d3d12/descriptor_heap.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use d3d12_descriptor_heap::D3D12DescriptorHeapType;
|
||||
use windows::Win32::Graphics::Direct3D12::{
|
||||
D3D12_DESCRIPTOR_HEAP_DESC, D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CpuStagingHeap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderTargetHeap;
|
||||
|
||||
impl D3D12DescriptorHeapType for RenderTargetHeap {
|
||||
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
|
||||
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
|
||||
D3D12_DESCRIPTOR_HEAP_DESC {
|
||||
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
|
||||
NumDescriptors: size as u32,
|
||||
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||
NodeMask: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl D3D12DescriptorHeapType for CpuStagingHeap {
|
||||
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
|
||||
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
|
||||
D3D12_DESCRIPTOR_HEAP_DESC {
|
||||
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
|
||||
NumDescriptors: size as u32,
|
||||
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||
NodeMask: 0,
|
||||
}
|
||||
}
|
||||
}
|
426
librashader-cli/src/render/d3d12/mod.rs
Normal file
426
librashader-cli/src/render/d3d12/mod.rs
Normal file
|
@ -0,0 +1,426 @@
|
|||
mod descriptor_heap;
|
||||
mod util;
|
||||
|
||||
use crate::render::d3d12::descriptor_heap::{CpuStagingHeap, RenderTargetHeap};
|
||||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use d3d12_descriptor_heap::{D3D12DescriptorHeap, D3D12DescriptorHeapSlot};
|
||||
use image::RgbaImage;
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::d3d12::{D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions};
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
|
||||
use std::path::Path;
|
||||
use windows::core::Interface;
|
||||
use windows::Win32::Foundation::CloseHandle;
|
||||
use windows::Win32::Graphics::Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_12_1};
|
||||
use windows::Win32::Graphics::Direct3D12::{
|
||||
D3D12CreateDevice, ID3D12CommandAllocator, ID3D12CommandQueue, ID3D12Device, ID3D12Fence,
|
||||
ID3D12GraphicsCommandList, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||
D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||
D3D12_FENCE_FLAG_NONE, D3D12_HEAP_FLAG_NONE, D3D12_HEAP_PROPERTIES, D3D12_HEAP_TYPE_CUSTOM,
|
||||
D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD, D3D12_MEMORY_POOL_L0,
|
||||
D3D12_MEMORY_POOL_UNKNOWN, D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_DESC,
|
||||
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC, D3D12_SHADER_RESOURCE_VIEW_DESC_0,
|
||||
D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA, D3D12_TEX2D_SRV,
|
||||
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
||||
};
|
||||
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
|
||||
use windows::Win32::Graphics::Dxgi::{
|
||||
CreateDXGIFactory2, IDXGIAdapter1, IDXGIFactory4, DXGI_ADAPTER_FLAG_NONE,
|
||||
DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_DEBUG,
|
||||
};
|
||||
use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject, INFINITE};
|
||||
|
||||
pub struct Direct3D12 {
|
||||
device: ID3D12Device,
|
||||
_cpu_heap: D3D12DescriptorHeap<CpuStagingHeap>,
|
||||
rtv_heap: D3D12DescriptorHeap<RenderTargetHeap>,
|
||||
|
||||
texture: ID3D12Resource,
|
||||
_heap_slot: D3D12DescriptorHeapSlot<CpuStagingHeap>,
|
||||
command_pool: ID3D12CommandAllocator,
|
||||
queue: ID3D12CommandQueue,
|
||||
image: Image<BGRA8>,
|
||||
}
|
||||
|
||||
impl RenderTest for Direct3D12 {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Direct3D12::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image.size
|
||||
}
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
unsafe {
|
||||
let descriptor = self.rtv_heap.allocate_descriptor()?;
|
||||
|
||||
let cmd: ID3D12GraphicsCommandList = self.device.CreateCommandList(
|
||||
0,
|
||||
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
&self.command_pool,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let fence_event = CreateEventA(None, false, false, None)?;
|
||||
let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
|
||||
|
||||
let mut filter_chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
&self.device,
|
||||
Some(&FilterChainOptions {
|
||||
force_hlsl_pipeline: false,
|
||||
force_no_mipmaps: false,
|
||||
disable_cache: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
let output_size = output_size.unwrap_or(self.image.size);
|
||||
let mut output_texture = None;
|
||||
let desc = D3D12_RESOURCE_DESC {
|
||||
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||
Alignment: 0,
|
||||
Width: output_size.width as u64,
|
||||
Height: output_size.height,
|
||||
DepthOrArraySize: 1,
|
||||
MipLevels: 1,
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Layout: Default::default(),
|
||||
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
|
||||
};
|
||||
|
||||
// We only need to draw one frame, so don't worry about the performance impact
|
||||
// of this heap.
|
||||
self.device.CreateCommittedResource(
|
||||
&D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_CUSTOM,
|
||||
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
|
||||
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
|
||||
CreationNodeMask: 1,
|
||||
VisibleNodeMask: 1,
|
||||
},
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&desc,
|
||||
D3D12_RESOURCE_STATE_COMMON | D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
None,
|
||||
&mut output_texture,
|
||||
)?;
|
||||
|
||||
let output_texture: ID3D12Resource =
|
||||
output_texture.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
|
||||
|
||||
self.device
|
||||
.CreateRenderTargetView(&output_texture, None, *descriptor.as_ref());
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(
|
||||
D3D12OutputView::new_from_raw(
|
||||
*descriptor.as_ref(),
|
||||
output_size,
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
let image = self.texture.to_ref();
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
filter_chain.frame(&cmd, image.into(), &viewport, frame, options.as_ref())?;
|
||||
}
|
||||
|
||||
cmd.Close()?;
|
||||
self.queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
|
||||
self.queue.Signal(&fence, 1)?;
|
||||
|
||||
if fence.GetCompletedValue() < 1 {
|
||||
fence.SetEventOnCompletion(1, fence_event)?;
|
||||
WaitForSingleObject(fence_event, INFINITE);
|
||||
CloseHandle(fence_event)?;
|
||||
};
|
||||
|
||||
let mut buffer = vec![0u8; (output_size.height * output_size.width) as usize * 4];
|
||||
|
||||
output_texture.ReadFromSubresource(
|
||||
buffer.as_mut_ptr().cast(),
|
||||
4 * output_size.width,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
|
||||
BGRA8::convert(&mut buffer);
|
||||
|
||||
let image =
|
||||
RgbaImage::from_raw(output_size.width, output_size.height, Vec::from(buffer))
|
||||
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Direct3D12 {
|
||||
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
|
||||
let device = Self::create_device()?;
|
||||
let mut heap = unsafe { D3D12DescriptorHeap::new(&device, 8)? };
|
||||
let rtv_heap = unsafe { D3D12DescriptorHeap::new(&device, 16)? };
|
||||
|
||||
unsafe {
|
||||
let command_pool: ID3D12CommandAllocator =
|
||||
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?;
|
||||
|
||||
let queue: ID3D12CommandQueue =
|
||||
device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC {
|
||||
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
Priority: 0,
|
||||
Flags: D3D12_COMMAND_QUEUE_FLAG_NONE,
|
||||
NodeMask: 0,
|
||||
})?;
|
||||
let (image, texture, heap_slot) =
|
||||
Self::load_image(&device, &command_pool, &queue, &mut heap, image_path)?;
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
_cpu_heap: heap,
|
||||
rtv_heap,
|
||||
texture,
|
||||
_heap_slot: heap_slot,
|
||||
command_pool,
|
||||
image,
|
||||
queue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn create_device() -> anyhow::Result<ID3D12Device> {
|
||||
let dxgi_factory_flags = DXGI_CREATE_FACTORY_DEBUG;
|
||||
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(dxgi_factory_flags) }?;
|
||||
|
||||
let adapter = Self::get_hardware_adapter(&dxgi_factory)?;
|
||||
|
||||
let mut device: Option<ID3D12Device> = None;
|
||||
unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_12_1, &mut device) }?;
|
||||
let device = device.ok_or(anyhow!("Failed to initialize D3D12 device"))?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
fn load_image(
|
||||
device: &ID3D12Device,
|
||||
command_pool: &ID3D12CommandAllocator,
|
||||
queue: &ID3D12CommandQueue,
|
||||
heap: &mut D3D12DescriptorHeap<CpuStagingHeap>,
|
||||
path: &Path,
|
||||
) -> anyhow::Result<(
|
||||
Image<BGRA8>,
|
||||
ID3D12Resource,
|
||||
D3D12DescriptorHeapSlot<CpuStagingHeap>,
|
||||
)> {
|
||||
// 1 time queue infrastructure for lut uploads
|
||||
let image: Image<BGRA8> = Image::load(path, UVDirection::TopLeft)?;
|
||||
|
||||
let desc = D3D12_RESOURCE_DESC {
|
||||
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||
Alignment: 0,
|
||||
Width: image.size.width as u64,
|
||||
Height: image.size.height,
|
||||
DepthOrArraySize: 1,
|
||||
MipLevels: 1,
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Layout: Default::default(),
|
||||
Flags: Default::default(),
|
||||
};
|
||||
|
||||
let descriptor = heap.allocate_descriptor()?;
|
||||
|
||||
// create handles on GPU
|
||||
let mut resource: Option<ID3D12Resource> = None;
|
||||
unsafe {
|
||||
let cmd: ID3D12GraphicsCommandList = device.CreateCommandList(
|
||||
0,
|
||||
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
&command_pool.clone(),
|
||||
None,
|
||||
)?;
|
||||
let fence_event = CreateEventA(None, false, false, None)?;
|
||||
let fence: ID3D12Fence = device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
|
||||
|
||||
device.CreateCommittedResource(
|
||||
&D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_DEFAULT,
|
||||
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
|
||||
CreationNodeMask: 1,
|
||||
VisibleNodeMask: 1,
|
||||
},
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&desc,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
None,
|
||||
&mut resource,
|
||||
)?;
|
||||
|
||||
let resource = resource.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
|
||||
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
|
||||
Format: desc.Format,
|
||||
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
|
||||
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
|
||||
Texture2D: D3D12_TEX2D_SRV {
|
||||
MipLevels: u32::MAX,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
device.CreateShaderResourceView(&resource, Some(&srv_desc), *descriptor.as_ref());
|
||||
|
||||
let mut buffer_desc = D3D12_RESOURCE_DESC {
|
||||
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
|
||||
let mut total = 0;
|
||||
// texture upload
|
||||
device.GetCopyableFootprints(
|
||||
&desc,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
Some(&mut layout),
|
||||
None,
|
||||
None,
|
||||
Some(&mut total),
|
||||
);
|
||||
|
||||
buffer_desc.Width = total;
|
||||
buffer_desc.Height = 1;
|
||||
buffer_desc.DepthOrArraySize = 1;
|
||||
buffer_desc.MipLevels = 1;
|
||||
buffer_desc.SampleDesc.Count = 1;
|
||||
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
|
||||
let mut upload: Option<ID3D12Resource> = None;
|
||||
|
||||
device.CreateCommittedResource(
|
||||
&D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_UPLOAD,
|
||||
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
|
||||
CreationNodeMask: 1,
|
||||
VisibleNodeMask: 1,
|
||||
},
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&buffer_desc,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
None,
|
||||
&mut upload,
|
||||
)?;
|
||||
|
||||
let upload = upload.ok_or_else(|| anyhow!("Failed to allocate copy texture"))?;
|
||||
|
||||
let subresource = [D3D12_SUBRESOURCE_DATA {
|
||||
pData: image.bytes.as_ptr().cast(),
|
||||
RowPitch: 4 * image.size.width as isize,
|
||||
SlicePitch: (4 * image.size.width * image.size.height) as isize,
|
||||
}];
|
||||
|
||||
util::d3d12_resource_transition(
|
||||
&cmd,
|
||||
&resource,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||
);
|
||||
|
||||
util::d3d12_update_subresources(&cmd, &resource, &upload, 0, 0, 1, &subresource)?;
|
||||
|
||||
util::d3d12_resource_transition(
|
||||
&cmd,
|
||||
&resource,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
);
|
||||
|
||||
cmd.Close()?;
|
||||
queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
|
||||
queue.Signal(&fence, 1)?;
|
||||
|
||||
if fence.GetCompletedValue() < 1 {
|
||||
fence.SetEventOnCompletion(1, fence_event)?;
|
||||
WaitForSingleObject(fence_event, INFINITE);
|
||||
CloseHandle(fence_event)?;
|
||||
}
|
||||
|
||||
Ok((image, resource, descriptor))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hardware_adapter(factory: &IDXGIFactory4) -> windows::core::Result<IDXGIAdapter1> {
|
||||
for i in 0.. {
|
||||
let adapter = unsafe { factory.EnumAdapters1(i)? };
|
||||
|
||||
let desc = unsafe { adapter.GetDesc1()? };
|
||||
|
||||
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != DXGI_ADAPTER_FLAG_NONE.0 as u32
|
||||
{
|
||||
// Don't select the Basic Render Driver adapter. If you want a
|
||||
// software adapter, pass in "/warp" on the command line.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see whether the adapter supports Direct3D 12, but don't
|
||||
// create the actual device yet.
|
||||
if unsafe {
|
||||
D3D12CreateDevice(
|
||||
&adapter,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
std::ptr::null_mut::<Option<ID3D12Device>>(),
|
||||
)
|
||||
}
|
||||
.is_ok()
|
||||
{
|
||||
return Ok(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to warp
|
||||
unsafe { factory.EnumWarpAdapter() }
|
||||
}
|
||||
}
|
213
librashader-cli/src/render/d3d12/util.rs
Normal file
213
librashader-cli/src/render/d3d12/util.rs
Normal file
|
@ -0,0 +1,213 @@
|
|||
use anyhow::anyhow;
|
||||
use std::mem::ManuallyDrop;
|
||||
use windows::Win32::Graphics::Direct3D12::{
|
||||
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_MEMCPY_DEST,
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0,
|
||||
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_BARRIER_FLAG_NONE,
|
||||
D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_STATES,
|
||||
D3D12_RESOURCE_TRANSITION_BARRIER, D3D12_SUBRESOURCE_DATA, D3D12_TEXTURE_COPY_LOCATION,
|
||||
D3D12_TEXTURE_COPY_LOCATION_0, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
||||
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||
};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn d3d12_resource_transition(
|
||||
cmd: &ID3D12GraphicsCommandList,
|
||||
resource: &ID3D12Resource,
|
||||
before: D3D12_RESOURCE_STATES,
|
||||
after: D3D12_RESOURCE_STATES,
|
||||
) {
|
||||
d3d12_resource_transition_subresource(
|
||||
cmd,
|
||||
resource,
|
||||
before,
|
||||
after,
|
||||
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn d3d12_get_resource_transition_subresource(
|
||||
resource: &ID3D12Resource,
|
||||
before: D3D12_RESOURCE_STATES,
|
||||
after: D3D12_RESOURCE_STATES,
|
||||
subresource: u32,
|
||||
) -> D3D12_RESOURCE_BARRIER {
|
||||
D3D12_RESOURCE_BARRIER {
|
||||
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
||||
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
|
||||
Anonymous: D3D12_RESOURCE_BARRIER_0 {
|
||||
Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER {
|
||||
pResource: ManuallyDrop::new(Some(resource.clone())),
|
||||
Subresource: subresource,
|
||||
StateBefore: before,
|
||||
StateAfter: after,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn d3d12_resource_transition_subresource(
|
||||
cmd: &ID3D12GraphicsCommandList,
|
||||
resource: &ID3D12Resource,
|
||||
before: D3D12_RESOURCE_STATES,
|
||||
after: D3D12_RESOURCE_STATES,
|
||||
subresource: u32,
|
||||
) {
|
||||
let barrier = [d3d12_get_resource_transition_subresource(
|
||||
resource,
|
||||
before,
|
||||
after,
|
||||
subresource,
|
||||
)];
|
||||
unsafe { cmd.ResourceBarrier(&barrier) }
|
||||
}
|
||||
|
||||
pub(crate) fn d3d12_update_subresources(
|
||||
cmd: &ID3D12GraphicsCommandList,
|
||||
destination_resource: &ID3D12Resource,
|
||||
intermediate_resource: &ID3D12Resource,
|
||||
intermediate_offset: u64,
|
||||
first_subresouce: u32,
|
||||
num_subresources: u32,
|
||||
source: &[D3D12_SUBRESOURCE_DATA],
|
||||
) -> anyhow::Result<u64> {
|
||||
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
|
||||
// + std::mem::size_of::<u32>()
|
||||
// + std::mem::size_of::<u64>() * num_subresources;
|
||||
|
||||
unsafe {
|
||||
let destination_desc = destination_resource.GetDesc();
|
||||
let mut device: Option<ID3D12Device> = None;
|
||||
destination_resource.GetDevice(&mut device)?;
|
||||
let device = device.ok_or_else(|| anyhow!("Unable to get device"))?;
|
||||
|
||||
let mut layouts =
|
||||
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
|
||||
let mut num_rows = vec![0; num_subresources as usize];
|
||||
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
|
||||
let mut required_size = 0;
|
||||
|
||||
// texture upload
|
||||
device.GetCopyableFootprints(
|
||||
&destination_desc,
|
||||
first_subresouce,
|
||||
num_subresources,
|
||||
intermediate_offset,
|
||||
Some(layouts.as_mut_ptr()),
|
||||
Some(num_rows.as_mut_ptr()),
|
||||
Some(row_sizes_in_bytes.as_mut_ptr()),
|
||||
Some(&mut required_size),
|
||||
);
|
||||
|
||||
update_subresources(
|
||||
cmd,
|
||||
destination_resource,
|
||||
intermediate_resource,
|
||||
first_subresouce,
|
||||
num_subresources,
|
||||
required_size,
|
||||
&layouts,
|
||||
&num_rows,
|
||||
&row_sizes_in_bytes,
|
||||
source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_subresources(
|
||||
cmd: &ID3D12GraphicsCommandList,
|
||||
destination_resource: &ID3D12Resource,
|
||||
intermediate_resource: &ID3D12Resource,
|
||||
first_subresouce: u32,
|
||||
num_subresources: u32,
|
||||
required_size: u64,
|
||||
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
|
||||
num_rows: &[u32],
|
||||
row_sizes_in_bytes: &[u64],
|
||||
source_data: &[D3D12_SUBRESOURCE_DATA],
|
||||
) -> anyhow::Result<u64> {
|
||||
// ToDo: implement validation as in the original function
|
||||
|
||||
unsafe {
|
||||
let mut data = std::ptr::null_mut();
|
||||
intermediate_resource.Map(0, None, Some(&mut data))?;
|
||||
|
||||
for i in 0..num_subresources as usize {
|
||||
let dest_data = D3D12_MEMCPY_DEST {
|
||||
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
|
||||
RowPitch: layouts[i].Footprint.RowPitch as usize,
|
||||
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
|
||||
};
|
||||
|
||||
memcpy_subresource(
|
||||
&dest_data,
|
||||
&source_data[i],
|
||||
row_sizes_in_bytes[i],
|
||||
num_rows[i],
|
||||
layouts[i].Footprint.Depth,
|
||||
);
|
||||
}
|
||||
intermediate_resource.Unmap(0, None);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let destination_desc = destination_resource.GetDesc();
|
||||
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
|
||||
cmd.CopyBufferRegion(
|
||||
destination_resource,
|
||||
0,
|
||||
intermediate_resource,
|
||||
layouts[0].Offset,
|
||||
layouts[0].Footprint.Width as u64,
|
||||
);
|
||||
} else {
|
||||
for i in 0..num_subresources as usize {
|
||||
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
|
||||
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||
SubresourceIndex: i as u32 + first_subresouce,
|
||||
},
|
||||
};
|
||||
|
||||
let source_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
|
||||
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
||||
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||
PlacedFootprint: layouts[i],
|
||||
},
|
||||
};
|
||||
|
||||
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
|
||||
}
|
||||
}
|
||||
Ok(required_size)
|
||||
}
|
||||
}
|
||||
|
||||
// this function should not leak to the public API, so
|
||||
// there is no point in using struct wrappers
|
||||
unsafe fn memcpy_subresource(
|
||||
dest: &D3D12_MEMCPY_DEST,
|
||||
src: &D3D12_SUBRESOURCE_DATA,
|
||||
row_sizes_in_bytes: u64,
|
||||
num_rows: u32,
|
||||
num_slices: u32,
|
||||
) {
|
||||
unsafe {
|
||||
for z in 0..num_slices as usize {
|
||||
let dest_slice = dest.pData.add(dest.SlicePitch * z);
|
||||
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
|
||||
|
||||
for y in 0..num_rows as usize {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
src_slice.offset(src.RowPitch * y as isize),
|
||||
dest_slice.add(dest.RowPitch * y),
|
||||
row_sizes_in_bytes as usize,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
193
librashader-cli/src/render/d3d9.rs
Normal file
193
librashader-cli/src/render/d3d9.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use image::RgbaImage;
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
|
||||
use std::path::Path;
|
||||
use windows::Win32::Foundation::{HWND, TRUE};
|
||||
use windows::Win32::Graphics::Direct3D9::{
|
||||
Direct3DCreate9, IDirect3D9, IDirect3DDevice9, IDirect3DTexture9, D3DADAPTER_DEFAULT,
|
||||
D3DCREATE_HARDWARE_VERTEXPROCESSING, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DLOCKED_RECT,
|
||||
D3DPOOL_DEFAULT, D3DPOOL_MANAGED, D3DPOOL_SYSTEMMEM, D3DPRESENT_INTERVAL_IMMEDIATE,
|
||||
D3DPRESENT_PARAMETERS, D3DSURFACE_DESC, D3DSWAPEFFECT_DISCARD, D3DUSAGE_RENDERTARGET,
|
||||
D3D_SDK_VERSION,
|
||||
};
|
||||
|
||||
pub struct Direct3D9 {
|
||||
pub texture: IDirect3DTexture9,
|
||||
pub image: Image<BGRA8>,
|
||||
pub direct3d: IDirect3D9,
|
||||
pub device: IDirect3DDevice9,
|
||||
}
|
||||
|
||||
impl RenderTest for Direct3D9 {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Direct3D9::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
unsafe {
|
||||
let mut filter_chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
&self.device,
|
||||
Some(&FilterChainOptions {
|
||||
force_no_mipmaps: false,
|
||||
disable_cache: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
let mut render_texture = None;
|
||||
|
||||
let output_size = output_size.unwrap_or(self.image.size);
|
||||
self.device.CreateTexture(
|
||||
output_size.width,
|
||||
output_size.height,
|
||||
1,
|
||||
D3DUSAGE_RENDERTARGET as u32,
|
||||
D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_DEFAULT,
|
||||
&mut render_texture,
|
||||
std::ptr::null_mut(),
|
||||
)?;
|
||||
|
||||
let render_texture = render_texture
|
||||
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 render texture"))?;
|
||||
|
||||
let mut copy_texture = None;
|
||||
|
||||
self.device.CreateOffscreenPlainSurface(
|
||||
output_size.width,
|
||||
output_size.height,
|
||||
D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_SYSTEMMEM,
|
||||
&mut copy_texture,
|
||||
std::ptr::null_mut(),
|
||||
)?;
|
||||
|
||||
let copy_texture =
|
||||
copy_texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 copy texture"))?;
|
||||
|
||||
let surface = render_texture.GetSurfaceLevel(0)?;
|
||||
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(&surface, None)?;
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
filter_chain.frame(&self.texture, &viewport, frame, options.as_ref())?;
|
||||
}
|
||||
|
||||
self.device.GetRenderTargetData(&surface, ©_texture)?;
|
||||
|
||||
let mut desc = D3DSURFACE_DESC::default();
|
||||
surface.GetDesc(&mut desc)?;
|
||||
|
||||
let mut lock = D3DLOCKED_RECT::default();
|
||||
copy_texture.LockRect(&mut lock, std::ptr::null_mut(), 0)?;
|
||||
let mut buffer = vec![0u8; desc.Height as usize * lock.Pitch as usize];
|
||||
|
||||
std::ptr::copy_nonoverlapping(lock.pBits.cast(), buffer.as_mut_ptr(), buffer.len());
|
||||
copy_texture.UnlockRect()?;
|
||||
|
||||
BGRA8::convert(&mut buffer);
|
||||
|
||||
let image = RgbaImage::from_raw(output_size.width, output_size.height, buffer)
|
||||
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Direct3D9 {
|
||||
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let direct3d = unsafe {
|
||||
Direct3DCreate9(D3D_SDK_VERSION)
|
||||
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
|
||||
};
|
||||
|
||||
let image = Image::<BGRA8>::load(image_path, UVDirection::TopLeft)?;
|
||||
|
||||
let mut present_params: D3DPRESENT_PARAMETERS = Default::default();
|
||||
present_params.BackBufferWidth = image.size.width;
|
||||
present_params.BackBufferHeight = image.size.height;
|
||||
present_params.Windowed = TRUE;
|
||||
present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
present_params.BackBufferFormat = D3DFMT_A8R8G8B8;
|
||||
present_params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE as u32;
|
||||
|
||||
let device = unsafe {
|
||||
let mut device = None;
|
||||
direct3d.CreateDevice(
|
||||
D3DADAPTER_DEFAULT,
|
||||
D3DDEVTYPE_HAL,
|
||||
HWND(std::ptr::null_mut()),
|
||||
D3DCREATE_HARDWARE_VERTEXPROCESSING as u32,
|
||||
&mut present_params,
|
||||
&mut device,
|
||||
)?;
|
||||
device.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
|
||||
};
|
||||
|
||||
let texture = unsafe {
|
||||
let mut texture = None;
|
||||
device.CreateTexture(
|
||||
image.size.width,
|
||||
image.size.height,
|
||||
1,
|
||||
0,
|
||||
D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_MANAGED,
|
||||
&mut texture,
|
||||
std::ptr::null_mut(),
|
||||
)?;
|
||||
|
||||
texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 texture"))?
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut lock = D3DLOCKED_RECT::default();
|
||||
texture.LockRect(0, &mut lock, std::ptr::null_mut(), 0)?;
|
||||
std::ptr::copy_nonoverlapping(
|
||||
image.bytes.as_ptr(),
|
||||
lock.pBits.cast(),
|
||||
image.bytes.len(),
|
||||
);
|
||||
texture.UnlockRect(0)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
texture,
|
||||
image,
|
||||
direct3d,
|
||||
device,
|
||||
})
|
||||
}
|
||||
}
|
67
librashader-cli/src/render/gl/context.rs
Normal file
67
librashader-cli/src/render/gl/context.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2023 Christian Vallentin
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use glfw::{fail_on_errors, Context, Glfw, OpenGlProfileHint, PWindow, WindowHint, WindowMode};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GlfwContext {
|
||||
_wnd: PWindow,
|
||||
_glfw: Glfw,
|
||||
pub gl: Arc<glow::Context>,
|
||||
}
|
||||
|
||||
impl GlfwContext {
|
||||
pub fn new(version: GLVersion, width: u32, height: u32) -> Result<Self, anyhow::Error> {
|
||||
let mut glfw = glfw::init(fail_on_errors!())?;
|
||||
|
||||
let GLVersion(major, minor) = version;
|
||||
glfw.window_hint(WindowHint::ContextVersion(major, minor));
|
||||
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
|
||||
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
|
||||
glfw.window_hint(WindowHint::Visible(false));
|
||||
glfw.window_hint(WindowHint::OpenGlDebugContext(true));
|
||||
|
||||
let (mut wnd, _events) = glfw
|
||||
.create_window(width, height, env!("CARGO_PKG_NAME"), WindowMode::Windowed)
|
||||
.ok_or_else(|| anyhow!("No window"))?;
|
||||
|
||||
wnd.make_current();
|
||||
|
||||
let gl =
|
||||
unsafe { glow::Context::from_loader_function(|proc| wnd.get_proc_address(proc) as _) };
|
||||
|
||||
// unsafe {
|
||||
// gl.enable(glow::DEBUG_OUTPUT);
|
||||
// gl.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
// gl.debug_message_callback(debug_callback);
|
||||
// gl.debug_message_control(glow::DONT_CARE, glow::DONT_CARE, glow::DONT_CARE, &[], true);
|
||||
// }
|
||||
|
||||
Ok(Self {
|
||||
_wnd: wnd,
|
||||
_glfw: glfw,
|
||||
gl: Arc::new(gl),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GLVersion(pub u32, pub u32);
|
259
librashader-cli/src/render/gl/mod.rs
Normal file
259
librashader-cli/src/render/gl/mod.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
mod context;
|
||||
|
||||
use crate::render::gl::context::{GLVersion, GlfwContext};
|
||||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use glow::{HasContext, PixelPackData, PixelUnpackData};
|
||||
use image::RgbaImage;
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, UVDirection, RGBA8};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct OpenGl {
|
||||
context: GlfwContext,
|
||||
texture: GLImage,
|
||||
image_bytes: Image<RGBA8>,
|
||||
}
|
||||
|
||||
pub struct OpenGl3(OpenGl);
|
||||
pub struct OpenGl4(OpenGl);
|
||||
|
||||
impl RenderTest for OpenGl3 {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
OpenGl3::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.0.image_bytes.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let mut filter_chain = unsafe {
|
||||
FilterChain::load_from_preset(
|
||||
preset,
|
||||
Arc::clone(&self.0.context.gl),
|
||||
Some(&FilterChainOptions {
|
||||
glsl_version: 330,
|
||||
use_dsa: false,
|
||||
force_no_mipmaps: false,
|
||||
disable_cache: false,
|
||||
}),
|
||||
)
|
||||
}?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
Ok(self.0.render(
|
||||
&mut filter_chain,
|
||||
frame_count,
|
||||
output_size,
|
||||
frame_options
|
||||
.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
})
|
||||
.as_ref(),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTest for OpenGl4 {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
OpenGl4::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.0.image_bytes.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let mut filter_chain = unsafe {
|
||||
FilterChain::load_from_preset(
|
||||
preset,
|
||||
Arc::clone(&self.0.context.gl),
|
||||
Some(&FilterChainOptions {
|
||||
glsl_version: 460,
|
||||
use_dsa: true,
|
||||
force_no_mipmaps: false,
|
||||
disable_cache: true,
|
||||
}),
|
||||
)
|
||||
}?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
Ok(self.0.render(
|
||||
&mut filter_chain,
|
||||
frame_count,
|
||||
output_size,
|
||||
frame_options
|
||||
.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
})
|
||||
.as_ref(),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenGl3 {
|
||||
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
|
||||
Ok(Self(OpenGl::new(image_path, false)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenGl4 {
|
||||
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
|
||||
Ok(Self(OpenGl::new(image_path, true)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenGl {
|
||||
pub fn new(image_path: &Path, use_dsa: bool) -> anyhow::Result<Self> {
|
||||
let image: Image<RGBA8> = Image::load(image_path, UVDirection::TopLeft)?;
|
||||
let height = image.size.height;
|
||||
let width = image.size.width;
|
||||
let version = if use_dsa {
|
||||
GLVersion(4, 6)
|
||||
} else {
|
||||
GLVersion(3, 3)
|
||||
};
|
||||
|
||||
let context = GlfwContext::new(version, width, height)?;
|
||||
|
||||
let texture = unsafe {
|
||||
let tex = context.gl.create_texture().map_err(|s| anyhow!("{}", s))?;
|
||||
context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
|
||||
context.gl.tex_storage_2d(
|
||||
glow::TEXTURE_2D,
|
||||
1,
|
||||
glow::RGBA8,
|
||||
image.size.width as i32,
|
||||
image.size.height as i32,
|
||||
);
|
||||
|
||||
context.gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
|
||||
context.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 4);
|
||||
context.gl.bind_buffer(glow::PIXEL_UNPACK_BUFFER, None);
|
||||
|
||||
context.gl.tex_sub_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
image.size.width as i32,
|
||||
image.size.height as i32,
|
||||
glow::RGBA,
|
||||
glow::UNSIGNED_BYTE,
|
||||
PixelUnpackData::Slice(&image.bytes),
|
||||
);
|
||||
|
||||
context.gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
tex
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
context,
|
||||
texture: GLImage {
|
||||
handle: Some(texture),
|
||||
format: glow::RGBA8,
|
||||
size: image.size,
|
||||
},
|
||||
image_bytes: image,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
chain: &mut FilterChain,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
options: Option<&FrameOptions>,
|
||||
) -> Result<RgbaImage, anyhow::Error> {
|
||||
let output_size = output_size.unwrap_or(self.image_bytes.size);
|
||||
|
||||
let render_texture = unsafe {
|
||||
let tex = self
|
||||
.context
|
||||
.gl
|
||||
.create_texture()
|
||||
.map_err(|s| anyhow!("{}", s))?;
|
||||
self.context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
|
||||
self.context.gl.tex_storage_2d(
|
||||
glow::TEXTURE_2D,
|
||||
1,
|
||||
glow::RGBA8,
|
||||
output_size.width as i32,
|
||||
output_size.height as i32,
|
||||
);
|
||||
self.context.gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
tex
|
||||
};
|
||||
|
||||
let output = GLImage {
|
||||
handle: Some(render_texture),
|
||||
format: glow::RGBA8,
|
||||
size: output_size,
|
||||
};
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(&output, None)?;
|
||||
for frame in 0..=frame_count {
|
||||
unsafe {
|
||||
chain.frame(&self.texture, &viewport, frame, options)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = vec![0u8; output_size.width as usize * output_size.height as usize * 4];
|
||||
|
||||
unsafe {
|
||||
self.context
|
||||
.gl
|
||||
.bind_texture(glow::TEXTURE_2D, output.handle);
|
||||
self.context.gl.get_tex_image(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::RGBA,
|
||||
glow::UNSIGNED_BYTE,
|
||||
PixelPackData::Slice(&mut data),
|
||||
)
|
||||
}
|
||||
Ok(
|
||||
RgbaImage::from_raw(output_size.width, output_size.height, data)
|
||||
.ok_or(anyhow!("failed to create image from slice"))?,
|
||||
)
|
||||
}
|
||||
}
|
174
librashader-cli/src/render/mod.rs
Normal file
174
librashader-cli/src/render/mod.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
#[cfg(all(windows, feature = "d3d11"))]
|
||||
pub mod d3d11;
|
||||
|
||||
#[cfg(all(windows, feature = "d3d12"))]
|
||||
pub mod d3d12;
|
||||
|
||||
#[cfg(all(windows, feature = "d3d9"))]
|
||||
pub mod d3d9;
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
pub mod gl;
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub mod vk;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub mod wgpu;
|
||||
|
||||
#[cfg(all(target_vendor = "apple", feature = "metal"))]
|
||||
pub mod mtl;
|
||||
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::Size;
|
||||
use librashader_runtime::impl_default_frame_options;
|
||||
use librashader_runtime::parameters::RuntimeParameters;
|
||||
use std::path::Path;
|
||||
|
||||
/// Test harness to set up a device, render a triangle, and apply a shader
|
||||
pub trait RenderTest {
|
||||
/// Create a new instance of the test harness.
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get the size of the image loaded.
|
||||
fn image_size(&self) -> Size<u32>;
|
||||
|
||||
/// Render a shader onto an image buffer, applying the provided shader.
|
||||
///
|
||||
/// The test should render in linear colour space for proper comparison against
|
||||
/// backends.
|
||||
///
|
||||
/// For testing purposes, it is often that a single image will be reused with multiple
|
||||
/// shader presets, so the actual image that a shader will be applied to
|
||||
/// will often be part of the test harness object.
|
||||
fn render(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let preset = ShaderPreset::try_parse(path)?;
|
||||
self.render_with_preset(preset, frame_count, output_size)
|
||||
}
|
||||
|
||||
/// Render a shader onto an image buffer, applying the provided shader.
|
||||
///
|
||||
/// The test should render in linear colour space for proper comparison against
|
||||
/// backends.
|
||||
///
|
||||
/// For testing purposes, it is often that a single image will be reused with multiple
|
||||
/// shader presets, so the actual image that a shader will be applied to
|
||||
/// will often be part of the test harness object.
|
||||
fn render_with_preset(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
self.render_with_preset_and_params(preset, frame_count, output_size, None, None)
|
||||
}
|
||||
|
||||
/// Render a shader onto an image buffer, applying the provided shader.
|
||||
///
|
||||
/// The test should render in linear colour space for proper comparison against
|
||||
/// backends.
|
||||
///
|
||||
/// For testing purposes, it is often that a single image will be reused with multiple
|
||||
/// shader presets, so the actual image that a shader will be applied to
|
||||
/// will often be part of the test harness object.
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage>;
|
||||
}
|
||||
|
||||
impl_default_frame_options!(CommonFrameOptions);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use crate::render::RenderTest;
|
||||
use image::codecs::png::PngEncoder;
|
||||
use std::fs::File;
|
||||
|
||||
const IMAGE_PATH: &str = "../triangle.png";
|
||||
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
|
||||
|
||||
// const FILTER_PATH: &str =
|
||||
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
|
||||
|
||||
fn do_test<T: RenderTest>() -> anyhow::Result<()> {
|
||||
let mut test = T::new(IMAGE_PATH.as_ref())?;
|
||||
let image = test.render(FILTER_PATH.as_ref(), 100)?;
|
||||
|
||||
let out = File::create("out.png")?;
|
||||
image.write_with_encoder(PngEncoder::new(out))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(windows, feature = "d3d11"))]
|
||||
pub fn test_d3d11() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::d3d11::Direct3D11>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub fn test_wgpu() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::wgpu::Wgpu>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub fn test_vk() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::vk::Vulkan>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn test_gl3() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::gl::OpenGl3>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn test_gl4() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::gl::OpenGl4>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(target_vendor = "apple", feature = "metal"))]
|
||||
pub fn test_metal() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::mtl::Metal>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(windows, feature = "d3d9"))]
|
||||
pub fn test_d3d9() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::d3d9::Direct3D9>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(windows, feature = "d3d12"))]
|
||||
pub fn test_d3d12() -> anyhow::Result<()> {
|
||||
do_test::<crate::render::d3d12::Direct3D12>()
|
||||
}
|
||||
|
||||
pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
|
||||
let mut a = A::new(IMAGE_PATH.as_ref())?;
|
||||
let mut b = B::new(IMAGE_PATH.as_ref())?;
|
||||
|
||||
let a_image = a.render(FILTER_PATH.as_ref(), 100)?;
|
||||
let b_image = b.render(FILTER_PATH.as_ref(), 100)?;
|
||||
|
||||
let similarity = image_compare::rgba_hybrid_compare(&a_image, &b_image)?;
|
||||
assert!(similarity.score > 0.95);
|
||||
Ok(())
|
||||
}
|
||||
}
|
208
librashader-cli/src/render/mtl.rs
Normal file
208
librashader-cli/src/render/mtl.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use image::RgbaImage;
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
|
||||
use objc2::ffi::NSUInteger;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2_metal::{
|
||||
MTLCommandBuffer, MTLCommandQueue, MTLCreateSystemDefaultDevice, MTLDevice, MTLOrigin,
|
||||
MTLPixelFormat, MTLRegion, MTLSize, MTLStorageMode, MTLTexture, MTLTextureDescriptor,
|
||||
MTLTextureUsage,
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct Metal {
|
||||
device: Retained<ProtocolObject<dyn MTLDevice>>,
|
||||
texture: Retained<ProtocolObject<dyn MTLTexture>>,
|
||||
image_bytes: Image<BGRA8>,
|
||||
}
|
||||
|
||||
impl RenderTest for Metal {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Metal::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image_bytes.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let queue = self
|
||||
.device
|
||||
.newCommandQueue()
|
||||
.ok_or_else(|| anyhow!("Unable to create command queue"))?;
|
||||
|
||||
let cmd = queue
|
||||
.commandBuffer()
|
||||
.ok_or_else(|| anyhow!("Unable to create command buffer"))?;
|
||||
|
||||
let mut filter_chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
&queue,
|
||||
Some(&FilterChainOptions {
|
||||
force_no_mipmaps: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
let output_size = output_size.unwrap_or(self.image_bytes.size);
|
||||
|
||||
let render_texture = unsafe {
|
||||
let texture_descriptor =
|
||||
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
output_size.width as NSUInteger,
|
||||
output_size.height as NSUInteger,
|
||||
false,
|
||||
);
|
||||
|
||||
texture_descriptor.setSampleCount(1);
|
||||
texture_descriptor.setStorageMode(
|
||||
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
|
||||
MTLStorageMode::Shared
|
||||
} else {
|
||||
MTLStorageMode::Managed
|
||||
},
|
||||
);
|
||||
texture_descriptor.setUsage(MTLTextureUsage::ShaderWrite);
|
||||
|
||||
let texture = self
|
||||
.device
|
||||
.newTextureWithDescriptor(&texture_descriptor)
|
||||
.ok_or_else(|| anyhow!("Failed to create texture"))?;
|
||||
|
||||
texture
|
||||
};
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(render_texture.as_ref(), None)?;
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
filter_chain.frame(
|
||||
&self.texture,
|
||||
&viewport,
|
||||
cmd.as_ref(),
|
||||
frame,
|
||||
options.as_ref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
cmd.commit();
|
||||
unsafe {
|
||||
cmd.waitUntilCompleted();
|
||||
}
|
||||
|
||||
let region = MTLRegion {
|
||||
origin: MTLOrigin { x: 0, y: 0, z: 0 },
|
||||
size: MTLSize {
|
||||
width: output_size.width as usize,
|
||||
height: output_size.height as usize,
|
||||
depth: 1,
|
||||
},
|
||||
};
|
||||
|
||||
unsafe {
|
||||
// should be the same size
|
||||
let mut buffer =
|
||||
vec![0u8; output_size.width as usize * output_size.height as usize * 4];
|
||||
render_texture.getBytes_bytesPerRow_fromRegion_mipmapLevel(
|
||||
NonNull::new(buffer.as_mut_ptr().cast()).unwrap(),
|
||||
4 * output_size.width as usize,
|
||||
region,
|
||||
0,
|
||||
);
|
||||
|
||||
// swap the BGRA back to RGBA.
|
||||
BGRA8::convert(&mut buffer);
|
||||
|
||||
let image = RgbaImage::from_raw(
|
||||
render_texture.width() as u32,
|
||||
render_texture.height() as u32,
|
||||
Vec::from(buffer),
|
||||
)
|
||||
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
|
||||
|
||||
unsafe {
|
||||
let device = Retained::from_raw(MTLCreateSystemDefaultDevice())
|
||||
.ok_or_else(|| anyhow!("Unable to create default Metal device"))?;
|
||||
|
||||
let texture_descriptor =
|
||||
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
image.size.width as NSUInteger,
|
||||
image.size.height as NSUInteger,
|
||||
false,
|
||||
);
|
||||
|
||||
texture_descriptor.setSampleCount(1);
|
||||
texture_descriptor.setStorageMode(
|
||||
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
|
||||
MTLStorageMode::Shared
|
||||
} else {
|
||||
MTLStorageMode::Managed
|
||||
},
|
||||
);
|
||||
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
|
||||
|
||||
let texture = device
|
||||
.newTextureWithDescriptor(&texture_descriptor)
|
||||
.ok_or_else(|| anyhow!("Failed to create texture"))?;
|
||||
|
||||
let region = MTLRegion {
|
||||
origin: MTLOrigin { x: 0, y: 0, z: 0 },
|
||||
size: MTLSize {
|
||||
width: image.size.width as usize,
|
||||
height: image.size.height as usize,
|
||||
depth: 1,
|
||||
},
|
||||
};
|
||||
texture.replaceRegion_mipmapLevel_withBytes_bytesPerRow(
|
||||
region,
|
||||
0,
|
||||
// SAFETY: replaceRegion withBytes is const.
|
||||
NonNull::new_unchecked(image.bytes.as_slice().as_ptr() as *mut _),
|
||||
4 * image.size.width as usize,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
texture,
|
||||
image_bytes: image,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
153
librashader-cli/src/render/vk/base.rs
Normal file
153
librashader-cli/src/render/vk/base.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use ash::vk;
|
||||
use gpu_allocator::vulkan::Allocator;
|
||||
use librashader::runtime::vk::VulkanObjects;
|
||||
use parking_lot::Mutex;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct VulkanBase {
|
||||
device: Arc<ash::Device>,
|
||||
graphics_queue: vk::Queue,
|
||||
allocator: Arc<Mutex<Allocator>>,
|
||||
cmd_buffer: vk::CommandBuffer,
|
||||
pool: vk::CommandPool,
|
||||
}
|
||||
|
||||
impl From<&VulkanBase> for VulkanObjects {
|
||||
fn from(value: &VulkanBase) -> Self {
|
||||
VulkanObjects {
|
||||
device: Arc::clone(&value.device),
|
||||
alloc: Arc::clone(&value.allocator),
|
||||
queue: value.graphics_queue.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const KHRONOS_VALIDATION: &[u8] = b"VK_LAYER_KHRONOS_validation\0";
|
||||
const LIBRASHADER_VULKAN: &[u8] = b"librashader Vulkan\0";
|
||||
|
||||
impl VulkanBase {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let entry = unsafe { ash::Entry::load() }?;
|
||||
let layers = [KHRONOS_VALIDATION.as_ptr().cast()];
|
||||
let app_info = vk::ApplicationInfo::default()
|
||||
.application_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
|
||||
.engine_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
|
||||
.engine_version(0)
|
||||
.application_version(0)
|
||||
.api_version(vk::make_api_version(0, 1, 3, 0));
|
||||
|
||||
let create_info = vk::InstanceCreateInfo::default()
|
||||
.application_info(&app_info)
|
||||
.enabled_layer_names(&layers)
|
||||
.enabled_extension_names(&[]);
|
||||
|
||||
let instance = unsafe { entry.create_instance(&create_info, None) }?;
|
||||
|
||||
let physical_device = super::physical_device::pick_physical_device(&instance)?;
|
||||
|
||||
let (device, queue, cmd_pool) = Self::create_device(&instance, &physical_device)?;
|
||||
|
||||
let alloc = super::memory::create_allocator(
|
||||
device.clone(),
|
||||
instance.clone(),
|
||||
physical_device.clone(),
|
||||
);
|
||||
|
||||
let buffer_info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(cmd_pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(1);
|
||||
|
||||
let buffers = unsafe { device.allocate_command_buffers(&buffer_info) }?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
device: Arc::new(device),
|
||||
graphics_queue: queue,
|
||||
// debug,
|
||||
allocator: alloc,
|
||||
pool: cmd_pool,
|
||||
cmd_buffer: buffers,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn device(&self) -> &Arc<ash::Device> {
|
||||
&self.device
|
||||
}
|
||||
pub(crate) fn allocator(&self) -> &Arc<Mutex<Allocator>> {
|
||||
&self.allocator
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
instance: &ash::Instance,
|
||||
physical_device: &vk::PhysicalDevice,
|
||||
) -> anyhow::Result<(ash::Device, vk::Queue, vk::CommandPool)> {
|
||||
let _debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }];
|
||||
let indices = super::physical_device::find_queue_family(&instance, *physical_device);
|
||||
|
||||
let queue_info = [vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(indices.graphics_family()?)
|
||||
.queue_priorities(&[1.0f32])];
|
||||
|
||||
let mut physical_device_features =
|
||||
vk::PhysicalDeviceVulkan13Features::default().dynamic_rendering(true);
|
||||
|
||||
let extensions = [ash::khr::dynamic_rendering::NAME.as_ptr()];
|
||||
|
||||
let device_create_info = vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(&queue_info)
|
||||
.enabled_extension_names(&extensions)
|
||||
.push_next(&mut physical_device_features);
|
||||
|
||||
let device =
|
||||
unsafe { instance.create_device(*physical_device, &device_create_info, None)? };
|
||||
|
||||
let queue = unsafe { device.get_device_queue(indices.graphics_family()?, 0) };
|
||||
|
||||
let create_info = vk::CommandPoolCreateInfo::default()
|
||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||
.queue_family_index(indices.graphics_family()?);
|
||||
let pool = unsafe { device.create_command_pool(&create_info, None) }?;
|
||||
|
||||
Ok((device, queue, pool))
|
||||
}
|
||||
|
||||
/// Simple helper function to synchronously queue work on the graphics queue
|
||||
pub fn queue_work<T>(&self, f: impl FnOnce(vk::CommandBuffer) -> T) -> anyhow::Result<T> {
|
||||
unsafe {
|
||||
self.device.begin_command_buffer(
|
||||
self.cmd_buffer,
|
||||
&vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
|
||||
)?
|
||||
}
|
||||
|
||||
let result = f(self.cmd_buffer);
|
||||
|
||||
unsafe {
|
||||
self.device.end_command_buffer(self.cmd_buffer)?;
|
||||
|
||||
self.device.queue_submit(
|
||||
self.graphics_queue,
|
||||
&[vk::SubmitInfo::default().command_buffers(&[self.cmd_buffer])],
|
||||
vk::Fence::null(),
|
||||
)?;
|
||||
self.device.queue_wait_idle(self.graphics_queue)?;
|
||||
self.device
|
||||
.reset_command_buffer(self.cmd_buffer, vk::CommandBufferResetFlags::empty())?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanBase {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device.destroy_command_pool(self.pool, None);
|
||||
}
|
||||
}
|
||||
}
|
134
librashader-cli/src/render/vk/memory.rs
Normal file
134
librashader-cli/src/render/vk/memory.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use ash::vk;
|
||||
use gpu_allocator::vulkan::{
|
||||
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
|
||||
};
|
||||
use gpu_allocator::{AllocationSizes, MemoryLocation};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct VulkanImageMemory {
|
||||
pub(crate) allocation: ManuallyDrop<Allocation>,
|
||||
allocator: Arc<Mutex<Allocator>>,
|
||||
}
|
||||
|
||||
impl VulkanImageMemory {
|
||||
pub fn new(
|
||||
device: &Arc<ash::Device>,
|
||||
allocator: &Arc<Mutex<Allocator>>,
|
||||
requirements: vk::MemoryRequirements,
|
||||
image: &vk::Image,
|
||||
location: MemoryLocation,
|
||||
) -> anyhow::Result<VulkanImageMemory> {
|
||||
let allocation = allocator.lock().allocate(&AllocationCreateDesc {
|
||||
name: "imagemem",
|
||||
requirements,
|
||||
location,
|
||||
linear: false,
|
||||
allocation_scheme: AllocationScheme::DedicatedImage(*image),
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
device.bind_image_memory(*image, allocation.memory(), 0)?;
|
||||
Ok(VulkanImageMemory {
|
||||
allocation: ManuallyDrop::new(allocation),
|
||||
allocator: Arc::clone(allocator),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanImageMemory {
|
||||
fn drop(&mut self) {
|
||||
let allocation = unsafe { ManuallyDrop::take(&mut self.allocation) };
|
||||
if let Err(e) = self.allocator.lock().free(allocation) {
|
||||
println!("librashader-runtime-vk: [warn] failed to deallocate image buffer {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VulkanBuffer {
|
||||
pub handle: vk::Buffer,
|
||||
device: Arc<ash::Device>,
|
||||
allocation: ManuallyDrop<Allocation>,
|
||||
allocator: Arc<Mutex<Allocator>>,
|
||||
}
|
||||
|
||||
impl VulkanBuffer {
|
||||
pub fn new(
|
||||
device: &Arc<ash::Device>,
|
||||
allocator: &Arc<Mutex<Allocator>>,
|
||||
usage: vk::BufferUsageFlags,
|
||||
size: usize,
|
||||
) -> anyhow::Result<VulkanBuffer> {
|
||||
unsafe {
|
||||
let buffer_info = vk::BufferCreateInfo::default()
|
||||
.size(size as vk::DeviceSize)
|
||||
.usage(usage)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
let buffer = device.create_buffer(&buffer_info, None)?;
|
||||
|
||||
let memory_reqs = device.get_buffer_memory_requirements(buffer);
|
||||
|
||||
let alloc = allocator.lock().allocate(&AllocationCreateDesc {
|
||||
name: "buffer",
|
||||
requirements: memory_reqs,
|
||||
location: MemoryLocation::CpuToGpu,
|
||||
linear: true,
|
||||
allocation_scheme: AllocationScheme::DedicatedBuffer(buffer),
|
||||
})?;
|
||||
|
||||
// let alloc = device.allocate_memory(&alloc_info, None)?;
|
||||
device.bind_buffer_memory(buffer, alloc.memory(), 0)?;
|
||||
|
||||
Ok(VulkanBuffer {
|
||||
handle: buffer,
|
||||
allocation: ManuallyDrop::new(alloc),
|
||||
allocator: Arc::clone(allocator),
|
||||
device: device.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_slice(&mut self) -> anyhow::Result<&mut [u8]> {
|
||||
let Some(allocation) = self.allocation.mapped_slice_mut() else {
|
||||
return Err(anyhow!("Allocation is not host visible"));
|
||||
};
|
||||
Ok(allocation)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanBuffer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// SAFETY: things can not be double dropped.
|
||||
let allocation = ManuallyDrop::take(&mut self.allocation);
|
||||
if let Err(e) = self.allocator.lock().free(allocation) {
|
||||
println!("librashader-test-vk: [warn] failed to deallocate buffer memory {e}")
|
||||
}
|
||||
|
||||
if self.handle != vk::Buffer::null() {
|
||||
self.device.destroy_buffer(self.handle, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_allocator(
|
||||
device: ash::Device,
|
||||
instance: ash::Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Arc<Mutex<Allocator>> {
|
||||
let alloc = Allocator::new(&AllocatorCreateDesc {
|
||||
instance,
|
||||
device,
|
||||
physical_device,
|
||||
debug_settings: Default::default(),
|
||||
buffer_device_address: false,
|
||||
allocation_sizes: AllocationSizes::default(),
|
||||
})
|
||||
.unwrap();
|
||||
Arc::new(Mutex::new(alloc))
|
||||
}
|
391
librashader-cli/src/render/vk/mod.rs
Normal file
391
librashader-cli/src/render/vk/mod.rs
Normal file
|
@ -0,0 +1,391 @@
|
|||
use crate::render::vk::base::VulkanBase;
|
||||
use crate::render::vk::memory::{VulkanBuffer, VulkanImageMemory};
|
||||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use ash::vk;
|
||||
use gpu_allocator::MemoryLocation;
|
||||
use image::RgbaImage;
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::vk::{FilterChain, FilterChainOptions, FrameOptions, VulkanImage};
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, UVDirection, BGRA8};
|
||||
use std::path::Path;
|
||||
|
||||
mod base;
|
||||
mod memory;
|
||||
mod physical_device;
|
||||
mod util;
|
||||
|
||||
pub struct Vulkan {
|
||||
vk: VulkanBase,
|
||||
image_bytes: Image<BGRA8>,
|
||||
image: vk::Image,
|
||||
_image_alloc: VulkanImageMemory,
|
||||
}
|
||||
|
||||
impl RenderTest for Vulkan {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Vulkan::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image_bytes.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
unsafe {
|
||||
let mut filter_chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
&self.vk,
|
||||
Some(&FilterChainOptions {
|
||||
frames_in_flight: 3,
|
||||
force_no_mipmaps: false,
|
||||
use_dynamic_rendering: false,
|
||||
disable_cache: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
if let Some(setter) = param_setter {
|
||||
setter(filter_chain.parameters());
|
||||
}
|
||||
|
||||
let image_info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::B8G8R8A8_UNORM)
|
||||
.extent(output_size.map_or(self.image_bytes.size.into(), |size| size.into()))
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::OPTIMAL)
|
||||
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||
|
||||
let render_texture = { self.vk.device().create_image(&image_info, None)? };
|
||||
|
||||
// This just needs to stay alive until the read.
|
||||
let _memory = {
|
||||
let mem_reqs = self
|
||||
.vk
|
||||
.device()
|
||||
.get_image_memory_requirements(render_texture);
|
||||
VulkanImageMemory::new(
|
||||
self.vk.device(),
|
||||
&self.vk.allocator(),
|
||||
mem_reqs,
|
||||
&render_texture,
|
||||
MemoryLocation::GpuOnly,
|
||||
)?
|
||||
};
|
||||
|
||||
let transfer_texture = self.vk.device().create_image(
|
||||
&vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::R8G8B8A8_UNORM)
|
||||
.extent(self.image_bytes.size.into())
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::LINEAR)
|
||||
.usage(vk::ImageUsageFlags::TRANSFER_DST)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mut transfer_memory = {
|
||||
let mem_reqs = self
|
||||
.vk
|
||||
.device()
|
||||
.get_image_memory_requirements(transfer_texture);
|
||||
VulkanImageMemory::new(
|
||||
self.vk.device(),
|
||||
&self.vk.allocator(),
|
||||
mem_reqs,
|
||||
&transfer_texture,
|
||||
MemoryLocation::GpuToCpu,
|
||||
)?
|
||||
};
|
||||
|
||||
self.vk.queue_work(|cmd| {
|
||||
util::vulkan_image_layout_transition_levels(
|
||||
&self.vk.device(),
|
||||
cmd,
|
||||
render_texture,
|
||||
vk::REMAINING_MIP_LEVELS,
|
||||
vk::ImageLayout::UNDEFINED,
|
||||
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
);
|
||||
|
||||
util::vulkan_image_layout_transition_levels(
|
||||
&self.vk.device(),
|
||||
cmd,
|
||||
transfer_texture,
|
||||
vk::REMAINING_MIP_LEVELS,
|
||||
vk::ImageLayout::UNDEFINED,
|
||||
vk::ImageLayout::GENERAL,
|
||||
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
|
||||
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
|
||||
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
);
|
||||
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(
|
||||
VulkanImage {
|
||||
image: render_texture,
|
||||
size: self.image_bytes.size.into(),
|
||||
format: vk::Format::B8G8R8A8_UNORM,
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
filter_chain.frame(
|
||||
&VulkanImage {
|
||||
image: self.image,
|
||||
size: self.image_bytes.size,
|
||||
format: vk::Format::B8G8R8A8_UNORM,
|
||||
},
|
||||
&viewport,
|
||||
cmd,
|
||||
frame,
|
||||
options.as_ref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
{
|
||||
util::vulkan_image_layout_transition_levels(
|
||||
&self.vk.device(),
|
||||
cmd,
|
||||
render_texture,
|
||||
vk::REMAINING_MIP_LEVELS,
|
||||
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||
vk::AccessFlags::TRANSFER_READ,
|
||||
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
)
|
||||
}
|
||||
|
||||
let offsets = [
|
||||
vk::Offset3D { x: 0, y: 0, z: 0 },
|
||||
vk::Offset3D {
|
||||
x: self.image_bytes.size.width as i32,
|
||||
y: self.image_bytes.size.height as i32,
|
||||
z: 1,
|
||||
},
|
||||
];
|
||||
|
||||
let subresource = vk::ImageSubresourceLayers::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.base_array_layer(0)
|
||||
.layer_count(1);
|
||||
|
||||
let image_blit = vk::ImageBlit::default()
|
||||
.src_subresource(subresource.clone())
|
||||
.src_offsets(offsets.clone())
|
||||
.dst_subresource(subresource)
|
||||
.dst_offsets(offsets);
|
||||
|
||||
self.vk.device().cmd_blit_image(
|
||||
cmd,
|
||||
render_texture,
|
||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
transfer_texture,
|
||||
vk::ImageLayout::GENERAL,
|
||||
&[image_blit],
|
||||
vk::Filter::NEAREST,
|
||||
);
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})??;
|
||||
|
||||
// should have read now.
|
||||
let mut memory = transfer_memory
|
||||
.allocation
|
||||
.mapped_slice_mut()
|
||||
.ok_or(anyhow!("readback buffer was not mapped"))?;
|
||||
|
||||
let layout = self.vk.device().get_image_subresource_layout(
|
||||
transfer_texture,
|
||||
vk::ImageSubresource::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.array_layer(0),
|
||||
);
|
||||
memory = &mut memory[layout.offset as usize..];
|
||||
|
||||
// let mut pixels = Vec::with_capacity(layout.size as usize);
|
||||
|
||||
let image = RgbaImage::from_raw(
|
||||
self.image_bytes.size.width,
|
||||
self.image_bytes.size.height,
|
||||
Vec::from(memory),
|
||||
)
|
||||
.ok_or(anyhow!("failed to create image from slice"));
|
||||
|
||||
self.vk.device().destroy_image(transfer_texture, None);
|
||||
self.vk.device().destroy_image(render_texture, None);
|
||||
|
||||
Ok(image?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vulkan {
|
||||
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
|
||||
let vk = VulkanBase::new()?;
|
||||
|
||||
let (image_bytes, image_alloc, image, _view) = Self::load_image(&vk, image_path)?;
|
||||
|
||||
Ok(Self {
|
||||
vk,
|
||||
image,
|
||||
image_bytes,
|
||||
_image_alloc: image_alloc,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_image(
|
||||
vk: &VulkanBase,
|
||||
image_path: &Path,
|
||||
) -> anyhow::Result<(Image<BGRA8>, VulkanImageMemory, vk::Image, vk::ImageView)> {
|
||||
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
|
||||
|
||||
let image_info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::B8G8R8A8_UNORM)
|
||||
.extent(image.size.into())
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::OPTIMAL)
|
||||
.usage(
|
||||
vk::ImageUsageFlags::SAMPLED
|
||||
| vk::ImageUsageFlags::TRANSFER_SRC
|
||||
| vk::ImageUsageFlags::TRANSFER_DST,
|
||||
)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||
|
||||
let texture = unsafe { vk.device().create_image(&image_info, None)? };
|
||||
|
||||
let memory = unsafe {
|
||||
let mem_reqs = vk.device().get_image_memory_requirements(texture);
|
||||
VulkanImageMemory::new(
|
||||
vk.device(),
|
||||
&vk.allocator(),
|
||||
mem_reqs,
|
||||
&texture,
|
||||
MemoryLocation::GpuOnly,
|
||||
)?
|
||||
};
|
||||
|
||||
let image_subresource = vk::ImageSubresourceRange::default()
|
||||
.level_count(image_info.mip_levels)
|
||||
.layer_count(1)
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR);
|
||||
|
||||
let swizzle_components = vk::ComponentMapping::default()
|
||||
.r(vk::ComponentSwizzle::R)
|
||||
.g(vk::ComponentSwizzle::G)
|
||||
.b(vk::ComponentSwizzle::B)
|
||||
.a(vk::ComponentSwizzle::A);
|
||||
|
||||
let view_info = vk::ImageViewCreateInfo::default()
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(vk::Format::B8G8R8A8_UNORM)
|
||||
.image(texture)
|
||||
.subresource_range(image_subresource)
|
||||
.components(swizzle_components);
|
||||
|
||||
let texture_view = unsafe { vk.device().create_image_view(&view_info, None)? };
|
||||
|
||||
let mut staging = VulkanBuffer::new(
|
||||
&vk.device(),
|
||||
&vk.allocator(),
|
||||
vk::BufferUsageFlags::TRANSFER_SRC,
|
||||
image.bytes.len(),
|
||||
)?;
|
||||
|
||||
staging.as_mut_slice()?.copy_from_slice(&image.bytes);
|
||||
|
||||
vk.queue_work(|cmd| unsafe {
|
||||
util::vulkan_image_layout_transition_levels(
|
||||
&vk.device(),
|
||||
cmd,
|
||||
texture,
|
||||
vk::REMAINING_MIP_LEVELS,
|
||||
vk::ImageLayout::UNDEFINED,
|
||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
vk::AccessFlags::empty(),
|
||||
vk::AccessFlags::TRANSFER_WRITE,
|
||||
vk::PipelineStageFlags::TOP_OF_PIPE,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
);
|
||||
|
||||
let builder = vk::BufferImageCopy::default()
|
||||
.image_subresource(
|
||||
vk::ImageSubresourceLayers::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.mip_level(0)
|
||||
.base_array_layer(0)
|
||||
.layer_count(1),
|
||||
)
|
||||
.image_extent(image.size.into());
|
||||
|
||||
vk.device().cmd_copy_buffer_to_image(
|
||||
cmd,
|
||||
staging.handle,
|
||||
texture,
|
||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
&[builder],
|
||||
);
|
||||
|
||||
util::vulkan_image_layout_transition_levels(
|
||||
&vk.device(),
|
||||
cmd,
|
||||
texture,
|
||||
vk::REMAINING_MIP_LEVELS,
|
||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
vk::AccessFlags::TRANSFER_WRITE,
|
||||
vk::AccessFlags::SHADER_READ,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
vk::QUEUE_FAMILY_IGNORED,
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok((image, memory, texture, texture_view))
|
||||
}
|
||||
}
|
144
librashader-cli/src/render/vk/physical_device.rs
Normal file
144
librashader-cli/src/render/vk/physical_device.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use anyhow::anyhow;
|
||||
use ash::vk;
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub struct QueueFamilyIndices {
|
||||
graphics_family: Option<u32>,
|
||||
}
|
||||
|
||||
impl QueueFamilyIndices {
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.graphics_family.is_some()
|
||||
}
|
||||
|
||||
pub fn graphics_family(&self) -> anyhow::Result<u32> {
|
||||
self.graphics_family
|
||||
.ok_or(anyhow!("The queue family is not complete"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pick_physical_device(instance: &ash::Instance) -> anyhow::Result<vk::PhysicalDevice> {
|
||||
let physical_devices = unsafe { instance.enumerate_physical_devices()? };
|
||||
|
||||
let mut result = None;
|
||||
for &physical_device in physical_devices.iter() {
|
||||
if is_physical_device_suitable(instance, physical_device) && result.is_none() {
|
||||
result = Some(physical_device)
|
||||
}
|
||||
}
|
||||
|
||||
result.ok_or(anyhow!("Failed to find a suitable GPU"))
|
||||
}
|
||||
|
||||
fn is_physical_device_suitable(
|
||||
instance: &ash::Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> bool {
|
||||
let device_properties = unsafe { instance.get_physical_device_properties(physical_device) };
|
||||
let device_features = unsafe { instance.get_physical_device_features(physical_device) };
|
||||
let device_queue_families =
|
||||
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||
|
||||
if cfg!(feature = "vulkan-debug") {
|
||||
let device_type = match device_properties.device_type {
|
||||
vk::PhysicalDeviceType::CPU => "Cpu",
|
||||
vk::PhysicalDeviceType::INTEGRATED_GPU => "Integrated GPU",
|
||||
vk::PhysicalDeviceType::DISCRETE_GPU => "Discrete GPU",
|
||||
vk::PhysicalDeviceType::VIRTUAL_GPU => "Virtual GPU",
|
||||
vk::PhysicalDeviceType::OTHER => "Unknown",
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) };
|
||||
|
||||
let device_name = device_name.to_string_lossy();
|
||||
println!(
|
||||
"\tDevice Name: {}, id: {}, type: {}",
|
||||
device_name, device_properties.device_id, device_type
|
||||
);
|
||||
|
||||
println!("\tAPI Version: {}", device_properties.api_version);
|
||||
|
||||
println!("\tSupport Queue Family: {}", device_queue_families.len());
|
||||
println!("\t\tQueue Count | Graphics, Compute, Transfer, Sparse Binding");
|
||||
for queue_family in device_queue_families.iter() {
|
||||
let is_graphics_support = if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
|
||||
{
|
||||
"support"
|
||||
} else {
|
||||
"unsupport"
|
||||
};
|
||||
let is_compute_support = if queue_family.queue_flags.contains(vk::QueueFlags::COMPUTE) {
|
||||
"support"
|
||||
} else {
|
||||
"unsupport"
|
||||
};
|
||||
let is_transfer_support = if queue_family.queue_flags.contains(vk::QueueFlags::TRANSFER)
|
||||
{
|
||||
"support"
|
||||
} else {
|
||||
"unsupport"
|
||||
};
|
||||
let is_sparse_support = if queue_family
|
||||
.queue_flags
|
||||
.contains(vk::QueueFlags::SPARSE_BINDING)
|
||||
{
|
||||
"support"
|
||||
} else {
|
||||
"unsupport"
|
||||
};
|
||||
|
||||
println!(
|
||||
"\t\t{}\t | {}, {}, {}, {}",
|
||||
queue_family.queue_count,
|
||||
is_graphics_support,
|
||||
is_compute_support,
|
||||
is_transfer_support,
|
||||
is_sparse_support
|
||||
);
|
||||
}
|
||||
|
||||
// there are plenty of features
|
||||
println!(
|
||||
"\tGeometry Shader support: {}",
|
||||
if device_features.geometry_shader == 1 {
|
||||
"support"
|
||||
} else {
|
||||
"unsupported"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let indices = find_queue_family(instance, physical_device);
|
||||
|
||||
indices.is_complete()
|
||||
}
|
||||
|
||||
pub fn find_queue_family(
|
||||
instance: &ash::Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> QueueFamilyIndices {
|
||||
let queue_families =
|
||||
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||
|
||||
let mut queue_family_indices = QueueFamilyIndices {
|
||||
graphics_family: None,
|
||||
};
|
||||
|
||||
let mut index = 0;
|
||||
for queue_family in queue_families.iter() {
|
||||
if queue_family.queue_count > 0
|
||||
&& queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
|
||||
{
|
||||
queue_family_indices.graphics_family = Some(index);
|
||||
}
|
||||
|
||||
if queue_family_indices.is_complete() {
|
||||
break;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
queue_family_indices
|
||||
}
|
42
librashader-cli/src/render/vk/util.rs
Normal file
42
librashader-cli/src/render/vk/util.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use ash::vk;
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn vulkan_image_layout_transition_levels(
|
||||
device: &ash::Device,
|
||||
cmd: vk::CommandBuffer,
|
||||
image: vk::Image,
|
||||
levels: u32,
|
||||
old_layout: vk::ImageLayout,
|
||||
new_layout: vk::ImageLayout,
|
||||
src_access: vk::AccessFlags,
|
||||
dst_access: vk::AccessFlags,
|
||||
src_stage: vk::PipelineStageFlags,
|
||||
dst_stage: vk::PipelineStageFlags,
|
||||
|
||||
src_queue_family_index: u32,
|
||||
dst_queue_family_index: u32,
|
||||
) {
|
||||
let mut barrier = vk::ImageMemoryBarrier::default();
|
||||
barrier.s_type = vk::StructureType::IMAGE_MEMORY_BARRIER;
|
||||
barrier.p_next = std::ptr::null();
|
||||
barrier.src_access_mask = src_access;
|
||||
barrier.dst_access_mask = dst_access;
|
||||
barrier.old_layout = old_layout;
|
||||
barrier.new_layout = new_layout;
|
||||
barrier.src_queue_family_index = src_queue_family_index;
|
||||
barrier.dst_queue_family_index = dst_queue_family_index;
|
||||
barrier.image = image;
|
||||
barrier.subresource_range.aspect_mask = vk::ImageAspectFlags::COLOR;
|
||||
barrier.subresource_range.base_array_layer = 0;
|
||||
barrier.subresource_range.level_count = levels;
|
||||
barrier.subresource_range.layer_count = vk::REMAINING_ARRAY_LAYERS;
|
||||
device.cmd_pipeline_barrier(
|
||||
cmd,
|
||||
src_stage,
|
||||
dst_stage,
|
||||
vk::DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[barrier],
|
||||
)
|
||||
}
|
268
librashader-cli/src/render/wgpu.rs
Normal file
268
librashader-cli/src/render/wgpu.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use crate::render::{CommonFrameOptions, RenderTest};
|
||||
use anyhow::anyhow;
|
||||
use image::RgbaImage;
|
||||
use librashader::runtime::wgpu::*;
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
use librashader_runtime::image::{Image, UVDirection};
|
||||
use std::io::{Cursor, Write};
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use wgpu::{Adapter, Device, Instance, Queue, Texture};
|
||||
use wgpu_types::{
|
||||
BufferAddress, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ImageCopyBuffer,
|
||||
ImageDataLayout, Maintain, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
||||
};
|
||||
|
||||
use librashader::presets::ShaderPreset;
|
||||
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub struct Wgpu {
|
||||
_instance: Instance,
|
||||
_adapter: Adapter,
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Queue>,
|
||||
image: Image,
|
||||
texture: Arc<Texture>,
|
||||
}
|
||||
|
||||
struct BufferDimensions {
|
||||
height: usize,
|
||||
unpadded_bytes_per_row: usize,
|
||||
padded_bytes_per_row: usize,
|
||||
}
|
||||
|
||||
impl BufferDimensions {
|
||||
fn new(width: usize, height: usize) -> Self {
|
||||
let bytes_per_pixel = std::mem::size_of::<u32>();
|
||||
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
||||
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
||||
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
|
||||
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
|
||||
Self {
|
||||
height,
|
||||
unpadded_bytes_per_row,
|
||||
padded_bytes_per_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTest for Wgpu {
|
||||
fn new(path: &Path) -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Wgpu::new(path)
|
||||
}
|
||||
|
||||
fn image_size(&self) -> Size<u32> {
|
||||
self.image.size
|
||||
}
|
||||
|
||||
fn render_with_preset_and_params(
|
||||
&mut self,
|
||||
preset: ShaderPreset,
|
||||
frame_count: usize,
|
||||
output_size: Option<Size<u32>>,
|
||||
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
|
||||
frame_options: Option<CommonFrameOptions>,
|
||||
) -> anyhow::Result<image::RgbaImage> {
|
||||
let mut chain = FilterChain::load_from_preset(
|
||||
preset,
|
||||
Arc::clone(&self.device),
|
||||
Arc::clone(&self.queue),
|
||||
Some(&FilterChainOptions {
|
||||
force_no_mipmaps: false,
|
||||
enable_cache: true,
|
||||
adapter_info: None,
|
||||
}),
|
||||
)?;
|
||||
if let Some(setter) = param_setter {
|
||||
setter(&chain.parameters());
|
||||
}
|
||||
let mut cmd = self
|
||||
.device
|
||||
.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
||||
|
||||
let output_tex = self.device.create_texture(&TextureDescriptor {
|
||||
label: None,
|
||||
size: output_size.map_or(self.texture.size(), |size| size.into()),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
|
||||
});
|
||||
|
||||
let buffer_dimensions =
|
||||
BufferDimensions::new(output_tex.width() as usize, output_tex.height() as usize);
|
||||
let output_buf = Arc::new(self.device.create_buffer(&BufferDescriptor {
|
||||
label: None,
|
||||
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height)
|
||||
as BufferAddress, // 4bpp
|
||||
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
}));
|
||||
|
||||
let view = output_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let output = WgpuOutputView::new_from_raw(
|
||||
&view,
|
||||
output_tex.size().into(),
|
||||
TextureFormat::Rgba8Unorm,
|
||||
);
|
||||
|
||||
let viewport = Viewport::new_render_target_sized_origin(output, None)?;
|
||||
let options = frame_options.map(|options| FrameOptions {
|
||||
clear_history: options.clear_history,
|
||||
frame_direction: options.frame_direction,
|
||||
rotation: options.rotation,
|
||||
total_subframes: options.total_subframes,
|
||||
current_subframe: options.current_subframe,
|
||||
});
|
||||
|
||||
for frame in 0..=frame_count {
|
||||
chain.frame(
|
||||
Arc::clone(&self.texture),
|
||||
&viewport,
|
||||
&mut cmd,
|
||||
frame,
|
||||
options.as_ref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
cmd.copy_texture_to_buffer(
|
||||
output_tex.as_image_copy(),
|
||||
ImageCopyBuffer {
|
||||
buffer: &output_buf,
|
||||
layout: ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
output_tex.size(),
|
||||
);
|
||||
|
||||
let si = self.queue.submit([cmd.finish()]);
|
||||
self.device.poll(Maintain::WaitForSubmissionIndex(si));
|
||||
|
||||
let capturable = Arc::clone(&output_buf);
|
||||
|
||||
let pixels = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let pixels_async = Arc::clone(&pixels);
|
||||
output_buf
|
||||
.slice(..)
|
||||
.map_async(wgpu::MapMode::Read, move |r| {
|
||||
if r.is_ok() {
|
||||
let buffer = capturable.slice(..).get_mapped_range();
|
||||
let mut pixels = pixels_async.lock();
|
||||
pixels.resize(buffer.len(), 0);
|
||||
|
||||
let mut cursor = Cursor::new(pixels.deref_mut());
|
||||
for chunk in buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
|
||||
cursor
|
||||
.write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row])
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
cursor.into_inner();
|
||||
}
|
||||
capturable.unmap();
|
||||
});
|
||||
|
||||
self.device.poll(Maintain::Wait);
|
||||
|
||||
if pixels.lock().len() == 0 {
|
||||
return Err(anyhow!("failed to copy pixels from buffer"));
|
||||
}
|
||||
|
||||
let image = RgbaImage::from_raw(
|
||||
output_tex.width(),
|
||||
output_tex.height(),
|
||||
pixels.lock().to_vec(),
|
||||
)
|
||||
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wgpu {
|
||||
pub fn new(image: &Path) -> anyhow::Result<Self> {
|
||||
pollster::block_on(async {
|
||||
let instance = wgpu::Instance::default();
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: None,
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.ok_or(anyhow!("Couldn't request WGPU adapter"))?;
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
|
||||
| wgpu::Features::PIPELINE_CACHE
|
||||
| wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
|
||||
| wgpu::Features::FLOAT32_FILTERABLE,
|
||||
required_limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
memory_hints: Default::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let (image, texture) = Self::load_image(&device, &queue, image)?;
|
||||
|
||||
Ok(Self {
|
||||
_instance: instance,
|
||||
_adapter: adapter,
|
||||
device: Arc::new(device),
|
||||
queue: Arc::new(queue),
|
||||
image: image,
|
||||
texture: Arc::new(texture),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn load_image(device: &Device, queue: &Queue, path: &Path) -> anyhow::Result<(Image, Texture)> {
|
||||
let image = Image::load(path, UVDirection::TopLeft)?;
|
||||
let texture = device.create_texture(&TextureDescriptor {
|
||||
size: image.size.into(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
|
||||
label: None,
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&image.bytes,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * image.size.width),
|
||||
rows_per_image: None,
|
||||
},
|
||||
image.size.into(),
|
||||
);
|
||||
|
||||
let si = queue.submit([]);
|
||||
|
||||
device.poll(Maintain::WaitForSubmissionIndex(si));
|
||||
|
||||
Ok((image, texture))
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ name = "librashader-common"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -13,25 +13,47 @@ description = "RetroArch shaders for all."
|
|||
|
||||
[features]
|
||||
default = []
|
||||
opengl = ["gl"]
|
||||
opengl = ["glow"]
|
||||
d3d9 = ["windows"]
|
||||
d3d11 = ["windows", "dxgi"]
|
||||
d3d12 = ["windows", "dxgi"]
|
||||
dxgi = ["windows"]
|
||||
vulkan = ["ash"]
|
||||
|
||||
wgpu = ["wgpu-types"]
|
||||
metal = ["objc2", "objc2-metal"]
|
||||
serde = ["dep:serde", "serde/derive", "smartstring/serde", "halfbrown/serde"]
|
||||
[dependencies]
|
||||
gl = { version = "0.14.0", optional = true }
|
||||
ash = { version = "0.37.1+1.3.235", optional = true }
|
||||
|
||||
num-traits = "0.2.15"
|
||||
rustc-hash = "2.0.0"
|
||||
halfbrown = "0.2.4"
|
||||
smartstring = "1.0"
|
||||
|
||||
glow = { workspace = true, optional = true }
|
||||
ash = { workspace = true, optional = true }
|
||||
wgpu-types = { workspace = true, optional = true }
|
||||
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
[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]
|
||||
optional = true
|
||||
workspace = true
|
||||
features = ["apple"]
|
||||
|
||||
|
||||
[target.'cfg(target_vendor="apple")'.dependencies.objc2-metal]
|
||||
optional = true
|
||||
workspace = true
|
||||
features = ["MTLPixelFormat", "MTLRenderCommandEncoder", "MTLSampler"]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::{FilterMode, WrapMode};
|
||||
use crate::{FilterMode, GetSize, Size, WrapMode};
|
||||
use windows::Win32::Foundation::E_NOINTERFACE;
|
||||
use windows::Win32::Graphics::Direct3D11;
|
||||
use windows::Win32::Graphics::Direct3D11::{ID3D11Texture2D, D3D11_RESOURCE_DIMENSION_TEXTURE2D};
|
||||
|
||||
impl From<WrapMode> for Direct3D11::D3D11_TEXTURE_ADDRESS_MODE {
|
||||
fn from(value: WrapMode) -> Self {
|
||||
|
@ -20,3 +22,82 @@ impl From<FilterMode> for Direct3D11::D3D11_FILTER {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for &Direct3D11::ID3D11RenderTargetView {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let parent: ID3D11Texture2D = unsafe {
|
||||
let resource = self.GetResource()?;
|
||||
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
|
||||
return Err(windows::core::Error::new(
|
||||
E_NOINTERFACE,
|
||||
"expected ID3D11Texture2D as the resource for the view.",
|
||||
));
|
||||
}
|
||||
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
|
||||
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
|
||||
// inheritance chain.
|
||||
//
|
||||
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
|
||||
std::mem::transmute(resource)
|
||||
};
|
||||
|
||||
let mut desc = Default::default();
|
||||
unsafe {
|
||||
parent.GetDesc(&mut desc);
|
||||
}
|
||||
|
||||
Ok(Size {
|
||||
height: desc.Height,
|
||||
width: desc.Width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for Direct3D11::ID3D11RenderTargetView {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
<&Self as GetSize<u32>>::size(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for &Direct3D11::ID3D11ShaderResourceView {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let parent: ID3D11Texture2D = unsafe {
|
||||
let resource = self.GetResource()?;
|
||||
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
|
||||
return Err(windows::core::Error::new(
|
||||
E_NOINTERFACE,
|
||||
"expected ID3D11Texture2D as the resource for the view.",
|
||||
));
|
||||
}
|
||||
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
|
||||
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
|
||||
// inheritance chain.
|
||||
//
|
||||
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
|
||||
std::mem::transmute(resource)
|
||||
};
|
||||
let mut desc = Default::default();
|
||||
unsafe {
|
||||
parent.GetDesc(&mut desc);
|
||||
}
|
||||
|
||||
Ok(Size {
|
||||
height: desc.Height,
|
||||
width: desc.Width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for Direct3D11::ID3D11ShaderResourceView {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
<&Self as GetSize<u32>>::size(&self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{FilterMode, WrapMode};
|
||||
use crate::{FilterMode, GetSize, Size, WrapMode};
|
||||
use windows::Win32::Graphics::Direct3D12;
|
||||
|
||||
impl From<WrapMode> for Direct3D12::D3D12_TEXTURE_ADDRESS_MODE {
|
||||
|
@ -20,3 +20,12 @@ impl From<FilterMode> for Direct3D12::D3D12_FILTER {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for Direct3D12::ID3D12Resource {
|
||||
type Error = std::convert::Infallible;
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let desc = unsafe { self.GetDesc() };
|
||||
|
||||
Ok(Size::new(desc.Width as u32, desc.Height))
|
||||
}
|
||||
}
|
||||
|
|
123
librashader-common/src/d3d9.rs
Normal file
123
librashader-common/src/d3d9.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use crate::{FilterMode, GetSize, ImageFormat, Size, 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 GetSize<u32> for &Direct3D9::IDirect3DSurface9 {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let mut desc = Default::default();
|
||||
unsafe {
|
||||
self.GetDesc(&mut desc)?;
|
||||
}
|
||||
|
||||
Ok(Size {
|
||||
height: desc.Height,
|
||||
width: desc.Width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for Direct3D9::IDirect3DSurface9 {
|
||||
type Error = windows::core::Error;
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
<&Self as GetSize<u32>>::size(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for &Direct3D9::IDirect3DTexture9 {
|
||||
type Error = windows::core::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let mut desc = Default::default();
|
||||
unsafe {
|
||||
self.GetLevelDesc(0, &mut desc)?;
|
||||
}
|
||||
|
||||
Ok(Size {
|
||||
height: desc.Height,
|
||||
width: desc.Width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for Direct3D9::IDirect3DTexture9 {
|
||||
type Error = windows::core::Error;
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
<&Self as GetSize<u32>>::size(&self)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -1,71 +1,71 @@
|
|||
use crate::{FilterMode, ImageFormat, WrapMode};
|
||||
|
||||
impl From<ImageFormat> for gl::types::GLenum {
|
||||
impl From<ImageFormat> for u32 {
|
||||
fn from(format: ImageFormat) -> Self {
|
||||
match format {
|
||||
ImageFormat::Unknown => 0 as gl::types::GLenum,
|
||||
ImageFormat::R8Unorm => gl::R8,
|
||||
ImageFormat::R8Uint => gl::R8UI,
|
||||
ImageFormat::R8Sint => gl::R8I,
|
||||
ImageFormat::R8G8Unorm => gl::RG8,
|
||||
ImageFormat::R8G8Uint => gl::RG8UI,
|
||||
ImageFormat::R8G8Sint => gl::RG8I,
|
||||
ImageFormat::R8G8B8A8Unorm => gl::RGBA8,
|
||||
ImageFormat::R8G8B8A8Uint => gl::RGBA8UI,
|
||||
ImageFormat::R8G8B8A8Sint => gl::RGBA8I,
|
||||
ImageFormat::R8G8B8A8Srgb => gl::SRGB8_ALPHA8,
|
||||
ImageFormat::A2B10G10R10UnormPack32 => gl::RGB10_A2,
|
||||
ImageFormat::A2B10G10R10UintPack32 => gl::RGB10_A2UI,
|
||||
ImageFormat::R16Uint => gl::R16UI,
|
||||
ImageFormat::R16Sint => gl::R16I,
|
||||
ImageFormat::R16Sfloat => gl::R16F,
|
||||
ImageFormat::R16G16Uint => gl::RG16UI,
|
||||
ImageFormat::R16G16Sint => gl::RG16I,
|
||||
ImageFormat::R16G16Sfloat => gl::RG16F,
|
||||
ImageFormat::R16G16B16A16Uint => gl::RGBA16UI,
|
||||
ImageFormat::R16G16B16A16Sint => gl::RGBA16I,
|
||||
ImageFormat::R16G16B16A16Sfloat => gl::RGBA16F,
|
||||
ImageFormat::R32Uint => gl::R32UI,
|
||||
ImageFormat::R32Sint => gl::R32I,
|
||||
ImageFormat::R32Sfloat => gl::R32F,
|
||||
ImageFormat::R32G32Uint => gl::RG32UI,
|
||||
ImageFormat::R32G32Sint => gl::RG32I,
|
||||
ImageFormat::R32G32Sfloat => gl::RG32F,
|
||||
ImageFormat::R32G32B32A32Uint => gl::RGBA32UI,
|
||||
ImageFormat::R32G32B32A32Sint => gl::RGBA32I,
|
||||
ImageFormat::R32G32B32A32Sfloat => gl::RGBA32F,
|
||||
ImageFormat::Unknown => 0,
|
||||
ImageFormat::R8Unorm => glow::R8,
|
||||
ImageFormat::R8Uint => glow::R8UI,
|
||||
ImageFormat::R8Sint => glow::R8I,
|
||||
ImageFormat::R8G8Unorm => glow::RG8,
|
||||
ImageFormat::R8G8Uint => glow::RG8UI,
|
||||
ImageFormat::R8G8Sint => glow::RG8I,
|
||||
ImageFormat::R8G8B8A8Unorm => glow::RGBA8,
|
||||
ImageFormat::R8G8B8A8Uint => glow::RGBA8UI,
|
||||
ImageFormat::R8G8B8A8Sint => glow::RGBA8I,
|
||||
ImageFormat::R8G8B8A8Srgb => glow::SRGB8_ALPHA8,
|
||||
ImageFormat::A2B10G10R10UnormPack32 => glow::RGB10_A2,
|
||||
ImageFormat::A2B10G10R10UintPack32 => glow::RGB10_A2UI,
|
||||
ImageFormat::R16Uint => glow::R16UI,
|
||||
ImageFormat::R16Sint => glow::R16I,
|
||||
ImageFormat::R16Sfloat => glow::R16F,
|
||||
ImageFormat::R16G16Uint => glow::RG16UI,
|
||||
ImageFormat::R16G16Sint => glow::RG16I,
|
||||
ImageFormat::R16G16Sfloat => glow::RG16F,
|
||||
ImageFormat::R16G16B16A16Uint => glow::RGBA16UI,
|
||||
ImageFormat::R16G16B16A16Sint => glow::RGBA16I,
|
||||
ImageFormat::R16G16B16A16Sfloat => glow::RGBA16F,
|
||||
ImageFormat::R32Uint => glow::R32UI,
|
||||
ImageFormat::R32Sint => glow::R32I,
|
||||
ImageFormat::R32Sfloat => glow::R32F,
|
||||
ImageFormat::R32G32Uint => glow::RG32UI,
|
||||
ImageFormat::R32G32Sint => glow::RG32I,
|
||||
ImageFormat::R32G32Sfloat => glow::RG32F,
|
||||
ImageFormat::R32G32B32A32Uint => glow::RGBA32UI,
|
||||
ImageFormat::R32G32B32A32Sint => glow::RGBA32I,
|
||||
ImageFormat::R32G32B32A32Sfloat => glow::RGBA32F,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WrapMode> for gl::types::GLenum {
|
||||
impl From<WrapMode> for i32 {
|
||||
fn from(value: WrapMode) -> Self {
|
||||
match value {
|
||||
WrapMode::ClampToBorder => gl::CLAMP_TO_BORDER,
|
||||
WrapMode::ClampToEdge => gl::CLAMP_TO_EDGE,
|
||||
WrapMode::Repeat => gl::REPEAT,
|
||||
WrapMode::MirroredRepeat => gl::MIRRORED_REPEAT,
|
||||
WrapMode::ClampToBorder => glow::CLAMP_TO_BORDER as i32,
|
||||
WrapMode::ClampToEdge => glow::CLAMP_TO_EDGE as i32,
|
||||
WrapMode::Repeat => glow::REPEAT as i32,
|
||||
WrapMode::MirroredRepeat => glow::MIRRORED_REPEAT as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilterMode> for gl::types::GLenum {
|
||||
impl From<FilterMode> for i32 {
|
||||
fn from(value: FilterMode) -> Self {
|
||||
match value {
|
||||
FilterMode::Linear => gl::LINEAR,
|
||||
_ => gl::NEAREST,
|
||||
FilterMode::Linear => glow::LINEAR as i32,
|
||||
_ => glow::NEAREST as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterMode {
|
||||
/// Get the mipmap filtering mode for the given combination.
|
||||
pub fn gl_mip(&self, mip: FilterMode) -> gl::types::GLenum {
|
||||
pub fn gl_mip(&self, mip: FilterMode) -> u32 {
|
||||
match (self, mip) {
|
||||
(FilterMode::Linear, FilterMode::Linear) => gl::LINEAR_MIPMAP_LINEAR,
|
||||
(FilterMode::Linear, FilterMode::Nearest) => gl::LINEAR_MIPMAP_NEAREST,
|
||||
(FilterMode::Nearest, FilterMode::Linear) => gl::NEAREST_MIPMAP_LINEAR,
|
||||
_ => gl::NEAREST_MIPMAP_NEAREST,
|
||||
(FilterMode::Linear, FilterMode::Linear) => glow::LINEAR_MIPMAP_LINEAR,
|
||||
(FilterMode::Linear, FilterMode::Nearest) => glow::LINEAR_MIPMAP_NEAREST,
|
||||
(FilterMode::Nearest, FilterMode::Linear) => glow::NEAREST_MIPMAP_LINEAR,
|
||||
_ => glow::NEAREST_MIPMAP_NEAREST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,15 +28,32 @@ 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 num_traits::{AsPrimitive, Num};
|
||||
use std::convert::Infallible;
|
||||
use std::ops::{Add, Sub};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum StorageType {
|
||||
/// The fully qualified path to the resource, often a shader source file or a texture.
|
||||
Path(std::path::PathBuf),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Supported image formats for textures.
|
||||
pub enum ImageFormat {
|
||||
#[default]
|
||||
|
@ -75,6 +100,7 @@ pub enum ImageFormat {
|
|||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// The filtering mode for a texture sampler.
|
||||
pub enum FilterMode {
|
||||
/// Linear filtering.
|
||||
|
@ -113,6 +139,7 @@ impl FromStr for FilterMode {
|
|||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// The wrapping (address) mode for a texture sampler.
|
||||
pub enum WrapMode {
|
||||
#[default]
|
||||
|
@ -173,6 +200,7 @@ impl FromStr for ImageFormat {
|
|||
|
||||
/// A size with a width and height.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Size<T> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
|
@ -185,6 +213,50 @@ impl<T> Size<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Sub<Output = T>> Sub for Size<T> {
|
||||
type Output = Size<T>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
width: self.width - rhs.width,
|
||||
height: self.height - rhs.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sub<T, Output = T> + Copy> Sub<T> for Size<T> {
|
||||
type Output = Size<T>;
|
||||
|
||||
fn sub(self, rhs: T) -> Self::Output {
|
||||
Self {
|
||||
width: self.width - rhs,
|
||||
height: self.height - rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Add<Output = T>> Add for Size<T> {
|
||||
type Output = Size<T>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
width: self.width + rhs.width,
|
||||
height: self.height + rhs.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Add<T, Output = T> + Copy> Add<T> for Size<T> {
|
||||
type Output = Size<T>;
|
||||
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
Self {
|
||||
width: self.width + rhs,
|
||||
height: self.height + rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Size<T>> for [f32; 4]
|
||||
where
|
||||
T: Copy + AsPrimitive<f32>,
|
||||
|
@ -199,3 +271,22 @@ where
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for surface or texture objects that can fetch size.
|
||||
pub trait GetSize<C: Num> {
|
||||
type Error;
|
||||
/// Fetch the size of the object
|
||||
fn size(&self) -> Result<Size<C>, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T: GetSize<u32>> GetSize<f32> for T {
|
||||
type Error = T::Error;
|
||||
|
||||
fn size(&self) -> Result<Size<f32>, Self::Error> {
|
||||
let size = <T as GetSize<u32>>::size(self)?;
|
||||
Ok(Size {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
8
librashader-common/src/map.rs
Normal file
8
librashader-common/src/map.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
/// A hashmap optimized for small sets of size less than 32 with a fast hash implementation.
|
||||
///
|
||||
/// Used widely for shader reflection.
|
||||
pub type FastHashMap<K, V> =
|
||||
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
|
||||
|
||||
/// A string with small string optimizations up to 23 bytes.
|
||||
pub type ShortString = smartstring::SmartString<smartstring::LazyCompact>;
|
122
librashader-common/src/metal.rs
Normal file
122
librashader-common/src/metal.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2_metal::{
|
||||
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLTexture,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for ProtocolObject<dyn MTLTexture> {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let height = self.height();
|
||||
let width = self.width();
|
||||
Ok(Size {
|
||||
height: height as u32,
|
||||
width: width as u32,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSize<u32> for &ProtocolObject<dyn MTLTexture> {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn size(&self) -> Result<Size<u32>, Self::Error> {
|
||||
let height = self.height();
|
||||
let width = self.width();
|
||||
Ok(Size {
|
||||
height: height as u32,
|
||||
width: width as u32,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
use crate::{GetSize, Size};
|
||||
|
||||
/// The rendering output of a filter chain.
|
||||
///
|
||||
/// Viewport coordinates are relative to the coordinate system of the
|
||||
/// target runtime. For correct results, `x` and `y` should almost always be
|
||||
/// 0, and `size` should be the same as the size of the output texture.
|
||||
///
|
||||
/// Size uniforms will always be passed the full size of the output texture,
|
||||
/// regardless of the user-specified viewport size.
|
||||
pub struct Viewport<'a, T> {
|
||||
/// The x offset to start rendering from.
|
||||
/// The x offset to start rendering from. For correct results, this should almost
|
||||
/// always be 0 to indicate the origin.
|
||||
pub x: f32,
|
||||
/// The y offset to begin rendering from.
|
||||
pub y: f32,
|
||||
|
@ -9,4 +19,28 @@ pub struct Viewport<'a, T> {
|
|||
pub mvp: Option<&'a [f32; 16]>,
|
||||
/// The output handle to render the final image to.
|
||||
pub output: T,
|
||||
/// The extent of the viewport size starting from the origin defined
|
||||
/// by x and y.
|
||||
pub size: Size<u32>,
|
||||
}
|
||||
|
||||
impl<'a, T: GetSize<u32>> Viewport<'a, T> {
|
||||
/// Create a new viewport from an output that can be sized.
|
||||
///
|
||||
/// This will create a viewport that spans the entire output texture,
|
||||
/// which will give correct results in the general case.
|
||||
#[inline(always)]
|
||||
pub fn new_render_target_sized_origin(
|
||||
output: T,
|
||||
mvp: Option<&'a [f32; 16]>,
|
||||
) -> Result<Viewport<'a, T>, T::Error> {
|
||||
let size = output.size()?;
|
||||
Ok(Self {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
mvp,
|
||||
output,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
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,
|
||||
}
|
||||
}
|
||||
}
|
41
librashader-pack/Cargo.toml
Normal file
41
librashader-pack/Cargo.toml
Normal file
|
@ -0,0 +1,41 @@
|
|||
[package]
|
||||
name = "librashader-pack"
|
||||
version = "0.5.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
categories = ["emulators", "compilers", "graphics"]
|
||||
keywords = ["shader", "retroarch", "SPIR-V"]
|
||||
description = "RetroArch shaders for all."
|
||||
|
||||
[dependencies]
|
||||
librashader-presets = { path = "../librashader-presets", version = "0.5.1", features = [
|
||||
"serde",
|
||||
] }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1", features = [
|
||||
"serde",
|
||||
] }
|
||||
librashader-common = { path = "../librashader-common", version = "0.5.1", features = [
|
||||
"serde",
|
||||
] }
|
||||
|
||||
thiserror = "1.0.64"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
rayon = { workspace = true }
|
||||
image = { workspace = true }
|
||||
base64 = { version = "0.22.1", optional = true }
|
||||
serde_bytes = { version = "0.11.15", optional = true }
|
||||
|
||||
[features]
|
||||
parse_legacy_glsl = ["librashader-presets/parse_legacy_glsl"]
|
||||
serde = ["dep:serde", "dep:base64", "dep:serde_bytes"]
|
||||
|
||||
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
|
||||
rayon = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.89"
|
||||
serde_json = "1.0.128"
|
||||
rmp-serde = "1.3.0"
|
254
librashader-pack/src/lib.rs
Normal file
254
librashader-pack/src/lib.rs
Normal file
|
@ -0,0 +1,254 @@
|
|||
//! Shader preset resource handling for librashader.
|
||||
//!
|
||||
//! This crate contains facilities to load shader preset resources from a [`ShaderPreset`].
|
||||
//!
|
||||
//! Also defines abstractly the `.slangpack` shader format implemented via serde derives on [`ShaderPresetPack`].
|
||||
//!
|
||||
use image::{ImageError, RgbaImage};
|
||||
use librashader_common::StorageType;
|
||||
use librashader_preprocess::{PreprocessError, ShaderSource};
|
||||
use librashader_presets::{ParameterMeta, PassMeta, ShaderPreset, TextureMeta};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// A buffer holding RGBA image bytes.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TextureBuffer {
|
||||
#[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))]
|
||||
image: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl From<TextureBuffer> for Option<RgbaImage> {
|
||||
fn from(value: TextureBuffer) -> Self {
|
||||
RgbaImage::from_raw(value.width, value.height, value.image)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for TextureBuffer {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.image.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RgbaImage> for TextureBuffer {
|
||||
fn from(value: RgbaImage) -> Self {
|
||||
let width = value.width();
|
||||
let height = value.height();
|
||||
TextureBuffer {
|
||||
image: value.into_raw(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A resource for a shader preset, fully loaded into memory.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct LoadedResource<M: LoadableResource> {
|
||||
/// The fully qualified path to the texture.
|
||||
pub data: M::ResourceType,
|
||||
/// Meta information about the texture.
|
||||
pub meta: M,
|
||||
}
|
||||
|
||||
/// Trait for a resource that is loadable from disk.
|
||||
pub trait LoadableResource {
|
||||
/// The type of the resource.
|
||||
type ResourceType;
|
||||
/// The error type when loading the resource.
|
||||
type Error;
|
||||
/// Load the resource from the path.
|
||||
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error>;
|
||||
}
|
||||
|
||||
impl LoadableResource for PassMeta {
|
||||
type ResourceType = ShaderSource;
|
||||
type Error = PreprocessError;
|
||||
|
||||
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
|
||||
ShaderSource::load(storage)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoadableResource for TextureMeta {
|
||||
type ResourceType = TextureBuffer;
|
||||
type Error = ImageError;
|
||||
|
||||
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
|
||||
let image = match storage {
|
||||
StorageType::Path(path_buf) => image::open(path_buf),
|
||||
StorageType::String(_) => todo!(),
|
||||
};
|
||||
image.map(|img| TextureBuffer::from(img.to_rgba8()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The loaded resource information for the source code of a shader pass.
|
||||
pub type PassResource = LoadedResource<PassMeta>;
|
||||
|
||||
/// The loaded texture resource for a shader preset.
|
||||
pub type TextureResource = LoadedResource<TextureMeta>;
|
||||
|
||||
/// A fully loaded-in-memory shader preset, with all paths resolved to data.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShaderPresetPack {
|
||||
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
|
||||
/// Otherwise, the FBO after pass #N is passed a texture to next frame
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
pub feedback_pass: i32,
|
||||
|
||||
/// The number of shaders enabled in the filter chain.
|
||||
pub pass_count: i32,
|
||||
// Everything is in Vecs because the expect number of values is well below 64.
|
||||
/// Preset information for each shader.
|
||||
pub passes: Vec<PassResource>,
|
||||
|
||||
/// Preset information for each texture.
|
||||
pub textures: Vec<TextureResource>,
|
||||
|
||||
/// Preset information for each user parameter.
|
||||
pub parameters: Vec<ParameterMeta>,
|
||||
}
|
||||
|
||||
impl ShaderPresetPack {
|
||||
/// Load a `ShaderPack` from a [`ShaderPreset`].
|
||||
pub fn load_from_preset<E>(preset: ShaderPreset) -> Result<ShaderPresetPack, E>
|
||||
where
|
||||
E: From<PreprocessError>,
|
||||
E: From<ImageError>,
|
||||
E: Send,
|
||||
{
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let shaders_iter = preset.passes.into_par_iter();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let shaders_iter = preset.shaders.into_iter();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let textures_iter = preset.textures.into_par_iter();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let textures_iter = preset.textures.into_iter();
|
||||
|
||||
Ok(ShaderPresetPack {
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
feedback_pass: preset.feedback_pass,
|
||||
|
||||
pass_count: preset.pass_count,
|
||||
passes: shaders_iter
|
||||
.map(|v| {
|
||||
Ok::<_, E>(PassResource {
|
||||
data: PassMeta::load(&v.storage)?,
|
||||
meta: v.meta,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
textures: textures_iter
|
||||
.into_par_iter()
|
||||
.map(|t| {
|
||||
Ok::<_, E>(TextureResource {
|
||||
data: TextureMeta::load(&t.storage)?,
|
||||
meta: t.meta,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
parameters: preset.parameters,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde_base64_or_bytes {
|
||||
use base64::display::Base64Display;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
|
||||
if s.is_human_readable() {
|
||||
s.collect_str(&Base64Display::new(v, &STANDARD))
|
||||
} else {
|
||||
serde_bytes::serialize(v, s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
||||
if d.is_human_readable() {
|
||||
struct Base64Visitor;
|
||||
impl<'de> serde::de::Visitor<'de> for Base64Visitor {
|
||||
type Value = Vec<u8>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a base64 encoded string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
self.visit_bytes(v.as_ref())
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
self.visit_bytes(v.as_ref())
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
STANDARD.decode(v).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
self.visit_bytes(v.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
d.deserialize_str(Base64Visitor)
|
||||
} else {
|
||||
serde_bytes::deserialize(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ShaderPresetPack;
|
||||
use librashader_presets::ShaderPreset;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let preset =
|
||||
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
|
||||
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
|
||||
let mut file = File::create("crt-royale.slangpack.json").unwrap();
|
||||
file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rmp() {
|
||||
let preset =
|
||||
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
|
||||
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
|
||||
let mut file = File::create("crt-royale.slangpack").unwrap();
|
||||
file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ name = "librashader-preprocess"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -14,15 +14,15 @@ description = "RetroArch shaders for all."
|
|||
[dependencies]
|
||||
thiserror = "1.0.37"
|
||||
nom = "7.1.1"
|
||||
librashader-common = { path = "../librashader-common", version = "0.1.1" }
|
||||
rustc-hash = "1.1.0"
|
||||
librashader-common = { path = "../librashader-common", version = "0.5.1" }
|
||||
encoding_rs = "0.8.31"
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = [ "line_directives" ]
|
||||
line_directives = []
|
||||
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
|
||||
|
||||
[dev-dependencies]
|
||||
librashader-presets = "0.1.0-rc.3"
|
||||
glob = "0.3.1"
|
||||
rayon = "1.6.1"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use librashader_common::map::ShortString;
|
||||
use std::convert::Infallible;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
@ -27,7 +28,7 @@ pub enum PreprocessError {
|
|||
PragmaParseError(String),
|
||||
/// The given pragma was declared multiple times with differing values.
|
||||
#[error("duplicate pragma found")]
|
||||
DuplicatePragmaError(String),
|
||||
DuplicatePragmaError(ShortString),
|
||||
/// The image format requested by the shader was unknown or not supported.
|
||||
#[error("shader format is unknown or not found")]
|
||||
UnknownImageFormat,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
//!
|
||||
//! This crate contains facilities and types for resolving `#include` directives in `.slang`
|
||||
//! into a single compilation unit. `#pragma` directives are also parsed and resolved as
|
||||
//! [`ShaderParameter`](crate::ShaderParameter) structs.
|
||||
//! [`ShaderParameter`] structs.
|
||||
//!
|
||||
//! The resulting [`ShaderSource`](crate::ShaderSource) can then be passed into a
|
||||
//! The resulting [`ShaderSource`]can then be passed into a
|
||||
//! reflection target for reflection and compilation into the target shader format.
|
||||
//!
|
||||
//! Re-exported as [`librashader::preprocess`](https://docs.rs/librashader/latest/librashader/preprocess/index.html).
|
||||
|
@ -15,12 +15,13 @@ mod stage;
|
|||
|
||||
use crate::include::read_source;
|
||||
pub use error::*;
|
||||
use librashader_common::ImageFormat;
|
||||
use rustc_hash::FxHashMap;
|
||||
use librashader_common::map::{FastHashMap, ShortString};
|
||||
use librashader_common::{ImageFormat, StorageType};
|
||||
use std::path::Path;
|
||||
|
||||
/// The source file for a single shader pass.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShaderSource {
|
||||
/// The source contents for the vertex shader.
|
||||
pub vertex: String,
|
||||
|
@ -29,10 +30,10 @@ pub struct ShaderSource {
|
|||
pub fragment: String,
|
||||
|
||||
/// The alias of the shader if available.
|
||||
pub name: Option<String>,
|
||||
pub name: Option<ShortString>,
|
||||
|
||||
/// The list of shader parameters found in the shader source.
|
||||
pub parameters: FxHashMap<String, ShaderParameter>,
|
||||
pub parameters: FastHashMap<ShortString, ShaderParameter>,
|
||||
|
||||
/// The image format the shader expects.
|
||||
pub format: ImageFormat,
|
||||
|
@ -40,9 +41,10 @@ pub struct ShaderSource {
|
|||
|
||||
/// A user tweakable parameter for the shader as declared in source.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShaderParameter {
|
||||
/// The name of the parameter.
|
||||
pub id: String,
|
||||
pub id: ShortString,
|
||||
/// The description of the parameter.
|
||||
pub description: String,
|
||||
/// The initial value the parameter is set to.
|
||||
|
@ -58,8 +60,11 @@ 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(storage: &StorageType) -> Result<ShaderSource, PreprocessError> {
|
||||
match storage {
|
||||
StorageType::Path(path_buf) => load_shader_source(path_buf),
|
||||
StorageType::String(source) => parse_shader_source(source.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,9 +85,14 @@ impl SourceOutput for String {
|
|||
|
||||
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
|
||||
let source = read_source(path)?;
|
||||
parse_shader_source(source)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_shader_source(source: String) -> Result<ShaderSource, PreprocessError> {
|
||||
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,
|
||||
|
|
|
@ -2,7 +2,9 @@ use crate::{PreprocessError, ShaderParameter};
|
|||
use librashader_common::ImageFormat;
|
||||
use nom::bytes::complete::{is_not, tag, take_while};
|
||||
|
||||
use nom::character::complete::multispace1;
|
||||
use librashader_common::map::ShortString;
|
||||
use nom::character::complete::{multispace0, multispace1};
|
||||
use nom::combinator::opt;
|
||||
use nom::number::complete::float;
|
||||
use nom::sequence::delimited;
|
||||
use nom::IResult;
|
||||
|
@ -12,7 +14,7 @@ use std::str::FromStr;
|
|||
pub(crate) struct ShaderMeta {
|
||||
pub(crate) format: ImageFormat,
|
||||
pub(crate) parameters: Vec<ShaderParameter>,
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) name: Option<ShortString>,
|
||||
}
|
||||
|
||||
fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessError> {
|
||||
|
@ -35,17 +37,25 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
|
|||
let (input, minimum) = float(input)?;
|
||||
let (input, _) = multispace1(input)?;
|
||||
let (input, maximum) = float(input)?;
|
||||
let (input, _) = multispace1(input)?;
|
||||
let (input, step) = float(input)?;
|
||||
|
||||
// Step is actually optional and defaults to 0.02
|
||||
// This behaviour can be seen in shaders like
|
||||
// crt/crt-slangtest-cubic.slangp
|
||||
// which doesn't have a step argument
|
||||
// #pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4
|
||||
|
||||
// https://github.com/libretro/slang-shaders/blob/0e2939787076e4a8a83be89175557fde23abe837/crt/shaders/crt-slangtest/parameters.inc#L1
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, step) = opt(float)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
ShaderParameter {
|
||||
id: name.to_string(),
|
||||
id: name.into(),
|
||||
description: description.to_string(),
|
||||
initial,
|
||||
minimum,
|
||||
maximum,
|
||||
step,
|
||||
step: step.unwrap_or(0.02),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -60,7 +70,7 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
|
|||
Ok(param)
|
||||
} else {
|
||||
Ok(ShaderParameter {
|
||||
id: name.to_string(),
|
||||
id: name.into(),
|
||||
description: description.to_string(),
|
||||
initial: 0f32,
|
||||
minimum: 0f32,
|
||||
|
@ -89,7 +99,7 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
|
|||
|
||||
if let Some(format_string) = line.strip_prefix("#pragma format ") {
|
||||
if format != ImageFormat::Unknown {
|
||||
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
|
||||
return Err(PreprocessError::DuplicatePragmaError(line.into()));
|
||||
}
|
||||
|
||||
let format_string = format_string.trim();
|
||||
|
@ -100,12 +110,12 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
|
|||
}
|
||||
}
|
||||
|
||||
if line.starts_with("#pragma name ") {
|
||||
if let Some(pragma_name) = line.strip_prefix("#pragma name ") {
|
||||
if name.is_some() {
|
||||
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
|
||||
return Err(PreprocessError::DuplicatePragmaError(line.into()));
|
||||
}
|
||||
|
||||
name = Some(line.trim().to_string())
|
||||
name = Some(ShortString::from(pragma_name.trim()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +134,7 @@ mod test {
|
|||
#[test]
|
||||
fn parses_parameter_pragma() {
|
||||
assert_eq!(ShaderParameter {
|
||||
id: "exc".to_string(),
|
||||
id: "exc".into(),
|
||||
description: "orizontal correction hack (games where players stay at center)".to_string(),
|
||||
initial: 0.0,
|
||||
minimum: -10.0,
|
||||
|
@ -132,4 +142,34 @@ 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".into(),
|
||||
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())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_parameter_pragma_with_no_step() {
|
||||
assert_eq!(
|
||||
ShaderParameter {
|
||||
id: "OUT_GAMMA".into(),
|
||||
description: "Monitor Output Gamma".to_string(),
|
||||
initial: 2.2,
|
||||
minimum: 1.8,
|
||||
maximum: 2.4,
|
||||
step: 0.02
|
||||
},
|
||||
parse_parameter_string(
|
||||
r#"#pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4"#
|
||||
)
|
||||
.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.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -15,11 +15,18 @@ 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.1" }
|
||||
librashader-common = { path = "../librashader-common", version = "0.5.1" }
|
||||
num-traits = "0.2"
|
||||
once_cell = "1"
|
||||
# we don't need unicode
|
||||
regex = { version = "1", default-features = false, features = ["perf"] }
|
||||
vec_extract_if_polyfill = "0.1.0"
|
||||
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
[features]
|
||||
parse_legacy_glsl = []
|
||||
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
|
||||
|
||||
[dev-dependencies]
|
||||
glob = "0.3.1"
|
||||
|
|
413
librashader-presets/src/context.rs
Normal file
413
librashader-presets/src/context.rs
Normal file
|
@ -0,0 +1,413 @@
|
|||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
/// The wildcard key associated with the context item.
|
||||
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 into_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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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")]
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
//! This crate contains facilities and types for parsing `.slangp` shader presets files.
|
||||
//!
|
||||
//! Shader presets contain shader and texture parameters, and the order in which to apply a set of
|
||||
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`](crate::ShaderPreset)
|
||||
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`]
|
||||
//! as input to create a filter chain.
|
||||
//!
|
||||
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
|
||||
#![feature(drain_filter)]
|
||||
|
||||
pub mod context;
|
||||
mod error;
|
||||
mod parse;
|
||||
mod preset;
|
||||
|
||||
pub use context::WildcardContext;
|
||||
pub use error::*;
|
||||
pub use preset::*;
|
||||
|
|
|
@ -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,41 +1,46 @@
|
|||
use crate::parse::remove_if;
|
||||
use crate::parse::value::Value;
|
||||
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
|
||||
use crate::{
|
||||
ParameterMeta, PassConfig, PassMeta, Scale2D, Scaling, ShaderPreset, TextureConfig, TextureMeta,
|
||||
};
|
||||
use vec_extract_if_polyfill::MakeExtractIf;
|
||||
|
||||
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
||||
let textures: Vec<TextureConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Texture {
|
||||
name,
|
||||
filter_mode,
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
} = value
|
||||
{
|
||||
TextureConfig {
|
||||
let textures: Vec<TextureConfig> =
|
||||
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Texture { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Texture {
|
||||
name,
|
||||
path,
|
||||
wrap_mode,
|
||||
filter_mode,
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
} = value
|
||||
{
|
||||
TextureConfig {
|
||||
storage: librashader_common::StorageType::Path(path),
|
||||
meta: TextureMeta {
|
||||
name,
|
||||
wrap_mode,
|
||||
filter_mode,
|
||||
mipmap,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
unreachable!("values should all be of type Texture")
|
||||
}
|
||||
} else {
|
||||
unreachable!("values should all be of type Texture")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let parameters: Vec<ParameterConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Parameter(name, value) = value {
|
||||
ParameterConfig { name, value }
|
||||
} else {
|
||||
unreachable!("values should be all of type parameters")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
})
|
||||
.collect();
|
||||
let parameters: Vec<ParameterMeta> =
|
||||
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Parameter { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Parameter(name, value) = value {
|
||||
ParameterMeta { name, value }
|
||||
} else {
|
||||
unreachable!("values should be all of type parameters")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut shaders = Vec::new();
|
||||
let shader_count =
|
||||
|
@ -63,9 +68,9 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
&mut values,
|
||||
|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))
|
||||
.collect();
|
||||
let shader_values: Vec<Value> =
|
||||
MakeExtractIf::extract_if(&mut values, |v| v.shader_index() == Some(shader))
|
||||
.collect();
|
||||
let scale_type = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleType(_, value) => Some(*value),
|
||||
_ => None,
|
||||
|
@ -112,64 +117,66 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
scale_y = scale;
|
||||
}
|
||||
|
||||
let shader = ShaderPassConfig {
|
||||
id,
|
||||
name,
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
}),
|
||||
filter: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FilterMode(_, value) => Some(*value),
|
||||
let shader = PassConfig {
|
||||
storage: librashader_common::StorageType::Path(name),
|
||||
meta: PassMeta {
|
||||
id,
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
wrap_mode: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::WrapMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
frame_count_mod: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FrameCountMod(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0),
|
||||
srgb_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::SrgbFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
float_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FloatFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
mipmap_input: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::MipmapInput(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
scaling: Scale2D {
|
||||
valid: scale_valid,
|
||||
x: Scaling {
|
||||
scale_type: scale_type_x.unwrap_or_default(),
|
||||
factor: scale_x.unwrap_or_default(),
|
||||
},
|
||||
y: Scaling {
|
||||
scale_type: scale_type_y.unwrap_or_default(),
|
||||
factor: scale_y.unwrap_or_default(),
|
||||
}),
|
||||
filter: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FilterMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
wrap_mode: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::WrapMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
frame_count_mod: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FrameCountMod(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0),
|
||||
srgb_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::SrgbFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
float_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FloatFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
mipmap_input: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::MipmapInput(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
scaling: Scale2D {
|
||||
valid: scale_valid,
|
||||
x: Scaling {
|
||||
scale_type: scale_type_x.unwrap_or_default(),
|
||||
factor: scale_x.unwrap_or_default(),
|
||||
},
|
||||
y: Scaling {
|
||||
scale_type: scale_type_y.unwrap_or_default(),
|
||||
factor: scale_y.unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -181,8 +188,8 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
ShaderPreset {
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
feedback_pass,
|
||||
shader_count,
|
||||
shaders,
|
||||
pass_count: shader_count,
|
||||
passes: shaders,
|
||||
textures,
|
||||
parameters,
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@ 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,
|
||||
bytes::complete::tag, character::complete::multispace0, AsChar, IResult, InputIter,
|
||||
InputLength, InputTake, Slice,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -52,9 +53,24 @@ fn parse_assignment(input: Span) -> IResult<Span, ()> {
|
|||
Ok((input, ()))
|
||||
}
|
||||
|
||||
fn unbalanced_quote<I>(input: I) -> IResult<I, ()>
|
||||
where
|
||||
I: Slice<RangeFrom<usize>> + InputIter + InputLength,
|
||||
<I as InputIter>::Item: AsChar,
|
||||
I: Copy,
|
||||
{
|
||||
if let Ok((input, _)) = eof::<_, ()>(input) {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
let (input, _) = char('"')(input)?;
|
||||
Ok((input, ()))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_from_quotes(input: Span) -> IResult<Span, Span> {
|
||||
let (input, between) = delimited(char('"'), is_not("\""), char('"'))(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
// Allow unbalanced quotes because some presets just leave an open quote.
|
||||
let (input, between) = delimited(char('"'), is_not("\""), unbalanced_quote)(input)?;
|
||||
let (input, _) = opt_whitespace(input)?;
|
||||
let (input, _) = eof(input)?;
|
||||
Ok((input, between))
|
||||
}
|
||||
|
@ -71,7 +87,7 @@ fn single_comment(i: Span) -> IResult<Span, Span> {
|
|||
)(i)
|
||||
}
|
||||
|
||||
fn whitespace(i: Span) -> IResult<Span, ()> {
|
||||
fn opt_whitespace(i: Span) -> IResult<Span, ()> {
|
||||
value(
|
||||
(), // Output is thrown away.
|
||||
multispace0,
|
||||
|
@ -108,7 +124,7 @@ fn parse_tokens(mut span: Span) -> IResult<Span, Vec<Token>> {
|
|||
let mut values = Vec::new();
|
||||
while !span.is_empty() {
|
||||
// important to munch whitespace first.
|
||||
if let Ok((input, _)) = whitespace(span) {
|
||||
if let Ok((input, _)) = opt_whitespace(span) {
|
||||
span = input;
|
||||
}
|
||||
// handle references before comments because comments can start with #
|
||||
|
@ -155,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() {
|
||||
|
@ -163,549 +179,4 @@ mod test {
|
|||
single_comment("// Define textures to be used by the different passes\ntetx=n".into());
|
||||
eprintln!("{parsed:?}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_key_value_line() {
|
||||
let parsed = do_lex(TEST3);
|
||||
eprintln!("{parsed:#?}")
|
||||
}
|
||||
|
||||
// todo: fix
|
||||
const TEST2: &str = r#"
|
||||
// Color Correction with Dogway's awesome Grade shader
|
||||
// Grade is after Afterglow so that brightening the black level does not break the afterglow
|
||||
shader9 = ../../shaders/dogway/hsm-grade.slang
|
||||
"#;
|
||||
const TEST: &str = r#"
|
||||
#reference "../../alt"
|
||||
shaders = 54
|
||||
|
||||
shader0 = ../../shaders/base/add-params-all.slang
|
||||
alias0 = "CorePass" # hello
|
||||
|
||||
shader1 = ../../shaders/hyllian/cubic/hsm-drez-b-spline-x.slang
|
||||
filter_linear1 = false
|
||||
scale_type_x1 = absolute
|
||||
scale_x1 = 640
|
||||
scale_type_y1 = viewport
|
||||
scaley0 = 1.0
|
||||
wrap_mode1 = "clamp_to_edge"
|
||||
|
||||
shader2 = ../../shaders/hyllian/cubic/hsm-drez-b-spline-y.slang
|
||||
filter_linear2 = false
|
||||
scale_type2 = absolute
|
||||
scale_x2 = 640
|
||||
scale_y2 = 480
|
||||
wrap_mode2 = "clamp_to_edge"
|
||||
alias2 = "DerezedPass"
|
||||
|
||||
shader3 = ../../shaders/base/add-negative-crop-area.slang
|
||||
filter_linear3 = false
|
||||
mipmap_input3 = false
|
||||
srgb_framebuffer3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1
|
||||
scale_y3 = 1
|
||||
alias3 = "NegativeCropAddedPass"
|
||||
|
||||
shader4 = ../../shaders/base/cache-info-all-params.slang
|
||||
filter_linear4 = false
|
||||
scale_type4 = source
|
||||
scale4 = 1.0
|
||||
alias4 = "InfoCachePass"
|
||||
|
||||
shader5 = ../../shaders/base/text-std.slang
|
||||
filter_linear5 = false
|
||||
float_framebuffer5 = true
|
||||
scale_type5 = source
|
||||
scale5 = 1.0
|
||||
alias5 = "TextPass"
|
||||
|
||||
shader6 = ../../shaders/base/intro.slang
|
||||
filter_linear6 = false
|
||||
float_framebuffer6 = true
|
||||
scale_type6 = source
|
||||
scale6 = 1.0
|
||||
alias6 = "IntroPass"
|
||||
|
||||
shader7 = ../../shaders/dedither/dedither-gamma-prep-1-before.slang
|
||||
alias7 = LinearGamma
|
||||
|
||||
shader8 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass1.slang
|
||||
shader9 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass2.slang
|
||||
shader10 = ../../shaders/hyllian/checkerboard-dedither/checkerboard-dedither-pass3.slang
|
||||
alias10 = "PreMdaptPass"
|
||||
|
||||
// De-Dithering - Mdapt
|
||||
shader11 = ../../shaders/mdapt/hsm-mdapt-pass0.slang
|
||||
shader12 = ../../shaders/mdapt/hsm-mdapt-pass1.slang
|
||||
shader13 = ../../shaders/mdapt/hsm-mdapt-pass2.slang
|
||||
shader14 = ../../shaders/mdapt/hsm-mdapt-pass3.slang
|
||||
shader15 = ../../shaders/mdapt/hsm-mdapt-pass4.slang
|
||||
|
||||
shader16 = ../../shaders/dedither/dedither-gamma-prep-2-after.slang
|
||||
|
||||
shader17 = ../../shaders/ps1dither/hsm-PS1-Undither-BoxBlur.slang
|
||||
|
||||
shader18 = ../../shaders/guest/extras/hsm-sharpsmoother.slang
|
||||
|
||||
shader19 = ../../shaders/base/stock.slang
|
||||
alias19 = refpass
|
||||
|
||||
shader20 = ../../shaders/scalefx/hsm-scalefx-pass0.slang
|
||||
filter_linear20 = false
|
||||
scale_type20 = source
|
||||
scale20 = 1.0
|
||||
float_framebuffer20 = true
|
||||
alias20 = scalefx_pass0
|
||||
|
||||
shader21 = ../../shaders/scalefx/hsm-scalefx-pass1.slang
|
||||
filter_linear21 = false
|
||||
scale_type21 = source
|
||||
scale21 = 1.0
|
||||
float_framebuffer12 = true
|
||||
|
||||
shader22 = ../../shaders/scalefx/hsm-scalefx-pass2.slang
|
||||
filter_linear22 = false
|
||||
scale_type22 = source
|
||||
scale22 = 1.0
|
||||
|
||||
shader23 = ../../shaders/scalefx/hsm-scalefx-pass3.slang
|
||||
filter_linear23 = false
|
||||
scale_type23 = source
|
||||
scale23 = 1.0
|
||||
|
||||
shader24 = ../../shaders/scalefx/hsm-scalefx-pass4.slang
|
||||
filter_linear24 = false
|
||||
scale_type24 = source
|
||||
scale24 = 3
|
||||
|
||||
shader25 = ../../shaders/base/stock.slang
|
||||
alias25 = "PreCRTPass"
|
||||
|
||||
shader26 = ../../shaders/guest/hsm-afterglow0.slang
|
||||
filter_linear26 = true
|
||||
scale_type26 = source
|
||||
scale26 = 1.0
|
||||
alias26 = "AfterglowPass"
|
||||
|
||||
shader27 = ../../shaders/guest/hsm-pre-shaders-afterglow.slang
|
||||
filter_linear27 = true
|
||||
scale_type27 = source
|
||||
mipmap_input27 = true
|
||||
scale27 = 1.0
|
||||
|
||||
// Color Correction with Dogway's awesome Grade shader
|
||||
// Grade is after Afterglow so that brightening the black level does not break the afterglow
|
||||
shader28 = ../../shaders/dogway/hsm-grade.slang
|
||||
filter_linear28 = true
|
||||
scale_type28 = source
|
||||
scale28 = 1.0
|
||||
|
||||
shader29 = ../../shaders/base/stock.slang
|
||||
alias29 = "PrePass0"
|
||||
|
||||
shader30 = ../../shaders/guest/ntsc/hsm-ntsc-pass1.slang
|
||||
filter_linear30 = false
|
||||
float_framebuffer30 = true
|
||||
scale_type_x30 = source
|
||||
scale_type_y30 = source
|
||||
scale_x30 = 4.0
|
||||
scale_y30 = 1.0
|
||||
frame_count_mod30 = 2
|
||||
alias30 = NPass1
|
||||
|
||||
shader31 = ../../shaders/guest/ntsc/hsm-ntsc-pass2.slang
|
||||
float_framebuffer31 = true
|
||||
filter_linear31 = true
|
||||
scale_type31 = source
|
||||
scale_x31 = 0.5
|
||||
scale_y31 = 1.0
|
||||
|
||||
shader32 = ../../shaders/guest/ntsc/hsm-ntsc-pass3.slang
|
||||
filter_linear32 = true
|
||||
scale_type32 = source
|
||||
scale_x32 = 1.0
|
||||
scale_y32 = 1.0
|
||||
|
||||
shader33 = ../../shaders/guest/hsm-custom-fast-sharpen.slang
|
||||
filter_linear33 = true
|
||||
scale_type33 = source
|
||||
scale_x33 = 1.0
|
||||
scale_y33 = 1.0
|
||||
|
||||
shader34 = ../../shaders/base/stock.slang
|
||||
filter_linear34 = true
|
||||
scale_type34 = source
|
||||
scale_x34 = 1.0
|
||||
scale_y34 = 1.0
|
||||
alias34 = "PrePass"
|
||||
mipmap_input34 = true
|
||||
|
||||
shader35 = ../../shaders/guest/hsm-avg-lum.slang
|
||||
filter_linear35 = true
|
||||
scale_type35 = source
|
||||
scale35 = 1.0
|
||||
mipmap_input35 = true
|
||||
alias35 = "AvgLumPass"
|
||||
|
||||
// Pass referenced by subsequent blurring passes and crt pass
|
||||
shader36 = ../../shaders/guest/hsm-interlace-and-linearize.slang
|
||||
filter_linear36 = true
|
||||
scale_type36 = source
|
||||
scale36 = 1.0
|
||||
float_framebuffer36 = true
|
||||
alias36 = "LinearizePass"
|
||||
|
||||
shader37 = ../../shaders/guest/hsm-crt-guest-advanced-ntsc-pass1.slang
|
||||
filter_linear37 = true
|
||||
scale_type_x37 = viewport
|
||||
scale_x37 = 1.0
|
||||
scale_type_y37 = source
|
||||
scale_y37 = 1.0
|
||||
float_framebuffer37 = true
|
||||
alias37 = Pass1
|
||||
|
||||
shader38 = ../../shaders/guest/hsm-gaussian_horizontal.slang
|
||||
filter_linear38 = true
|
||||
scale_type_x38 = absolute
|
||||
scale_x38 = 640.0
|
||||
scale_type_y38 = source
|
||||
scale_y38 = 1.0
|
||||
float_framebuffer38 = true
|
||||
|
||||
shader39 = ../../shaders/guest/hsm-gaussian_vertical.slang
|
||||
filter_linear39 = true
|
||||
scale_type_x39 = absolute
|
||||
scale_x39 = 640.0
|
||||
scale_type_y39 = absolute
|
||||
scale_y39 = 480.0
|
||||
float_framebuffer39 = true
|
||||
alias39 = GlowPass
|
||||
|
||||
shader40 = ../../shaders/guest/hsm-bloom_horizontal.slang
|
||||
filter_linear40 = true
|
||||
scale_type_x40 = absolute
|
||||
scale_x40 = 640.0
|
||||
scale_type_y40 = absolute
|
||||
scale_y40 = 480.0
|
||||
float_framebuffer40 = true
|
||||
|
||||
shader41 = ../../shaders/guest/hsm-bloom_vertical.slang
|
||||
filter_linear41 = true
|
||||
scale_type_x41 = absolute
|
||||
scale_x41 = 640.0
|
||||
scale_type_y41 = absolute
|
||||
scale_y41 = 480.0
|
||||
float_framebuffer41 = true
|
||||
alias41 = BloomPass
|
||||
|
||||
shader42 = ../../shaders/guest/hsm-crt-guest-advanced-ntsc-pass2.slang
|
||||
filter_linear42 = true
|
||||
float_framebuffer42 = true
|
||||
scale_type42 = viewport
|
||||
scale_x42 = 1.0
|
||||
scale_y42 = 1.0
|
||||
|
||||
shader43 = ../../shaders/guest/hsm-deconvergence.slang
|
||||
filter_linear43 = true
|
||||
scale_type43 = viewport
|
||||
scale_x43 = 1.0
|
||||
scale_y43 = 1.0
|
||||
|
||||
shader44 = ../../shaders/base/post-crt-prep-image-layers.slang
|
||||
alias44 = "MBZ_PostCRTPass"
|
||||
|
||||
// Reduce Resolution ----------------------------------------------------------------
|
||||
// Reduce the resolution to a small static size regardless of final resolution
|
||||
// Allows consistent look and faster at different final resolutions for blur
|
||||
// Mipmap option allows downscaling without artifacts
|
||||
shader45 = ../../shaders/base/linearize-crt.slang
|
||||
mipmap_input45 = true
|
||||
filter_linear45 = true
|
||||
scale_type45 = absolute
|
||||
// scale_x45 = 480
|
||||
// scale_y45 = 270
|
||||
// scale_x45 = 960
|
||||
// scale_y45 = 540
|
||||
scale_x45 = 800
|
||||
scale_y45 = 600
|
||||
alias45 = "BR_MirrorLowResPass"
|
||||
|
||||
// Add Blur for the Reflection (Horizontal) ----------------------------------------------------------------
|
||||
shader46 = ../../shaders/base/blur-outside-screen-horiz.slang
|
||||
mipmap_input46 = true
|
||||
filter_linear46 = true
|
||||
|
||||
// Add Blur for the Reflection (Vertical) ----------------------------------------------------------------
|
||||
shader47 = ../../shaders/base/blur-outside-screen-vert.slang
|
||||
filter_linear47 = true
|
||||
alias47 = "BR_MirrorBlurredPass"
|
||||
|
||||
// Reduce resolution ----------------------------------------------------------------
|
||||
// Reduced to a very small amount so we can create a blur which will create a glow from the screen
|
||||
// Mipmap option allows smoother downscaling
|
||||
shader48 = ../../../../blurs/shaders/royale/blur9x9.slang
|
||||
mipmap_input48 = true
|
||||
filter_linear48 = true
|
||||
scale_type48 = absolute
|
||||
scale_x48 = 128
|
||||
scale_y48 = 128
|
||||
alias48 = "BR_MirrorReflectionDiffusedPass"
|
||||
|
||||
// Add Diffused glow all around the screen ----------------------------------------------------------------
|
||||
// Blurred so much that it's non directional
|
||||
// Mipmap option allows downscaling without artifacts
|
||||
shader49 = ../../../../blurs/shaders/royale/blur9x9.slang
|
||||
mipmap_input49 = true
|
||||
filter_linear49 = true
|
||||
scale_type49 = absolute
|
||||
scale_x49 = 12
|
||||
scale_y49 = 12
|
||||
alias49 = "BR_MirrorFullscreenGlowPass"
|
||||
|
||||
// Bezel Reflection ----------------------------------------------------------------
|
||||
shader50 = ../../shaders/base/reflection.slang
|
||||
scale_type50 = viewport
|
||||
float_framebuffer50 = true
|
||||
alias50 = "BR_CRTAndReflectionPass"
|
||||
|
||||
// Bezel Generation & Composite of Image Layers ----------------------------------------------------------------
|
||||
|
||||
shader51 = ../../shaders/base/bezel-images-under-crt.slang
|
||||
filter_linear51 = true
|
||||
scale_type51 = viewport
|
||||
float_framebuffer51 = true
|
||||
alias51 = "BR_LayersUnderCRTPass"
|
||||
|
||||
shader52 = ../../shaders/base/bezel-images-over-crt.slang
|
||||
filter_linear52 = true
|
||||
scale_type52 = viewport
|
||||
float_framebuffer52 = true
|
||||
alias52 = "BR_LayersOverCRTPass"
|
||||
|
||||
// Combine Passes ----------------------------------------------------------------
|
||||
shader53 = ../../shaders/base/combine-passes.slang
|
||||
scale_type53 = viewport
|
||||
alias53 = "CombinePass"
|
||||
// Define textures to be used by the different passes
|
||||
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4;IntroImage;ScreenPlacementImage;TubeDiffuseImage;TubeColoredGelImage;TubeShadowImage;TubeStaticReflectionImage;BackgroundImage;BackgroundVertImage;ReflectionMaskImage;FrameTextureImage;CabinetGlassImage;DeviceImage;DeviceVertImage;DeviceLEDImage;DecalImage;NightLightingImage;NightLighting2Image;LEDImage;TopLayerImage;"
|
||||
|
||||
SamplerLUT1 = ../../shaders/guest/lut/trinitron-lut.png
|
||||
SamplerLUT1_linear = true
|
||||
SamplerLUT2 = ../../shaders/guest/lut/inv-trinitron-lut.png
|
||||
SamplerLUT2_linear = true
|
||||
SamplerLUT3 = ../../shaders/guest/lut/nec-lut.png
|
||||
SamplerLUT3_linear = true
|
||||
SamplerLUT4 = ../../shaders/guest/lut/ntsc-lut.png
|
||||
SamplerLUT4_linear = true
|
||||
|
||||
IntroImage = ../../shaders/textures/IntroImage_MegaBezelLogo.png
|
||||
IntroImage_linear = true
|
||||
IntroImage_mipmap = 1
|
||||
|
||||
ScreenPlacementImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
ScreenPlacementImage_linear = false
|
||||
|
||||
TubeDiffuseImage = ../../shaders/textures/Tube_Diffuse_2390x1792.png
|
||||
TubeDiffuseImage_linear = true
|
||||
TubeDiffuseImage_mipmap = 1
|
||||
|
||||
TubeColoredGelImage = ../../shaders/textures/Colored_Gel_Rainbow.png
|
||||
TubeColoredGelImage_linear = true
|
||||
TubeColoredGelImage_mipmap = 1
|
||||
|
||||
TubeShadowImage = ../../shaders/textures/Tube_Shadow_1600x1200.png
|
||||
TubeShadowImage_linear = true
|
||||
TubeShadowImage_mipmap = 1
|
||||
|
||||
TubeStaticReflectionImage = ../../shaders/textures/TubeGlassOverlayImageCropped_1440x1080.png
|
||||
TubeStaticReflectionImage_linear = true
|
||||
TubeStaticReflectionImage_mipmap = 1
|
||||
|
||||
ReflectionMaskImage = ../../shaders/textures/Placeholder_White_16x16.png
|
||||
ReflectionMaskImage_linear = true
|
||||
ReflectionMaskImage_mipmap = 1
|
||||
|
||||
FrameTextureImage = ../../shaders/textures/FrameTexture_2800x2120.png
|
||||
FrameTextureImage_linear = true
|
||||
FrameTextureImage_mipmap = 1
|
||||
|
||||
BackgroundImage = ../../shaders/textures/BackgroundImage_Carbon_3840x2160.png
|
||||
BackgroundImage_linear = true
|
||||
BackgroundImage_mipmap = 1
|
||||
|
||||
BackgroundVertImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
BackgroundVertImage_linear = true
|
||||
BackgroundVertImage_mipmap = 1
|
||||
|
||||
CabinetGlassImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
CabinetGlassImage_linear = true
|
||||
CabinetGlassImage_mipmap = 1
|
||||
|
||||
DeviceImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
DeviceImage_linear = true
|
||||
DeviceImage_mipmap = 1
|
||||
|
||||
DeviceVertImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
DeviceVertImage_linear = true
|
||||
DeviceVertImage_mipmap = 1
|
||||
|
||||
DeviceLEDImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
DeviceLEDImage_linear = true
|
||||
DeviceLEDImage_mipmap = 1
|
||||
|
||||
DecalImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
DecalImage_linear = true
|
||||
DecalImage_mipmap = 1
|
||||
|
||||
NightLightingImage = ../../shaders/textures/NightLightingClose_1920x1080.png
|
||||
NightLightingImage_linear = true
|
||||
NightLightingImage_mipmap = 1
|
||||
|
||||
NightLighting2Image = ../../shaders/textures/NightLightingFar_1920x1080.png
|
||||
NightLighting2Image_linear = true
|
||||
NightLighting2Image_mipmap = 1
|
||||
|
||||
LEDImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
LEDImage_linear = true
|
||||
LEDImage_mipmap = 1
|
||||
|
||||
TopLayerImage = ../../shaders/textures/Placeholder_Transparent_16x16.png
|
||||
TopLayerImage_linear = true
|
||||
TopLayerImage_mipmap = 1
|
||||
|
||||
// Use for matching vanilla GDV-Advanced
|
||||
// HSM_ASPECT_RATIO_MODE = 6
|
||||
// HSM_CURVATURE_MODE = 0
|
||||
|
||||
// SMOOTH-ADV
|
||||
HSM_DEDITHER_MODE = 1
|
||||
|
||||
HSM_SCALEFX_ON = 1
|
||||
|
||||
HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR = 300
|
||||
HSM_CORE_RES_SAMPLING_MULT_OPPOSITE_DIR = 125
|
||||
HSM_DOWNSAMPLE_BLUR_SCANLINE_DIR = 0
|
||||
HSM_DOWNSAMPLE_BLUR_OPPOSITE_DIR = 0
|
||||
|
||||
ntsc_scale = 0.4
|
||||
|
||||
shadowMask = 3
|
||||
|
||||
// NTSC Parameters
|
||||
GAMMA_INPUT = 2.0
|
||||
gamma_out = 1.95
|
||||
|
||||
// DREZ Parameters
|
||||
SHARPEN = 0"#;
|
||||
|
||||
const TEST3: &str = r#"// DUIMON MEGA BEZEL GRAPHICS AND PRESETS | https://duimon.github.io/Gallery-Guides/ | duimonmb@gmail.com
|
||||
// SOME RIGHTS RESERVED - RELEASED UNDER CC BY NC ND LICENSE https://creativecommons.org/licenses/by-nc-nd/4.0/deed
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// PRESET START
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// SHADER :: CONNECTOR | Interface to Mega Bezel Presets folders.
|
||||
// Edit the target file in the following reference to globally define the base preset.
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
#reference "../../../zzz_global_params/Base_Shader/ADV_Bezel.slangp"
|
||||
|
||||
// SHADER :: CONNECTOR :: LOCAL OVERRIDES | Interface to specific base presets.
|
||||
// Comment out the top reference line and uncomment the following reference line to locally define the base preset.
|
||||
// Keep in mind that some of the base presets use Integer Scale and may yield unexpected results. (e.g. Megatron)
|
||||
|
||||
//#reference "../../../zzz_global_params/Local_Shader/ADV_06.slangp"
|
||||
|
||||
// "ADV_06" matches the default "MBZ__1__ADV__GDV.slangp".
|
||||
// Replace the "06" with any from the following list.
|
||||
// 01. SMOOTH-ADV__GDV 08. ADV__GDV-MINI-NTSC
|
||||
// 02. SMOOTH-ADV__GDV-NTSC 09. ADV__GDV-NTSC
|
||||
// 03. SMOOTH-ADV__MEGATRON 10. ADV__MEGATRON
|
||||
// 04. SMOOTH-ADV__MEGATRON-NTSC 11. ADV__MEGATRON-NTSC
|
||||
// 05. ADV__EASYMODE 12. ADV-RESHADE-FX__GDV
|
||||
// 06. ADV__GDV 13. ADV-SUPER-XBR__GDV
|
||||
// 07. ADV__GDV-MINI 14. ADV-SUPER-XBR__GDV-NTSC
|
||||
|
||||
// INTRO | Intro animation
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// ON
|
||||
#reference "../../../zzz_global_params/Intro/on.params"
|
||||
// ON - No Image
|
||||
//#reference "../../../zzz_global_params/Intro/on_no_image.params"
|
||||
// ON - Default Mega Bezel intro
|
||||
//#reference "../../../zzz_global_params/Intro/on_default.params"
|
||||
// OFF
|
||||
//#reference "../../../zzz_global_params/Intro/off.params"
|
||||
|
||||
// DEVICE | Screen/Monitor/CRT/TV settings
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// DEVICE :: BASE
|
||||
#reference "../../../res/bezel/Nintendo_GBA/bezel.params"
|
||||
|
||||
// DEVICE :: SCALING
|
||||
#reference "../../../res/scale/Nintendo_GBA/bezel.params"
|
||||
|
||||
// DEVICE :: CRT
|
||||
#reference "../../../res/crt/Nintendo_GBA/bezel.params"
|
||||
|
||||
// IMAGE LAYERS
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
#reference "../../../res/layers/Nintendo_GBA/bezel.params"
|
||||
|
||||
// HSV :: Hue, Saturation, and Value parameters
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// GRAPHICS OVERRIDES | Overrides for Image layers, scaling, etc
|
||||
// that are not related to Guest's shader. (Three examples are provided)
|
||||
// These are intended for [Bezel] versions and the following reference should be left commented out for others.
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// GRAPHICS :: OVERRIDES
|
||||
//#reference "../../../res/overrides/batocera.params"
|
||||
//#reference "../../../res/overrides/batocera_nocurve.params"
|
||||
//#reference "../../../res/overrides/batocera_hud.params"
|
||||
|
||||
// GLOBAL GRAPHICS :: OVERRIDES
|
||||
// The user can edit the "user.params" to globally change the presets.
|
||||
// These are for the bezel, frame, and other graphic attributes.
|
||||
// Examples are included in the params file and commented out.
|
||||
// These are also intended for [Bezel] versions and the following reference should be left commented out for others.
|
||||
#reference "../../../zzz_global_params/Graphics/user.params"
|
||||
|
||||
// The following is restricted to the [Custom-Bezel_002] presets.
|
||||
// One example is included in the params file and commented out.
|
||||
//#reference "../../../zzz_global_params/Graphics/user2.params"
|
||||
|
||||
// SHADER OVERRIDES | Place *.params references to Guest derivatives here.
|
||||
// (Make sure you are using ADV__GDV, STD__GDV, or POTATO__GDV base presets for variations on the Guest shader.)
|
||||
// Two examples were kindly provided by guest.r. ;-)
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// SHADER :: OVERRIDES
|
||||
//#reference "../../../res/overrides_shader/guest_custom_aperture.params"
|
||||
//#reference "../../../res/overrides_shader/guest_custom_slotmask.params"
|
||||
|
||||
// GLOBAL SHADER :: OVERRIDES
|
||||
// The user can edit the target params file to globally change the presets.
|
||||
// To use community params that require another base preset, change the global base reference to match.
|
||||
// Examples are included in the params file and commented out.
|
||||
// Separate folders let users change global settings on each of the sets.
|
||||
// These are intentionally commented out for LCD-GRID presets.
|
||||
//#reference "../../../zzz_global_params/Shader/ADV/user_Bezel.params"
|
||||
//#reference "../../../zzz_global_params/Shader/ADV_DREZ/user_Bezel.params"
|
||||
//#reference "../../../zzz_global_params/Shader/STD/user_Bezel.params"
|
||||
//#reference "../../../zzz_global_params/Shader/STD_DREZ/user_Bezel.params"
|
||||
//#reference "../../../zzz_global_params/Shader/LITE/user_Bezel.params"
|
||||
|
||||
// AMBIENT LIGHTING
|
||||
//#reference "../../../res/lighting/night.params"
|
||||
|
||||
// PRESET END
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
"#;
|
||||
}
|
||||
|
|
|
@ -10,16 +10,20 @@ use nom::IResult;
|
|||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
use crate::parse::token::do_lex;
|
||||
use librashader_common::map::{FastHashMap, ShortString};
|
||||
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 vec_extract_if_polyfill::MakeExtractIf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
ShaderCount(i32),
|
||||
FeedbackPass(i32),
|
||||
FeedbackPass(#[allow(unused)] i32),
|
||||
Shader(i32, PathBuf),
|
||||
ScaleX(i32, ScaleFactor),
|
||||
ScaleY(i32, ScaleFactor),
|
||||
|
@ -33,10 +37,10 @@ pub enum Value {
|
|||
FloatFramebuffer(i32, bool),
|
||||
SrgbFramebuffer(i32, bool),
|
||||
MipmapInput(i32, bool),
|
||||
Alias(i32, String),
|
||||
Parameter(String, f32),
|
||||
Alias(i32, ShortString),
|
||||
Parameter(ShortString, f32),
|
||||
Texture {
|
||||
name: String,
|
||||
name: ShortString,
|
||||
filter_mode: FilterMode,
|
||||
wrap_mode: WrapMode,
|
||||
mipmap: bool,
|
||||
|
@ -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);
|
||||
|
@ -186,8 +195,10 @@ fn load_child_reference_strings(
|
|||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
|
||||
let mut new_tokens = do_lex(&reference_contents)?;
|
||||
let new_references: Vec<PathBuf> = new_tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
let new_references: Vec<PathBuf> =
|
||||
MakeExtractIf::extract_if(&mut new_tokens, |token| {
|
||||
*token.key.fragment() == "#reference"
|
||||
})
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
|
@ -202,8 +213,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.into_hashmap();
|
||||
|
||||
apply_context(&mut path, &context);
|
||||
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
@ -214,12 +233,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() {
|
||||
|
@ -231,13 +252,15 @@ pub fn parse_values(
|
|||
root_path.pop();
|
||||
}
|
||||
|
||||
let references: Vec<PathBuf> = tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
let references: Vec<PathBuf> =
|
||||
MakeExtractIf::extract_if(&mut tokens, |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() {
|
||||
|
@ -250,13 +273,16 @@ pub fn parse_values(
|
|||
// load depth first, so all child tokens are first.
|
||||
// Later tokens take precedence.
|
||||
all_tokens.push((root_path.as_path(), tokens));
|
||||
|
||||
// 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
|
||||
MakeExtractIf::extract_if(tokens, |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);
|
||||
parameter_names.push(parameter_name.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,10 +290,11 @@ 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 MakeExtractIf::extract_if(tokens, |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);
|
||||
texture_names.push(texture_name.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +302,9 @@ 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 MakeExtractIf::extract_if(tokens, |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;
|
||||
|
@ -306,8 +335,11 @@ 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
|
||||
MakeExtractIf::extract_if(tokens, |token| texture_names.contains(token.key.fragment()))
|
||||
{
|
||||
let mut relative_path = path.to_path_buf();
|
||||
// Don't trim paths
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
|
@ -358,7 +390,7 @@ pub fn parse_values(
|
|||
.map_or(None, |(_, v)| Some(FilterMode::from_str(&v.value).unwrap()));
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
name: ShortString::from(*texture.fragment()),
|
||||
filter_mode: filter.unwrap_or(if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
|
@ -373,7 +405,7 @@ pub fn parse_values(
|
|||
let mut rest_tokens = Vec::new();
|
||||
// hopefully no more textures left in the token tree
|
||||
for (p, token) in tokens {
|
||||
if parameter_names.contains(token.key.fragment()) {
|
||||
if parameter_names.contains(&token.key.fragment().trim()) {
|
||||
let param_val = from_float(token.value)
|
||||
// This is literally just to work around BEAM_PROFILE in crt-hyllian-sinc-glow.slangp
|
||||
// which has ""0'.000000". This somehow works in RA because it defaults to 0, probably.
|
||||
|
@ -381,7 +413,7 @@ pub fn parse_values(
|
|||
// params (god help me), it would be pretty bad because we lose texture path fallback.
|
||||
.unwrap_or(0.0);
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
ShortString::from(token.key.fragment().trim()),
|
||||
param_val,
|
||||
));
|
||||
continue;
|
||||
|
@ -462,7 +494,10 @@ pub fn parse_values(
|
|||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("alias", token.key) {
|
||||
values.push(Value::Alias(idx, token.value.to_string()));
|
||||
values.push(Value::Alias(
|
||||
idx,
|
||||
ShortString::from(token.value.fragment().trim()),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type", token.key) {
|
||||
|
@ -525,7 +560,7 @@ pub fn parse_values(
|
|||
// handle undeclared parameters after parsing everything else as a last resort.
|
||||
if let Ok(param_val) = from_float(token.value) {
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
ShortString::from(token.key.fragment().trim()),
|
||||
param_val,
|
||||
));
|
||||
}
|
||||
|
@ -536,6 +571,7 @@ pub fn parse_values(
|
|||
.all(|k| !token.key.ends_with(k))
|
||||
{
|
||||
let mut relative_path = path.to_path_buf();
|
||||
// Don't trim paths.
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
|
@ -574,7 +610,7 @@ pub fn parse_values(
|
|||
});
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
name: ShortString::from(*texture.fragment()),
|
||||
filter_mode: if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
|
@ -593,13 +629,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);
|
||||
PathBuf::from("../test/shaders_slang/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
|
||||
let basic = parse_preset(root, WildcardContext::new());
|
||||
eprintln!("{basic:?}");
|
||||
assert!(basic.is_ok());
|
||||
}
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
use crate::error::ParsePresetError;
|
||||
use librashader_common::map::ShortString;
|
||||
use librashader_common::{FilterMode, ImageFormat, WrapMode};
|
||||
use std::ops::Mul;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// The configuration for a single shader pass.
|
||||
pub type PassConfig = PathReference<PassMeta>;
|
||||
|
||||
/// Configuration options for a lookup texture used in the shader.
|
||||
pub type TextureConfig = PathReference<TextureMeta>;
|
||||
|
||||
/// A reference to a resource on disk.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShaderPassConfig {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct PathReference<M> {
|
||||
pub storage: librashader_common::StorageType,
|
||||
/// Meta information about the resource.
|
||||
pub meta: M,
|
||||
}
|
||||
|
||||
/// Meta information about a shader pass.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct PassMeta {
|
||||
/// 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,
|
||||
/// The alias of the shader pass if available.
|
||||
pub alias: Option<String>,
|
||||
pub alias: Option<ShortString>,
|
||||
/// The filtering mode that this shader pass should expect.
|
||||
pub filter: FilterMode,
|
||||
/// The texture addressing (wrap) mode that this shader pass expects.
|
||||
|
@ -29,7 +43,7 @@ pub struct ShaderPassConfig {
|
|||
pub scaling: Scale2D,
|
||||
}
|
||||
|
||||
impl ShaderPassConfig {
|
||||
impl PassMeta {
|
||||
/// If the framebuffer expects a different format than what was defined in the
|
||||
/// shader source, returns such format.
|
||||
#[inline(always)]
|
||||
|
@ -54,6 +68,7 @@ impl ShaderPassConfig {
|
|||
|
||||
#[repr(i32)]
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// The scaling type for the shader pass.
|
||||
pub enum ScaleType {
|
||||
#[default]
|
||||
|
@ -63,10 +78,13 @@ 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.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ScaleFactor {
|
||||
/// Scale by a fractional float factor.
|
||||
Float(f32),
|
||||
|
@ -119,6 +137,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())),
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +145,7 @@ impl FromStr for ScaleType {
|
|||
|
||||
/// Framebuffer scaling parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Scaling {
|
||||
/// The method to scale the framebuffer with.
|
||||
pub scale_type: ScaleType,
|
||||
|
@ -135,6 +155,7 @@ pub struct Scaling {
|
|||
|
||||
/// 2D quad scaling parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Scale2D {
|
||||
/// Whether or not this combination of scaling factors is valid.
|
||||
pub valid: bool,
|
||||
|
@ -146,24 +167,24 @@ pub struct Scale2D {
|
|||
|
||||
/// Configuration options for a lookup texture used in the shader.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextureConfig {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TextureMeta {
|
||||
/// The name of the texture.
|
||||
pub name: String,
|
||||
/// The fully qualified path to the texture.
|
||||
pub path: PathBuf,
|
||||
pub name: ShortString,
|
||||
/// The wrap (addressing) mode to use when sampling the texture.
|
||||
pub wrap_mode: WrapMode,
|
||||
/// The filter mode to use when sampling the texture.
|
||||
pub filter_mode: FilterMode,
|
||||
/// Whether or not to generate mipmaps for this texture.
|
||||
/// Whether to generate mipmaps for this texture.
|
||||
pub mipmap: bool,
|
||||
}
|
||||
|
||||
/// Configuration options for a shader parameter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParameterConfig {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ParameterMeta {
|
||||
/// The name of the parameter.
|
||||
pub name: String,
|
||||
pub name: ShortString,
|
||||
/// The value it is set to in the preset.
|
||||
pub value: f32,
|
||||
}
|
||||
|
@ -173,6 +194,7 @@ pub struct ParameterConfig {
|
|||
/// A shader preset can be used to create a filter chain runtime instance, or reflected to get
|
||||
/// parameter metadata.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShaderPreset {
|
||||
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
|
||||
/// Otherwise, the FBO after pass #N is passed a texture to next frame
|
||||
|
@ -180,14 +202,14 @@ pub struct ShaderPreset {
|
|||
pub feedback_pass: i32,
|
||||
|
||||
/// The number of shaders enabled in the filter chain.
|
||||
pub shader_count: i32,
|
||||
pub pass_count: i32,
|
||||
// Everything is in Vecs because the expect number of values is well below 64.
|
||||
/// Preset information for each shader.
|
||||
pub shaders: Vec<ShaderPassConfig>,
|
||||
pub passes: Vec<PassConfig>,
|
||||
|
||||
/// Preset information for each texture.
|
||||
pub textures: Vec<TextureConfig>,
|
||||
|
||||
/// Preset information for each user parameter.
|
||||
pub parameters: Vec<ParameterConfig>,
|
||||
pub parameters: Vec<ParameterMeta>,
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use glob::glob;
|
||||
use librashader_presets::context::{ContextItem, VideoDriver, WildcardContext};
|
||||
use librashader_presets::ShaderPreset;
|
||||
|
||||
#[test]
|
||||
fn parses_all_slang_presets() {
|
||||
for entry in glob("../test/slang-shaders/**/*.slangp").unwrap() {
|
||||
for entry in glob("../test/shaders_slang/**/*.slangp").unwrap() {
|
||||
if let Ok(path) = entry {
|
||||
if let Err(e) = ShaderPreset::try_parse(&path) {
|
||||
println!("Could not parse {}: {:?}", path.display(), e)
|
||||
|
@ -14,9 +15,20 @@ fn parses_all_slang_presets() {
|
|||
|
||||
#[test]
|
||||
fn parses_problematic() {
|
||||
for entry in glob("../test/slang-shaders/crt/crt-hyllian-sinc-glow.slangp").unwrap() {
|
||||
if let Ok(path) = entry {
|
||||
ShaderPreset::try_parse(&path).expect(&format!("Failed to parse {}", path.display()));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
#[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.1"
|
||||
version = "0.5.1"
|
||||
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.6.0"
|
||||
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.1" }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.1" }
|
||||
librashader-presets = { path = "../librashader-presets", version = "0.1.1" }
|
||||
librashader-common = { path = "../librashader-common", version = "0.5.1" }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
|
||||
librashader-presets = { path = "../librashader-presets", version = "0.5.1" }
|
||||
librashader-pack = { path = "../librashader-pack", version = "0.5.1" }
|
||||
|
||||
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-cross2 = { workspace = true, 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 }
|
||||
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" ]
|
||||
serialize = [ "serde" ]
|
||||
default = ["cross", "naga", "wgsl", "msl"]
|
||||
dxil = [ "spirv-cross2?/hlsl", "dep:spirv-to-dxil" ]
|
||||
wgsl = [ "cross", "naga", "naga/wgsl-out", "dep:spirv", "dep:rspirv"]
|
||||
cross = [ "spirv-cross2", "spirv-cross2/glsl", "spirv-cross2/hlsl", "spirv-cross2/msl" ]
|
||||
naga = [ "dep:rspirv", "dep:spirv", "dep:naga", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
|
||||
serde = ["dep:serde", "serde/derive", "librashader-common/serde", "bitflags/serde"]
|
||||
msl = [ "cross", "spirv-cross2/msl", "naga?/msl-out" ]
|
||||
|
||||
stable = []
|
||||
|
||||
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,45 +1,66 @@
|
|||
use crate::back::spirv::WriteSpirV;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
|
||||
use crate::back::targets::{OutputTarget, DXIL};
|
||||
use crate::back::{
|
||||
CompileReflectShader, CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput,
|
||||
};
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::glsl::GlslReflect;
|
||||
use crate::reflect::cross::SpirvCross;
|
||||
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 {
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
|
||||
type Target = DXIL;
|
||||
type Options = Option<ShaderModel>;
|
||||
type Context = ();
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stable")]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
|
||||
type Target = DXIL;
|
||||
type Options = Option<ShaderModel>;
|
||||
type Context = ();
|
||||
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
let reflect = GlslReflect::try_from(&compile)?;
|
||||
Ok(CompilerBackend {
|
||||
// we can just reuse WriteSpirV as the backend.
|
||||
backend: Box::new(WriteSpirV {
|
||||
reflect,
|
||||
vertex: compile.vertex,
|
||||
fragment: compile.fragment,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CompileShader<DXIL> for WriteSpirV {
|
||||
type Options = Option<ShaderModel>;
|
||||
type Context = ();
|
||||
|
@ -59,6 +80,7 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
register_space: 0,
|
||||
base_shader_register: 1,
|
||||
},
|
||||
shader_model_max: sm,
|
||||
..RuntimeConfig::default()
|
||||
};
|
||||
|
||||
|
@ -68,7 +90,6 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
None,
|
||||
"main",
|
||||
ShaderStage::Vertex,
|
||||
sm,
|
||||
ValidatorVersion::None,
|
||||
&config,
|
||||
)
|
||||
|
@ -79,7 +100,6 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
None,
|
||||
"main",
|
||||
ShaderStage::Fragment,
|
||||
sm,
|
||||
ValidatorVersion::None,
|
||||
&config,
|
||||
)
|
||||
|
@ -91,4 +111,11 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
context: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn compile_boxed(
|
||||
self: Box<Self>,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<DxilObject, Self::Context>, ShaderCompileError> {
|
||||
<WriteSpirV as CompileShader<DXIL>>::compile(*self, options)
|
||||
}
|
||||
}
|
||||
|
|
50
librashader-reflect/src/back/glsl.rs
Normal file
50
librashader-reflect/src/back/glsl.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use crate::back::targets::GLSL;
|
||||
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::{CompiledProgram, SpirvCross};
|
||||
|
||||
/// The GLSL version to target.
|
||||
pub use spirv_cross2::compile::glsl::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_cross2::targets::Glsl>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
|
||||
type Target = GLSL;
|
||||
type Options = GlslVersion;
|
||||
type Context = CrossGlslContext;
|
||||
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: GlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stable")]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
|
||||
type Target = GLSL;
|
||||
type Options = GlslVersion;
|
||||
type Context = CrossGlslContext;
|
||||
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: Box::new(GlslReflect::try_from(&compile)?),
|
||||
})
|
||||
}
|
||||
}
|
163
librashader-reflect/src/back/hlsl.rs
Normal file
163
librashader-reflect/src/back/hlsl.rs
Normal file
|
@ -0,0 +1,163 @@
|
|||
use crate::back::targets::HLSL;
|
||||
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::hlsl::HlslReflect;
|
||||
use crate::reflect::cross::{CompiledProgram, SpirvCross};
|
||||
|
||||
/// The HLSL shader model version to target.
|
||||
pub use spirv_cross2::compile::hlsl::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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for a HLSL compilation via spirv-cross.
|
||||
pub struct CrossHlslContext {
|
||||
/// The compiled HLSL program.
|
||||
pub artifact: CompiledProgram<spirv_cross2::targets::Hlsl>,
|
||||
pub vertex_buffers: HlslBufferAssignments,
|
||||
pub fragment_buffers: HlslBufferAssignments,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
|
||||
type Target = HLSL;
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: HlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stable")]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
|
||||
type Target = HLSL;
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: Box::new(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};
|
||||
|
@ -29,37 +32,50 @@ pub trait CompileShader<T: OutputTarget> {
|
|||
type Context;
|
||||
|
||||
/// Consume the object and return the compiled output of the shader.
|
||||
///
|
||||
/// The shader should either be reflected or validated as
|
||||
/// [ReflectShader] before being compiled, or the results may not be valid.
|
||||
fn compile(
|
||||
self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
|
||||
|
||||
/// Consume the object and return the compiled output of the shader.
|
||||
///
|
||||
/// This is an internal implementation detail for stable building without TAIT,
|
||||
/// to allow delegation when Self is unsized (i.e. dyn CompileReflectShader).
|
||||
#[doc(hidden)]
|
||||
fn compile_boxed(
|
||||
self: Box<Self>,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
|
||||
}
|
||||
|
||||
/// Marker trait for combinations of targets and compilations that can be reflected and compiled
|
||||
/// successfully.
|
||||
///
|
||||
/// 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>:
|
||||
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`] implement
|
||||
/// for a given target that also implement [`CompileShader`] for that target.
|
||||
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,
|
||||
>,
|
||||
{
|
||||
}
|
||||
|
@ -78,10 +94,23 @@ where
|
|||
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
|
||||
self.backend.compile(options)
|
||||
}
|
||||
|
||||
fn compile_boxed(
|
||||
self: Box<Self>,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
|
||||
self.backend.compile(options)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -113,4 +142,56 @@ where
|
|||
) -> Result<ShaderReflection, ShaderReflectError> {
|
||||
self.backend.reflect(pass_number, semantics)
|
||||
}
|
||||
|
||||
fn validate(&mut self) -> Result<(), ShaderReflectError> {
|
||||
self.backend.validate()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ReflectShader + ?Sized> ReflectShader for Box<T> {
|
||||
fn reflect(
|
||||
&mut self,
|
||||
pass_number: usize,
|
||||
semantics: &ShaderSemantics,
|
||||
) -> Result<ShaderReflection, ShaderReflectError> {
|
||||
(**self).reflect(pass_number, semantics)
|
||||
}
|
||||
|
||||
fn validate(&mut self) -> Result<(), ShaderReflectError> {
|
||||
(**self).validate()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, T> CompileShader<T> for Box<O>
|
||||
where
|
||||
O: CompileShader<T> + ?Sized,
|
||||
T: OutputTarget,
|
||||
{
|
||||
type Options = O::Options;
|
||||
type Context = O::Context;
|
||||
|
||||
fn compile(
|
||||
self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
|
||||
O::compile_boxed(self, options)
|
||||
}
|
||||
|
||||
fn compile_boxed(
|
||||
self: Box<Self>,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
|
||||
self.compile(options)
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
|
101
librashader-reflect/src/back/msl.rs
Normal file
101
librashader-reflect/src/back/msl.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use crate::back::targets::MSL;
|
||||
use crate::back::{CompileReflectShader, 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 naga::back::msl::TranslationInfo;
|
||||
use naga::Module;
|
||||
|
||||
/// The MSL language version to target.
|
||||
pub use spirv_cross2::compile::msl::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_cross2::targets::Msl>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = CrossMslContext;
|
||||
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: MslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stable")]
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = CrossMslContext;
|
||||
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: Box::new(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,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FromCompilation<SpirvCompilation, Naga> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = NagaMslContext;
|
||||
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: NagaReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stable")]
|
||||
impl FromCompilation<SpirvCompilation, Naga> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = NagaMslContext;
|
||||
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: Box::new(NagaReflect::try_from(&compile)?),
|
||||
})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue