Compare commits

...

144 commits

Author SHA1 Message Date
Alex Janka 30bb747e94 shaders can be either a path or a string 2024-05-26 10:48:15 +10:00
Isaac Marovitz d7665cac9b Update wgpu 2024-05-02 17:05:00 -04:00
chyyran e8ffd8fdf3 chore: Release 2024-03-08 00:18:30 -05:00
chyyran d893b6ec97 doc: update supported runtime table to include D3D9 and clarify wgpu status 2024-03-07 21:45:19 -05:00
chyyran 8c8e386a6c capi(d3d9): expose d3d9 in capi 2024-03-07 21:09:15 -05:00
chyyran b7071958bd rt(d3d9): add a runtime for direct3d 9 2024-03-07 21:09:15 -05:00
chyyran 5feac91af2 runtime: add ARGB8 pixel format 2024-03-07 21:09:15 -05:00
chyyran 9c895caa51 wgpu: fix format mismatch for copies 2024-03-07 21:09:15 -05:00
chyyran 7593f9f9b5 rt: pass device context to uniform binder if needed 2024-03-07 21:09:15 -05:00
chyyran af3ea252ba rt(d3d11): split luts out to own module 2024-03-07 21:09:15 -05:00
chyyran 9dc0cf26fd chore: Release 2024-03-05 19:04:14 -05:00
chyyran 2550bf7ed5 test: update shaders_slang 2024-03-05 19:03:54 -05:00
chyyran 35fc21bbef capi: fix mtl noop free fn name 2024-03-05 19:01:35 -05:00
chyyran 89b620a7c1 rt(gl46): fix invalid framebuffer copies 2024-03-05 19:01:35 -05:00
chyyran 66a0ee21e3 Revert "rt(gl): account for flipped coordinate space when blitting to output"
This reverts commit 623c6776f7.
2024-03-05 19:01:35 -05:00
chyyran 31b7a6f33f chore: Release 2024-03-03 13:21:29 -05:00
chyyran ed7216990a test: update slang_shaders 2024-03-03 13:19:55 -05:00
chyyran 623c6776f7 rt(gl): account for flipped coordinate space when blitting to output 2024-03-03 13:17:28 -05:00
chyyran bacfbf0791 cache: delete cache if corrupted 2024-03-03 13:17:28 -05:00
chyyran e02e1ae26a rt(gl): load luts with topleft origin due to MVP changes 2024-03-03 13:17:28 -05:00
chyyran d72519b9fd chore: Release 2024-03-01 01:41:50 -05:00
chyyran 752417f320 rt(vk): switch from an rwlock to a mutex for allocator 2024-02-28 18:30:59 -05:00
chyyran 10ad2d927c rt(d3d12): make descriptor heap lockfree 2024-02-28 18:30:59 -05:00
chyyran 8a9adebb96 rt(d3d12): upgrade to windows 0.52 2024-02-26 21:17:30 -05:00
chyyran 7719b939f9 rt(d3d11): upgrade to windows 0.52 2024-02-26 21:17:30 -05:00
chyyran a849f5e745 dep: unify on windows 0.52.0 2024-02-26 21:17:30 -05:00
chyyran f61bed3a22 vk: remove unneeded device arcs 2024-02-26 00:22:55 -05:00
chyyran 4ef4b8762b chore: Release 2024-02-24 12:38:13 -05:00
chyyran a5c684a7ee ci: parallelize tests 2024-02-24 12:26:37 -05:00
chyyran 8ba4b72cf1 ci: update github-ci and suppress info logs 2024-02-24 12:26:37 -05:00
chyyran 7a3a690166 reflect: improve error messages 2024-02-24 12:26:37 -05:00
chyyran b5bc3c11e1 ci: emit github warnings on failure 2024-02-24 12:26:37 -05:00
chyyran c7dd7796db ci: switch to dtolnay/rust-toolchain 2024-02-24 12:26:37 -05:00
chyyran 9741ab2cd1 ci: add a full test suite for reflecting things 2024-02-24 12:26:37 -05:00
chyyran b378c45039 lib: reexport MSL target 2024-02-24 01:48:55 -05:00
chyyran c7d1d347a4 rt: unify drawquad type 2024-02-22 01:16:42 -05:00
chyyran 95ac8adc20 rt(d3d12,d3d11): remove unused COLOR attribute in quad 2024-02-22 01:16:42 -05:00
chyyran 372315022d ci: pin toolchain used for aarch64-pc-windows-msvc 2024-02-20 19:26:43 -05:00
chyyran 699243c0ab dep: update Cargo.lock 2024-02-20 19:26:43 -05:00
chyyran 6d25a653a9 reflect: remove unused spirv-linker dependency 2024-02-20 19:26:43 -05:00
chyyran be11953516 build: allow passing arbitrary cargo flags 2024-02-20 19:26:43 -05:00
chyyran e38f2636d9 ci: build for aarch64-windows 2024-02-20 19:26:43 -05:00
chyyran f073c76ade chore: Release 2024-02-19 16:19:05 -05:00
chyyran 7ef3780222 fmt: cargo fmt 2024-02-19 12:39:44 -05:00
chyyran d60ff76fb2 ci: unify build workflow 2024-02-19 12:37:46 -05:00
chyyran 50aa582fa8 reflect(d3d12): fakesign dxil blobs to avoid needing dxil.dll 2024-02-19 09:54:19 -05:00
chyyran e8eee02bfb reflect: always link and trim unused inouts 2024-02-19 09:54:19 -05:00
chyyran 22aa59b598 reflect: move spirv_passes out of naga 2024-02-19 09:54:19 -05:00
chyyran b75a614873 doc(reflect): document ShaderReflectObject 2024-02-19 09:54:19 -05:00
chyyran a8ae407ddb chore: Release 2024-02-17 03:01:28 -05:00
Ronny Chan fe84e6a490 rt(gl): use identity matrix for intermediate GL passes 2024-02-17 03:00:53 -05:00
chyyran 913ede3852 rt(wgpu): set viewport depth to 0-1 2024-02-17 02:55:09 -05:00
chyyran 1f5b4380a3 chore: Release 2024-02-16 18:18:33 -05:00
chyyran ce3a8c6e52 doc: document msrv 2024-02-16 18:14:38 -05:00
chyyran c22328f025 build: revert ahash to 0.8.7 2024-02-16 18:07:41 -05:00
chyyran af49128ee7 chore: Release 2024-02-16 01:53:56 -05:00
chyyran d3d8e85461 capi: replace unstable library features with polyfills 2024-02-16 01:19:52 -05:00
chyyran 017a1a6232 fmt: clean up imports and features 2024-02-16 01:19:52 -05:00
chyyran e622479c76 build: ignore rust-version if using RUSTC_BOOTSTRAP 2024-02-16 01:19:52 -05:00
chyyran b47b27fadb rt(vk): update gpu-allocator 2024-02-16 01:19:52 -05:00
chyyran ba6c32e858 rt(wgpu): enable large thread size if possible 2024-02-15 21:43:02 -05:00
chyyran edca0f1749 rt(mtl): only gen mipmaps if the level count is greater than 1 2024-02-15 21:43:02 -05:00
chyyran efdfd56e0e rt(mtl): don't allow buffers of size 0 2024-02-15 21:43:02 -05:00
chyyran c0ecae844c reflect(wgsl): link spirv to remove unused input/outputs 2024-02-15 21:43:02 -05:00
chyyran cbac011969 reflect(wgsl): only analyze active ubo members 2024-02-15 21:43:02 -05:00
chyyran 350508a7aa preprocess: strip #pragma parameter 2024-02-15 21:43:02 -05:00
chyyran d6f1af8691 rt: fix tests 2024-02-15 21:43:02 -05:00
chyyran adeb9435fc ci: build on macos-14 runner 2024-02-14 21:41:08 -05:00
chyyran 499b8f5791 ci: make package-obs require approval from maintainers 2024-02-14 21:41:08 -05:00
chyyran e944330692 docs: add halfbrown as direct dependency of librashader for docs rendering 2024-02-14 21:28:21 -05:00
chyyran f7dd955c0a chore: Release 2024-02-14 20:51:57 -05:00
chyyran 227e6e743d pkg: remove runtime dependencies 2024-02-14 20:50:17 -05:00
chyyran 6fbc4b3075 fmt: cargo fmt 2024-02-14 20:50:17 -05:00
chyyran 05467c2c78 docs: update readme to include macOS binary refs 2024-02-14 20:50:17 -05:00
chyyran b7673de811 reflect: remove unneeded TAIT feature bound 2024-02-14 20:50:17 -05:00
chyyran 4247e64336 docs: update capi docs 2024-02-14 20:50:17 -05:00
chyyran b348e8591f lib: use a faster hashmap implementation 2024-02-14 20:50:17 -05:00
chyyran dc1ab35d89 doc: bump api version 2024-02-14 03:15:29 -05:00
chyyran 7f4a883288 doc: add some examples 2024-02-14 03:15:29 -05:00
chyyran a2987555a2 chore: Release 2024-02-14 03:08:40 -05:00
chyyran 3afcd6223c build(capi): dont enable all core features 2024-02-14 03:08:09 -05:00
chyyran fb62a1e3f4 build: downgrade ahash to 0.8.7 2024-02-14 03:08:09 -05:00
chyyran dca93a1310 test(mtl): add objc example 2024-02-14 03:08:09 -05:00
chyyran 4259b65ee0 doc(lib): hack to remove wgpu dependency on docsrs 2024-02-14 03:08:09 -05:00
chyyran f058134944 rt(wgpu): don't enable all backends by default 2024-02-14 03:08:09 -05:00
chyyran ad4e72f359 rt: use bytemuck::offset_of instead of std::mem::offset_of 2024-02-14 03:08:09 -05:00
chyyran 5c08205360 capi(ld): don't use designated initializer for loader 2024-02-14 03:08:09 -05:00
chyyran cc93e37701 reflect: get rid of redundant shader compiler argument 2024-02-14 03:08:09 -05:00
chyyran 76aa5ce4c6 capi(mtl): implement capi for metal 2024-02-14 03:08:09 -05:00
chyyran d89839be16 rt(msl): move main.rs test to tests folder 2024-02-14 03:08:09 -05:00
chyyran a1696813aa rt(mtl): optimize buffer usage on apple silicon gpus 2024-02-14 03:08:09 -05:00
chyyran 004b073b1a rt(mtl): reenable mipmaps 2024-02-14 03:08:09 -05:00
chyyran bceb0623a3 rt(mtl): implement clear textures 2024-02-14 03:08:09 -05:00
chyyran ab31abb3d7 capi(mtl): prep metal capi 2024-02-14 03:08:09 -05:00
chyyran 3b0531dc62 rt(mtl): rename librashader-runtime-metal to librashader-runtime-mtl to match existing convention and export from root crate 2024-02-14 03:08:09 -05:00
chyyran 363657deef rt(vk): make render passes the default, and dynamic rendering not.
This is technically a breaking change for the C API, but it doesn't break ABI.
If someone complains there is a migration guide anyways...
2024-02-14 03:08:09 -05:00
chyyran 05f634a9b9 rt(mtl): properly bind push buffer and select bgra8 in place of rgba8 2024-02-14 03:08:09 -05:00
chyyran 43da6e60c6 rt(mtl): do intermediate passes offscreen 2024-02-14 03:08:09 -05:00
chyyran 30dfa1a655 rt(mtl): update with new semantics 2024-02-14 03:08:09 -05:00
chyyran 325e39063a rt(mtl): move test to main 2024-02-14 03:08:09 -05:00
chyyran 5554703af7 rt(mtl): fix stride asssignment 2024-02-14 03:08:09 -05:00
chyyran a7b1682a37 rt(mtl): implement filter pass and filter chain logic 2024-02-14 03:08:09 -05:00
chyyran ba3154b92d rt(mtl): implement texture and buffer abstractions 2024-02-14 03:08:09 -05:00
chyyran 6780397d49 rt(mtl): fix build on windows 2024-02-14 03:08:09 -05:00
chyyran 1aedb1bea7 rt: auto-impl parameters 2024-02-14 03:08:09 -05:00
chyyran 8dc0e0d100 rt(mtl): set up pipeline objects and renderpass 2024-02-14 03:08:09 -05:00
chyyran f40df9a54a rt(mtl): drawquad 2024-02-14 03:08:09 -05:00
chyyran 12d55e928e rt(mtl): common + shaderset for metal 2024-02-14 03:08:09 -05:00
chyyran d5ef5904f3 cache: get rid of rusqlite to avoid a C dependency 2024-02-14 00:55:52 -05:00
chyyran aca5b5420c rt(gl): use struct for draw_quad 2024-02-13 02:01:20 -05:00
chyyran c121087348 rt(vk, wgpu): use structs for quad data 2024-02-13 02:01:20 -05:00
chyyran 2d98ebec1b rt(gl): fix tests 2024-02-13 02:01:20 -05:00
chyyran 849a749f1a reflect(cross-glsl): don't unset vertex input attributes 2024-02-13 02:01:20 -05:00
chyyran daf30c83c0 rt: add Rotation, TotalSubFrames, CurrentSubFrame uniform semantics 2024-02-12 01:58:05 -05:00
chyyran 3c3f024ef8 preset: add original scaletype
As defined in https://github.com/libretro/RetroArch/pull/15937
2024-02-12 01:58:05 -05:00
chyyran 4762055dc1 reflect(msl): naga msl implementation 2024-02-11 15:48:01 -05:00
chyyran d0a5224c10 dep: upgrade librashader-spirv-cross 2024-02-11 15:48:01 -05:00
chyyran 178790a202 ci: allow ubuntu to fail 2024-02-11 15:48:01 -05:00
chyyran 528dd1b53c reflect: fix some refs 2024-02-11 15:48:01 -05:00
chyyran a495b693a6 reflect(msl): implement spirv-cross msl 2024-02-11 15:48:01 -05:00
chyyran c67e9f4801 reflect: move folder structure around to be a little better 2024-02-11 15:48:01 -05:00
chyyran e1f62fc984 reflect: remove ShaderOutputCompiler and just delegate to FromCompilation 2024-02-11 15:48:01 -05:00
chyyran b98d86a940 reflect: allow specifying output toolchain 2024-02-11 15:48:01 -05:00
chyyran 252f685967 reflect: abstract away output compiler into its own trait 2024-02-11 15:48:01 -05:00
chyyran a7ca391ef6 reflect: abstract away input compiler from compilation 2024-02-11 15:48:01 -05:00
chyyran 11d12730eb rt(wgpu): don't use rayon on wasm32
doesn't build on wasm32 because missing glslang but that should be the only snag now
2024-02-11 15:48:01 -05:00
chyyran 4733831500 rt(vk): update winit in tests 2024-02-11 15:48:01 -05:00
chyyran abbec84594 rt(d3d12): rename quad_render to draw_quad 2024-02-11 15:48:01 -05:00
chyyran 12af3c3f3a chore: Release 2024-02-09 18:30:07 -05:00
Ronny Chan b9a6b869e3
Merge pull request #37 from SnowflakePowered/preset-context
Preset context
2024-02-09 18:29:11 -05:00
chyyran fa8ee5d143 build: fix cbindgen not finding ctypes
* removes capi internal interface from librashader crate
* adds missing noop impls in header
* fix build on rustc < 1.74
2024-02-09 18:02:30 -05:00
chyyran 8f89b3e720 doc(preset): document preset contexts 2024-02-09 03:07:23 -05:00
chyyran c34fa4195b chore: Release 2024-02-09 02:55:02 -05:00
chyyran 2fbc7f92da capi: add preset_ctx C API 2024-02-09 02:49:31 -05:00
chyyran 9732812b91 build: fix build script clap to 4.1.0 2024-02-09 02:02:13 -05:00
chyyran 4da6c98655 rt: add driver context + path context for FilterChain::load_with_path 2024-02-09 02:02:13 -05:00
chyyran a14b36e05b presets: initial preset contexts API 2024-02-09 02:02:13 -05:00
chyyran b2d8d084be test: update shaders_slang 2024-02-09 02:02:13 -05:00
chyyran f1524f6049 preset: initial work on context 2024-02-09 02:02:13 -05:00
Ronny Chan 6d4e6590de
Merge pull request #36 from LukeUsher/master
build: support building for macOS and non-linux unixes
2024-02-08 21:16:20 -05:00
chyyran e776ee2823 ci: only do OBS tasks on origin 2024-02-08 18:16:10 -05:00
chyyran 3d74f27d77 rt(vk): fix double free in RawVulkanBuffer 2024-02-08 18:16:10 -05:00
chyyran f9fdb93c0c dep: update glslang and bitflags 2024-02-08 18:16:10 -05:00
Luke Usher 7f0f985a14 support building for macOS and non-linux unixes
Also updates build.yml to add macOS runners
2024-02-08 16:31:05 +00:00
250 changed files with 16961 additions and 3734 deletions

View file

@ -1,44 +0,0 @@
name: build librashader-capi for ARM64 Linux
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "0 0 * * 6"
env:
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
matrix:
profile: ['debug', 'release', 'optimized']
fail-fast: false
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install nightly Rust
uses: actions-rs/toolchain@v1.0.6
with:
toolchain: nightly
override: true
target: aarch64-unknown-linux-gnu
- name: Install ARM64 cross-compilation dependencies
continue-on-error: true
run: |
sudo apt-get update || true
sudo dpkg --add-architecture arm64
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
sudo apt-get update || true
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
- name: Build dynamic library
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-unknown-linux-gnu
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('build-outputs-aarch64-unknown-linux-gnu-{0}', matrix.profile) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', matrix.profile) }}

View file

@ -1,4 +1,4 @@
name: build librashader-capi for x86_64
name: build librashader-capi
on:
push:
@ -14,22 +14,85 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
profile: ['debug', 'release', 'optimized']
os: ['windows-latest', 'ubuntu-latest', 'macos-latest', 'macos-14']
include:
- os: ubuntu-latest
output: x86_64-ubuntu
- os: windows-latest
output: x86_64-windows
- os: macos-latest
output: x86_64-macos
- os: macos-14
output: aarch64-macos
fail-fast: false
runs-on: ${{ matrix.os }}
name: ${{ matrix.output }} (${{ matrix.profile }})
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install nightly Rust
uses: actions-rs/toolchain@v1.0.6
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
override: true
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('build-outputs-{0}-{1}', matrix.os, matrix.profile) }}
name: ${{ format('librashader-{0}-{1}-{2}', matrix.output, github.sha, matrix.profile) }}
path: ${{ format('target/{0}/librashader.*', matrix.profile) }}
build-ubuntu-arm64:
strategy:
matrix:
profile: ['debug', 'release', 'optimized']
fail-fast: false
runs-on: ubuntu-latest
name: aarch64-ubuntu
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
targets: aarch64-unknown-linux-gnu
- name: Install ARM64 cross-compilation dependencies
continue-on-error: true
run: |
sudo apt-get update || true
sudo dpkg --add-architecture arm64
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
sudo apt-get update || true
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
- name: Build dynamic library
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-unknown-linux-gnu
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('librashader-aarch64-ubuntu-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', matrix.profile) }}
build-windows-arm64:
strategy:
matrix:
profile: ['debug', 'release', 'optimized']
fail-fast: false
runs-on: windows-latest
name: aarch64-windows
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-01-15 # pinned because it seems like there's a segfault on nightly
override: true
targets: aarch64-pc-windows-msvc
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-pc-windows-msvc
- name: Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('librashader-aarch64-windows-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader.*', matrix.profile) }}

View file

@ -1,29 +1,46 @@
name: build Linux packages with Open Build Service
on:
pull_request:
pull_request_target:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
approve-obs-build:
name: "approval"
runs-on: ubuntu-latest
steps:
- name: Approve
run: echo OBS build CI test runs need to be approved by a maintainer.
build-obs-binary:
environment:
name: obs-build-env
strategy:
matrix:
include:
- repo: Fedora_39
spec: librashader.spec
can_fail: true
name: Fedora 39 (.rpm)
- repo: xUbuntu_23.10
spec: librashader.spec
can_fail: true
name: Ubuntu 23.10 (.deb)
- repo: Arch
spec: PKGBUILD
can_fail: false
name: Arch (PKGBUILD)
runs-on: ubuntu-latest
needs: [approve-obs-build]
continue-on-error: ${{ matrix.can_fail }}
name: ${{ matrix.name }}
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}

83
.github/workflows/pr-full-test.yml vendored Normal file
View file

@ -0,0 +1,83 @@
name: integration test shader reflection
on:
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
approve-full-test:
name: "approval"
runs-on: ubuntu-latest
steps:
- name: Approve
run: echo Full test suite for PRs needs approval by a maintainer
test-presets:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [approve-full-test]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test preset processing
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture preprocess_all_slang_presets_parsed
test-naga:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test Naga reflection
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_wgsl_naga compile_all_slang_presets_msl_naga compile_all_slang_presets_spirv_naga
test-cross:
runs-on: ubuntu-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test SPIRV-Cross
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_msl_cross compile_all_slang_presets_glsl_cross compile_all_slang_presets_hlsl_cross compile_all_slang_presets_spirv_cross
test-dxil:
runs-on: windows-latest
continue-on-error: false
environment:
name: full-test
needs: [ approve-full-test ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
- name: Test DXIL
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_dxil_cross

View file

@ -8,22 +8,27 @@ env:
jobs:
build-obs-binary:
if: github.repository == 'SnowflakePowered/librashader'
strategy:
matrix:
include:
- repo: Fedora_39
spec: librashader.spec
can_fail: true
- repo: xUbuntu_23.10
spec: librashader.spec
can_fail: true
- repo: Arch
spec: PKGBUILD
can_fail: false
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.can_fail }}
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
@ -49,6 +54,7 @@ jobs:
osc build --no-verify --trust-all-projects ${{ matrix.repo }} x86_64 ${{ matrix.spec }}
publish-obs:
needs: build-obs-binary
if: github.repository == 'SnowflakePowered/librashader'
runs-on: ubuntu-latest
container: fedora:39
steps:

67
.github/workflows/push-full-test.yml vendored Normal file
View 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

1698
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,12 +10,16 @@ members = [
"librashader-runtime-d3d12",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-mtl",
"librashader-runtime-wgpu",
"librashader-cache",
"librashader-capi",
"librashader-build-script"
, "librashader-runtime-wgpu"]
"librashader-build-script", "librashader-runtime-d3d9"]
resolver = "2"
[workspace.dependencies]
windows = "0.52.0"
[workspace.metadata.release]
[profile.optimized]

View file

@ -11,24 +11,32 @@ librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for
![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 users can grab the latest DLL from [GitHub Releases](https://github.com/SnowflakePowered/librashader/releases).
For end-users, librashader is available from the [Open Build Service](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader) for a variety of Linux distributions and platforms.
Windows and macOS users can grab the latest binaries from [GitHub Releases](https://github.com/SnowflakePowered/librashader/releases).
## Supported Render APIs
librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU
are not currently supported (but pull-requests are welcome). librashader does not support legacy render
APIs such as older versions of OpenGL, or legacy versions of Direct3D.
librashader supports all modern graphics runtimes, including wgpu, Vulkan, OpenGL 3.3+ and 4.6 (with DSA),
Direct3D 11, Direct3D 12, and Metal.
librashader does not support legacy render APIs such as older versions of OpenGL or Direct3D, except for experimental
support for Direct3D 9.
| **API** | **Status** | **`librashader` feature** |
|-------------|------------|---------------------------|
| OpenGL 3.3+ | ✔ | `gl` |
| OpenGL 4.6 | ✔ | `gl` |
| Vulkan | ✔ | `vk` |
| Direct3D 11 | ✔ | `d3d11` |
| Direct3D 12 | ✔ | `d3d12` |
| wgpu | ✔ | `wgpu` |
| Metal | ❌ | |
| 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 &mdash; ❌ Render API is not supported
✅ Full Support &mdash; 🆗 Secondary Support &mdash; ⚠️ Experimental Support
wgpu may not support all shaders due to restrictions from WGSL. Direct3D 9 support is experimental and does not fully
support features such as previous frame feedback or history, as well as being unable to support shaders that need Direct3D 10+
only features.
## Usage
@ -37,20 +45,21 @@ The C API is geared more towards integration with existing projects. The Rust `l
of the internals if you wish to use parts of librashader piecemeal.
The librashader C API is best used by including `librashader_ld.h` in your project, which implements a loader that dynamically
loads the librashader (`librashader.so` or `librashader.dll`) implementation in the search path.
loads the librashader (`librashader.so`, `librashader.dll`, or `librashader.dylib`) implementation in the search path.
### C compatibility
The recommended way of integrating `librashader` is by the `librashader_ld` single header library which implements
a dynamic loader for `librashader.dll` / `librashader.so`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
for details on how librashader handles C ABI and API stability with regards to library updates.
a dynamic loader for `librashader.dll` / `librashader.so` / `librashader.dylib`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
for details on how librashader handles C ABI and API stability with regards to library updates. You can also link dynamically
with just `librashader.h` and the equivalent of `-lrashader`.
Linking statically against `librashader.h` is possible, but is not officially supported. You will need to ensure
linkage parameters are correct in order to successfully link with `librashader.lib` or `librashader.a`.
The [corrosion](https://github.com/corrosion-rs/) CMake package is highly recommended.
### Thread safety
In general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
Except for the Metal runtime, in general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
**external synchronization** of the filter chain object.
Filter chains can be created from any thread, but requires external synchronization of the graphics device queue where applicable
@ -62,8 +71,11 @@ OpenGL has an additional restriction where creating the filter chain instance in
the thread local OpenGL context is initialized to the same context as the drawing thread. Support for deferral of GPU resource initialization
is not available to OpenGL.
The Metal runtime is **not thread safe**. However you can still defer submission of GPU resource initialization through the
`filter_chain_create_deferred` function.
### Quad vertices and rotations
All runtimes except OpenGL render with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
All runtimes render intermediate passes with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
Quad VBO with range `[0, 1]` and the following projection matrix by default.
```rust
@ -78,17 +90,6 @@ static DEFAULT_MVP: &[f32; 16] = &[
As with RetroArch, a rotation on this MVP will be applied only on the final pass for these runtimes. This is the only way to
pass orientation information to shaders.
The OpenGL runtime uses a VBO for range `[0, 1]` for all passes and the following MVP for all passes.
```rust
static GL_DEFAULT_MVP: &[f32; 16] = &[
2.0, 0.0, 0.0, 0.0,
0.0, 2.0, 0.0, 0.0,
0.0, 0.0, 2.0, 0.0,
-1.0, -1.0, 0.0, 1.0,
];
```
### Building
For Rust projects, simply add the crate to your `Cargo.toml`.
@ -125,9 +126,12 @@ The following Rust examples show how to use each librashader runtime.
* [OpenGL](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-gl/tests/triangle.rs)
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d11/tests/triangle.rs)
* [Direct3D 12](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d12/tests/triangle.rs)
* [wgpu](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-wgpu/tests/hello_triangle.rs)
Some basic examples on using the C API are also provided in the [librashader-capi-tests](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
directory.
Some basic examples on using the C API are also provided.
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
* [Metal with Objective-C](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/objctest)
## Compatibility
@ -153,14 +157,16 @@ Please report an issue if you run into a shader that works in RetroArch, but not
* Sampler objects are used rather than `glTexParameter`.
* Sampler inputs and outputs are not renamed. This is useful for debugging shaders in RenderDoc.
* UBO and Push Constant Buffer sizes are padded to 16-byte boundaries.
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
* OpenGL 4.6+
* All caveats from the OpenGL 3.3+ section should be considered.
* Should work on OpenGL 4.5 but this is not guaranteed. The OpenGL 4.6 runtime may eventually switch to using `ARB_spirv_extensions` for loading shaders, and this will not be marked as a breaking change.
* The OpenGL 4.6 runtime uses Direct State Access to minimize changes to the OpenGL state. For GPUs released within the last 5 years, this may improve performance.
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
* Vulkan
* The Vulkan runtime uses [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html) by default.
This extension must be enabled at device creation. Explicit render passes can be used by configuring filter chain options, but may have reduced performance
compared to dynamic rendering.
* The Vulkan runtime can use [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html).
This extension must be enabled at device creation.
Dynamic rendering may have improved performance when enabled, and supported by the host hardware.
* Allocations within the runtime are done through [gpu-allocator](https://github.com/Traverse-Research/gpu-allocator) rather than handled manually.
* Direct3D 11
* Framebuffer copies are done via `ID3D11DeviceContext::CopySubresourceRegion` rather than a CPU conversion + copy.
@ -169,7 +175,9 @@ Please report an issue if you run into a shader that works in RetroArch, but not
which was released in late 2018.
* For maximum compatibility with shaders, a shader compile pipeline based on [`spirv-to-dxil`](https://github.com/SnowflakePowered/spirv-to-dxil-rs) is used, with the SPIRV-Cross HLSL pipeline used as a fallback.
This brings shader compatibility beyond what the RetroArch Direct3D 12 driver provides. The HLSL pipeline fallback may be removed in the future as `spirv-to-dxil` improves.
* The Direct3D 12 runtime requires `dxil.dll` and `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler).
* The Direct3D 12 runtime requires `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler), which may already be installed as part of Direct3D12. `dxil.dll` is not required.
* Metal
* The Metal runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's Metal driver uses only the final VBO.
Most, if not all shader presets should work fine on librashader. The runtime specific differences should not affect the output,
and are more a heads-up for integrating librashader into your project.
@ -177,7 +185,7 @@ and are more a heads-up for integrating librashader into your project.
## Versioning
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader)
![C ABI](https://img.shields.io/badge/ABI%20version-1-yellowgreen)
![C API](https://img.shields.io/badge/API%20version-0-blue)
![C API](https://img.shields.io/badge/API%20version-1-blue)
librashader typically follows [Semantic Versioning](https://semver.org/) with respect to the Rust API, where a minor version
@ -209,6 +217,15 @@ The `SONAME` of `librashader.so` when installed via package manager is set to `L
The above does not apply to releases of librashader prior to `0.1.0`, which were allowed to break API and ABI compatibility
in both the Rust and C API without an increase to either `LIBRASHADER_CURRENT_VERSION` or `LIBRASHADER_CURRENT_ABI`.
### MSRV Policy
While librashader requires nightly Rust, the following MSRV policy is enforced for unstable library features.
* Windows and macOS: **latest** nightly
* Linux: **1.70**
A CI job runs weekly to ensure librashader continues to build on nightly. Note that the MSRV is only intended to ease distribution on Linux and is allowed to change any time. It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
## License
The core parts of librashader such as the preprocessor, the preset parser,
the reflection library, and the runtimes, are all licensed under the Mozilla Public License version 2.0.
@ -218,13 +235,13 @@ are more permissively licensed, and may allow you to use librashader in your per
licensed or proprietary project.
To facilitate easier use of librashader in projects incompatible with MPL-2.0, `librashader_ld`
implements a loader which thunks its calls to any `librashader.so` or `librashader.dll`
implements a loader which thunks its calls to any `librashader.so`, `librashader.dll`, or `librashader.dylib`.
library found in the load path. A non-MPL-2.0 compatible project may link against
`librashader_ld` to use the librashader runtime, *provided that `librashader.so` or `librashader.dll`
`librashader_ld` to use the librashader runtime, *provided that `librashader.so`, `librashader.dll` or `librashader.dylib`
are distributed under the restrictions of MPLv2*.
Note that this means that if your project is unable to comply with the requirements of MPL-2.0,
you **can not distribute `librashader.so` or `librashader.dll`** alongside your project.
you **can not distribute `librashader.so`, `librashader.dll` or `librashader.dylib`** alongside your project.
The end user must obtain the implementation of librashader themselves. For more information,
see the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/).

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ publish = false
[dependencies]
cbindgen = "0.26.0"
clap = { version = "4.1.0", features = ["derive"] }
clap = { version = "=4.1.0", features = ["derive"] }
[package.metadata.release]

View file

@ -2,7 +2,7 @@ use clap::Parser;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, ExitCode};
use std::{env, fs};
#[derive(Parser, Debug)]
@ -12,12 +12,14 @@ struct Args {
profile: String,
#[arg(long, global = true)]
target: Option<String>,
#[arg(last = true)]
cargoflags: Vec<String>,
}
pub fn main() {
pub fn main() -> ExitCode {
// Do not update files on docsrs
if env::var("DOCS_RS").is_ok() {
return;
return ExitCode::SUCCESS;
}
let args = Args::parse();
@ -35,11 +37,23 @@ pub fn main() {
if profile == "debug" { "dev" } else { &profile }
));
// If we're on RUSTC_BOOTSTRAP, it's likely because we're building for a package..
if env::var("RUSTC_BOOTSTRAP").is_ok() {
cmd.arg("--ignore-rust-version");
}
if let Some(target) = &args.target {
cmd.arg(format!("--target={}", &target));
}
Some(cmd.status().expect("Failed to build librashader-capi"));
if !args.cargoflags.is_empty() {
cmd.args(args.cargoflags);
}
let status = cmd.status().expect("Failed to build librashader-capi");
if !status.success() {
return ExitCode::from(status.code().unwrap_or(1) as u8);
}
let mut output_dir = PathBuf::from(format!("target/{}", profile));
if let Some(target) = &args.target {
@ -66,7 +80,14 @@ pub fn main() {
.expect("Unable to write bindings.");
println!("Moving artifacts...");
if cfg!(target_os = "linux") {
if cfg!(target_os = "macos") {
let artifacts = &["liblibrashader_capi.dylib", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
let ext = ext.replace("_capi", "");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
}
} else if cfg!(target_family = "unix") {
let artifacts = &["liblibrashader_capi.so", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
@ -98,4 +119,6 @@ pub fn main() {
.unwrap();
}
}
return ExitCode::SUCCESS;
}

View file

@ -2,7 +2,7 @@
name = "librashader-cache"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,18 +12,18 @@ description = "RetroArch shaders for all."
[dependencies]
serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.7", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.7" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.7", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.7" }
platform-dirs = "0.3.0"
blake3 = { version = "1.3.3" }
thiserror = "1.0.38"
bincode = { version = "2.0.0-rc.2", features = ["serde"] }
rusqlite = { version = "0.28.0", features = ["bundled"] }
persy = "1.4.7"
bytemuck = "1.13.0"
[target.'cfg(windows)'.dependencies.windows]
version = "0.48.0"
workspace = true
features = [
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D_Fxc",
@ -35,7 +35,7 @@ optional = true
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"]

View file

@ -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)
)"#,
[],
)?;
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(conn)
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);
}
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),
let value = conn.get::<_, ByteVec>(index, &ByteVec::from(key))?.next();
Ok(value.map(|v| v.to_vec()))
}
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);
}
}

View file

@ -6,21 +6,29 @@ 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)?,
});
};
@ -33,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 })
@ -45,14 +55,18 @@ 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())
{
let Ok(()) =
crate::cache::internal::set_blob(&cache, "spirv", key.as_bytes(), &updated)
else {
return Ok(compilation);
};
}
Ok(compilation)
@ -60,53 +74,65 @@ impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize
}
#[cfg(all(target_os = "windows", feature = "d3d"))]
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;
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)
}

View file

@ -3,7 +3,7 @@ name = "librashader-capi"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -16,25 +16,50 @@ crate-type = [ "cdylib", "staticlib" ]
[features]
default = ["runtime-all" ]
runtime-all = ["runtime-opengl", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan"]
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
runtime-opengl = ["gl", "librashader/runtime-gl"]
runtime-d3d11 = ["windows", "librashader/runtime-d3d11", "windows/Win32_Graphics_Direct3D11"]
runtime-d3d12 = ["windows", "librashader/runtime-d3d12", "windows/Win32_Graphics_Direct3D12"]
runtime-d3d9 = ["windows", "librashader/runtime-d3d9", "windows/Win32_Graphics_Direct3D9"]
runtime-vulkan = ["ash", "librashader/runtime-vk"]
runtime-metal = ["__cbindgen_internal_objc", "librashader/runtime-metal"]
__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 = ["icrate", "objc2"]
[dependencies]
librashader = { path = "../librashader", version = "0.2.0-beta.7", 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", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
sptr = "0.3.2"
[dependencies.librashader]
path = "../librashader"
version = "0.2.7"
default-features = false
features = ["reflect", "presets", "preprocess"]
[target.'cfg(windows)'.dependencies.windows]
version = "0.48.0"
workspace = true
optional = true
[target.'cfg(target_vendor="apple")'.dependencies]
icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ], optional = true }
objc2 = { version = "0.5.0", features = ["apple"] , optional = true }
[package.metadata.docs.rs]
targets = ["x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu"]
targets = [ "x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"aarch64-apple-ios",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu", ]
features = ["librashader/docsrs"]

View file

@ -34,28 +34,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
after_includes = """
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11)
#include <d3d11.h>
#else
typedef void ID3D11Device;
typedef void ID3D11DeviceContext;
typedef void ID3D11RenderTargetView;
typedef void ID3D11ShaderResourceView;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
#include <vulkan\\vulkan.h>
#else
typedef int32_t VkFormat;
typedef uint64_t VkImage;
typedef void* VkPhysicalDevice;
typedef void* VkInstance;
typedef void* VkCommandBuffer;
#endif
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12)
#include <d3d12.h>
#else
typedef void ID3D12GraphicsCommandList;
typedef void ID3D12Device;
typedef void ID3D12Resource;
typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
#endif
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9)
#include <D3D9.h>
#endif
#if defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__)
#import <Metal/Metal.h>
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
#include <vulkan\\vulkan.h>
#endif
"""
@ -64,20 +54,20 @@ typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
"feature = runtime-vulkan" = "LIBRA_RUNTIME_VULKAN"
"feature = runtime-d3d11" = "LIBRA_RUNTIME_D3D11"
"feature = runtime-d3d12" = "LIBRA_RUNTIME_D3D12"
"feature = runtime-d3d9" = "LIBRA_RUNTIME_D3D9"
"feature = runtime-metal" = "LIBRA_RUNTIME_METAL"
"feature = __cbindgen_internal_objc" = "__OBJC__"
"target_os = windows" = "_WIN32"
"target_vendor = apple" = "__APPLE__"
[parse]
parse_deps = true
include = ["librashader",
"librashader-presets",
"librashader-preprocess",
"librashader-reflect",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-d3d11",
"librashader-runtime-d3d12",
]
expand = ["librashader-capi"]
parse_deps = false
include = ["librashader"]
[parse.expand]
crates = ["librashader-capi"]
features = ["__cbindgen_internal"]
[struct]
@ -99,6 +89,20 @@ include = [
"PFN_libra_preset_print",
"PFN_libra_preset_get_runtime_params",
"PFN_libra_preset_free_runtime_params",
"PFN_libra_preset_create_with_context",
"PFN_libra_preset_ctx_create",
"PFN_libra_preset_ctx_free",
"PFN_libra_preset_ctx_set_core_name",
"PFN_libra_preset_ctx_set_content_dir",
"PFN_libra_preset_ctx_set_param",
"PFN_libra_preset_ctx_set_core_rotation",
"PFN_libra_preset_ctx_set_user_rotation",
"PFN_libra_preset_ctx_set_screen_orientation",
"PFN_libra_preset_ctx_set_allow_rotation",
"PFN_libra_preset_ctx_set_view_aspect_orientation",
"PFN_libra_preset_ctx_set_core_aspect_orientation",
"PFN_libra_preset_ctx_set_runtime",
# error
"PFN_libra_error_errno",
@ -137,6 +141,14 @@ include = [
"PFN_libra_d3d11_filter_chain_get_active_pass_count",
"PFN_libra_d3d11_filter_chain_free",
# d3d11
"PFN_libra_d3d9_filter_chain_create",
"PFN_libra_d3d9_filter_chain_frame",
"PFN_libra_d3d9_filter_chain_set_param",
"PFN_libra_d3d9_filter_chain_get_param",
"PFN_libra_d3d9_filter_chain_set_active_pass_count",
"PFN_libra_d3d9_filter_chain_get_active_pass_count",
"PFN_libra_d3d9_filter_chain_free",
# d3d12
"PFN_libra_d3d12_filter_chain_create",
@ -147,17 +159,37 @@ include = [
"PFN_libra_d3d12_filter_chain_set_active_pass_count",
"PFN_libra_d3d12_filter_chain_get_active_pass_count",
"PFN_libra_d3d12_filter_chain_free",
# metal
"PFN_libra_mtl_filter_chain_create",
"PFN_libra_mtl_filter_chain_create_deferred",
"PFN_libra_mtl_filter_chain_frame",
"PFN_libra_mtl_filter_chain_set_param",
"PFN_libra_mtl_filter_chain_get_param",
"PFN_libra_mtl_filter_chain_set_active_pass_count",
"PFN_libra_mtl_filter_chain_get_active_pass_count",
"PFN_libra_mtl_filter_chain_free",
]
exclude = ["Option_ID3D11DeviceContext"]
exclude = [
"Option_ID3D11DeviceContext",
"PMTLCommandQueue",
"PMTLCommandBuffer",
"PMTLTexture"
]
[export.rename]
"LibrashaderError" = "_libra_error"
"ShaderPreset" = "_shader_preset"
"WildcardContext" = "_preset_ctx"
"FilterChainGL" = "_filter_chain_gl"
"FilterChainVulkan" = "_filter_chain_vk"
"FilterChainD3D11" = "_filter_chain_d3d11"
"FilterChainD3D12" = "_filter_chain_d3d12"
"FilterChainD3D9" = "_filter_chain_d3d9"
"FilterChainMetal" = "_filter_chain_mtl"
# vulkan renames
"PhysicalDevice" = "VkPhysicalDevice"
@ -174,6 +206,11 @@ 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 *"
@ -181,3 +218,7 @@ exclude = ["Option_ID3D11DeviceContext"]
"ID3D12Device" = "ID3D12Device *"
"ID3D12Resource" = "ID3D12Resource *"
"ID3D12GraphicsCommandList" = "ID3D12GraphicsCommandList *"
"PMTLCommandQueue" = "id<MTLCommandQueue>"
"PMTLCommandBuffer" = "id<MTLCommandBuffer>"
"PMTLTexture" = "id<MTLTexture>"

View file

@ -1,5 +1,6 @@
//! Binding types for the librashader C API.
use crate::error::LibrashaderError;
use librashader::presets::context::{Orientation, VideoDriver, WildcardContext};
use librashader::presets::ShaderPreset;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
@ -7,31 +8,122 @@ use std::ptr::NonNull;
/// A handle to a shader preset object.
pub type libra_shader_preset_t = Option<NonNull<ShaderPreset>>;
/// A handle to a preset wildcard context object.
pub type libra_preset_ctx_t = Option<NonNull<WildcardContext>>;
/// A handle to a librashader error object.
pub type libra_error_t = Option<NonNull<LibrashaderError>>;
/// An enum representing orientation for use in preset contexts.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_ORIENTATION {
Vertical = 0,
Horizontal,
}
impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
fn from(value: LIBRA_PRESET_CTX_ORIENTATION) -> Self {
match value {
LIBRA_PRESET_CTX_ORIENTATION::Vertical => Orientation::Vertical,
LIBRA_PRESET_CTX_ORIENTATION::Horizontal => Orientation::Horizontal,
}
}
}
// An enum representing graphics runtimes (video drivers) for use in preset contexts.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_RUNTIME {
None = 0,
GlCore,
Vulkan,
D3D11,
D3D12,
Metal,
}
impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
fn from(value: LIBRA_PRESET_CTX_RUNTIME) -> Self {
match value {
LIBRA_PRESET_CTX_RUNTIME::None => VideoDriver::None,
LIBRA_PRESET_CTX_RUNTIME::GlCore => VideoDriver::GlCore,
LIBRA_PRESET_CTX_RUNTIME::Vulkan => VideoDriver::Vulkan,
LIBRA_PRESET_CTX_RUNTIME::D3D11 => VideoDriver::Direct3D11,
LIBRA_PRESET_CTX_RUNTIME::D3D12 => VideoDriver::Direct3D12,
LIBRA_PRESET_CTX_RUNTIME::Metal => VideoDriver::Metal,
}
}
}
#[cfg(feature = "runtime-opengl")]
use librashader::runtime::gl::FilterChain as FilterChainGL;
/// A handle to a OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[doc(cfg(feature = "runtime-opengl"))]
pub type libra_gl_filter_chain_t = Option<NonNull<librashader::runtime::gl::capi::FilterChainGL>>;
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
/// A handle to a Direct3D 11 filter chain.
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
/// A handle to a Direct3D 11 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
pub type libra_d3d11_filter_chain_t =
Option<NonNull<librashader::runtime::d3d11::capi::FilterChainD3D11>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
pub type libra_d3d11_filter_chain_t = Option<NonNull<FilterChainD3D11>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
use librashader::runtime::d3d12::FilterChain as FilterChainD3D12;
/// A handle to a Direct3D 12 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
pub type libra_d3d12_filter_chain_t =
Option<NonNull<librashader::runtime::d3d12::capi::FilterChainD3D12>>;
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub type libra_d3d12_filter_chain_t = Option<NonNull<FilterChainD3D12>>;
/// A handle to a Direct3D 9 filter chain.
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
use librashader::runtime::d3d9::FilterChain as FilterChainD3D9;
/// A handle to a Direct3D 11 filter chain.
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
pub type libra_d3d9_filter_chain_t = Option<NonNull<FilterChainD3D9>>;
#[cfg(feature = "runtime-vulkan")]
use librashader::runtime::vk::FilterChain as FilterChainVulkan;
/// A handle to a Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
pub type libra_vk_filter_chain_t =
Option<NonNull<librashader::runtime::vk::capi::FilterChainVulkan>>;
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(
target_vendor = "apple",
feature = "runtime-metal",
feature = "__cbindgen_internal_objc"
)
))]
pub type libra_mtl_filter_chain_t = Option<NonNull<FilterChainMetal>>;
/// Defines the output viewport for a rendered frame.
#[repr(C)]
@ -87,3 +179,39 @@ macro_rules! config_struct {
pub(crate) use config_set_field;
pub(crate) use config_struct;
pub(crate) use config_version_set;
#[doc(hidden)]
#[deny(deprecated)]
#[deprecated = "Forward declarations for cbindgen, do not use."]
mod __cbindgen_opaque_forward_declarations {
macro_rules! typedef_struct {
($($(#[$($attrss:tt)*])* $name:ident;)*) => {
$($(#[$($attrss)*])*
#[allow(unused)]
#[doc(hidden)]
#[deny(deprecated)]
#[deprecated]
pub struct $name;
)*
};
}
typedef_struct! {
/// Opaque struct for a preset context.
WildcardContext;
/// Opaque struct for a shader preset.
ShaderPreset;
/// Opaque struct for an OpenGL filter chain.
FilterChainGL;
/// Opaque struct for a Direct3D 11 filter chain.
FilterChainD3D11;
/// Opaque struct for a Direct3D 12 filter chain.
FilterChainD3D12;
/// Opaque struct for a Direct3D 9 filter chain.
FilterChainD3D9;
/// Opaque struct for a Vulkan filter chain.
FilterChainVulkan;
/// Opaque struct for a Metal filter chain.
FilterChainMetal;
}
}

View file

@ -5,7 +5,7 @@ use std::mem::MaybeUninit;
use std::ptr::NonNull;
use thiserror::Error;
/// The error type for librashader.
/// The error type for librashader C API.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum LibrashaderError {
@ -37,10 +37,18 @@ pub enum LibrashaderError {
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[error("There was an error in the D3D12 filter chain.")]
D3D12FilterError(#[from] librashader::runtime::d3d12::error::FilterChainError),
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[error("There was an error in the D3D9 filter chain.")]
D3D9FilterError(#[from] librashader::runtime::d3d9::error::FilterChainError),
#[cfg(feature = "runtime-vulkan")]
#[doc(cfg(feature = "runtime-vulkan"))]
#[error("There was an error in the Vulkan filter chain.")]
VulkanFilterError(#[from] librashader::runtime::vk::error::FilterChainError),
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
#[error("There was an error in the D3D12 filter chain.")]
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
}
/// Error codes for librashader error types.
@ -185,8 +193,12 @@ impl LibrashaderError {
LibrashaderError::D3D11FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
LibrashaderError::D3D12FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
LibrashaderError::D3D9FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(feature = "runtime-vulkan")]
LibrashaderError::VulkanFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
LibrashaderError::MetalFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
}
}
pub(crate) const fn ok() -> libra_error_t {
@ -200,12 +212,12 @@ impl LibrashaderError {
macro_rules! assert_non_null {
($value:ident) => {
if $value.is_null() || !$value.is_aligned() {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
};
(noexport $value:ident) => {
if $value.is_null() || !$value.is_aligned() {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));

View file

@ -222,5 +222,25 @@ macro_rules! extern_fn {
};
}
pub fn boxed_slice_into_raw_parts<T>(vec: Box<[T]>) -> (*mut T, usize) {
let mut me = ManuallyDrop::new(vec);
(me.as_mut_ptr(), me.len())
}
pub unsafe fn boxed_slice_from_raw_parts<T>(ptr: *mut T, len: usize) -> Box<[T]> {
unsafe { Box::from_raw(std::slice::from_raw_parts_mut(ptr, len)) }
}
#[allow(unstable_name_collisions)]
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
use sptr::Strict;
let align = std::mem::align_of::<T>();
if !align.is_power_of_two() {
panic!("is_aligned_to: align is not a power-of-two");
}
ptr.addr() & (align - 1) == 0
}
pub(crate) use extern_fn;
pub(crate) use ffi_body;
use std::mem::ManuallyDrop;

View file

@ -5,7 +5,7 @@
//! possible by linking against `librashader.h` as well as any static libraries used by `librashader`.
//!
//! ## Usage
//! ⚠ Rust consumers use [librashader](https://docs.rs/librashader/) directly instead. ⚠
//! ⚠ Rust consumers should use [librashader](https://docs.rs/librashader/) directly instead. ⚠
//!
//! The librashader C API is designed to be easy to use and safe. Most objects are only accessible behind an opaque pointer.
//! Every allocated object can be freed with a corresponding `free` function **for that specific object type**.
@ -54,20 +54,21 @@
//!
//! ## Thread safety
//!
//! In general, it is **safe** to create a filter chain instance from a different thread, but drawing filter passes must be
//! synchronized externally. The exception to filter chain creation are in OpenGL, where creating the filter chain instance
//! is safe **if and only if** the thread local OpenGL context is initialized to the same context as the drawing thread, and
//! in Direct3D 11, where filter chain creation is unsafe if the `ID3D11Device` was created with
//! `D3D11_CREATE_DEVICE_SINGLETHREADED`.
//! Except for the metal runtime, it is in general, **safe** to create a filter chain instance from a different thread,
//! but drawing filter passes must be synchronized externally. The exception to filter chain creation are in OpenGL,
//! where creating the filter chain instance is safe **if and only if** the thread local OpenGL context is initialized
//! to the same context as the drawing thread, and in Direct3D 11, where filter chain creation is unsafe
//! if the `ID3D11Device` was created with `D3D11_CREATE_DEVICE_SINGLETHREADED`. Metal is entirely thread unsafe.
//!
//! You must ensure that only thread has access to a created filter pass **before** you call `*_frame`. `*_frame` may only be
//! called from one thread at a time.
#![allow(non_camel_case_types)]
#![feature(try_blocks)]
#![feature(pointer_is_aligned)]
#![feature(vec_into_raw_parts)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(deprecated)]
extern crate alloc;
pub mod ctypes;
pub mod error;
@ -80,6 +81,7 @@ pub mod reflect;
pub mod runtime;
pub mod version;
pub mod wildcard;
pub use version::LIBRASHADER_ABI_VERSION;
pub use version::LIBRASHADER_API_VERSION;

View file

@ -1,5 +1,5 @@
//! librashader preset C API (`libra_preset_*`).
use crate::ctypes::libra_shader_preset_t;
use crate::ctypes::{libra_preset_ctx_t, libra_shader_preset_t};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::presets::ShaderPreset;
@ -55,7 +55,6 @@ extern_fn! {
let filename = unsafe { CStr::from_ptr(filename) };
let filename = filename.to_str()?;
println!("loading {filename}");
let preset = ShaderPreset::try_parse(filename)?;
unsafe {
@ -66,6 +65,49 @@ extern_fn! {
}
}
extern_fn! {
/// Load a preset with the given wildcard context.
///
/// The wildcard context is immediately invalidated and must be recreated after
/// the preset is created.
///
/// Path information variables `PRESET_DIR` and `PRESET` will automatically be filled in.
/// ## Safety
/// - `filename` must be either null or a valid, aligned pointer to a string path to the shader preset.
/// - `context` must be either null or a valid, aligned pointer to a initialized `libra_preset_ctx_t`.
/// - `context` is invalidated after this function returns.
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_shader_preset_t`.
/// ## Returns
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
fn libra_preset_create_with_context(
filename: *const c_char,
context: *mut libra_preset_ctx_t,
out: *mut MaybeUninit<libra_shader_preset_t>
) {
assert_non_null!(filename);
assert_non_null!(context);
assert_non_null!(out);
let filename = unsafe { CStr::from_ptr(filename) };
let filename = filename.to_str()?;
let mut context = unsafe {
let context_ptr = &mut *context;
let context = context_ptr.take();
Box::from_raw(context.unwrap().as_ptr())
};
context.add_path_defaults(filename);
let preset = ShaderPreset::try_parse_with_context(filename, *context)?;
unsafe {
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
preset,
)))))
}
}
}
extern_fn! {
/// Free the preset.
///
@ -73,7 +115,7 @@ extern_fn! {
/// null.
///
/// ## Safety
/// - `preset` must be a valid and aligned pointer to a shader preset.
/// - `preset` must be a valid and aligned pointer to a `libra_shader_preset_t`.
fn libra_preset_free(preset: *mut libra_shader_preset_t) {
assert_non_null!(preset);
unsafe {
@ -88,7 +130,7 @@ extern_fn! {
/// Set the value of the parameter in the preset.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_set_param(
preset: *mut libra_shader_preset_t,
@ -135,7 +177,7 @@ extern_fn! {
/// Pretty print the shader preset.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
fn libra_preset_print(preset: *mut libra_shader_preset_t) |preset| {
assert_some_ptr!(preset);
println!("{preset:#?}");
@ -146,7 +188,7 @@ extern_fn! {
/// Get a list of runtime parameters.
///
/// ## Safety
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
/// - `out` must be an aligned pointer to a `libra_preset_parameter_list_t`.
/// - The output struct should be treated as immutable. Mutating any struct fields
/// in the returned struct may at best cause memory leaks, and at worse
@ -176,12 +218,15 @@ extern_fn! {
step: param.step
})
}
let (parts, len, cap) = values.into_raw_parts();
let values = values.into_boxed_slice();
let (parts, len) = crate::ffi::boxed_slice_into_raw_parts(values);
unsafe {
out.write(MaybeUninit::new(libra_preset_param_list_t {
parameters: parts,
length: len as u64,
_internal_alloc: cap as u64,
_internal_alloc: 0,
}));
}
}
@ -209,9 +254,9 @@ extern_fn! {
/// in undefined behaviour.
fn libra_preset_free_runtime_params(preset: libra_preset_param_list_t) {
unsafe {
let values = Vec::from_raw_parts(preset.parameters.cast_mut(),
preset.length as usize,
preset._internal_alloc as usize);
let values =
crate::ffi::boxed_slice_from_raw_parts(preset.parameters.cast_mut(),
preset.length as usize).into_vec();
for value in values {
let name = CString::from_raw(value.name.cast_mut());

View file

@ -6,8 +6,8 @@ use librashader::reflect::targets::SPIRV;
use librashader::reflect::{CompileShader, ReflectShader, ShaderCompilerOutput, ShaderReflection};
use librashader::{FilterMode, WrapMode};
use librashader::reflect::cross::GlslangCompilation;
use librashader::reflect::helper::image::{Image, UVDirection, RGBA8};
use librashader::reflect::SpirvCompilation;
pub(crate) struct LookupTexture {
wrap_mode: WrapMode,
@ -38,8 +38,9 @@ impl FilterReflection {
let (passes, textures) = (preset.shaders, preset.textures);
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
Glslang,
SPIRV,
GlslangCompilation,
SpirvCompilation,
error::LibrashaderError,
>(passes, &textures)?;

View file

@ -3,7 +3,9 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::d3d11::{D3D11InputView, D3D11OutputView};
use librashader::runtime::d3d11::{
D3D11InputView, D3D11OutputView, FilterChain, FilterChainOptions, FrameOptions,
};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::{ManuallyDrop, MaybeUninit};
@ -13,9 +15,6 @@ use windows::Win32::Graphics::Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11RenderTargetView, ID3D11ShaderResourceView,
};
use librashader::runtime::d3d11::capi::options::FilterChainOptionsD3D11;
use librashader::runtime::d3d11::capi::options::FrameOptionsD3D11;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
@ -58,7 +57,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 +73,18 @@ pub struct frame_d3d11_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsD3D11 => frame_d3d11_opt_t {
impl FrameOptions => frame_d3d11_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -114,7 +120,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 +180,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,

View file

@ -13,11 +13,10 @@ use windows::Win32::Graphics::Direct3D12::{
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT;
use librashader::runtime::d3d12::capi::options::FilterChainOptionsD3D12;
use librashader::runtime::d3d12::capi::options::FrameOptionsD3D12;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::d3d12::{D3D12InputImage, D3D12OutputView};
use librashader::runtime::d3d12::{
D3D12InputImage, D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions,
};
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Direct3D 12 parameters for the source image.
@ -55,11 +54,18 @@ pub struct frame_d3d12_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsD3D12 => frame_d3d12_opt_t {
impl FrameOptions => frame_d3d12_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -84,7 +90,7 @@ pub struct filter_chain_d3d12_opt_t {
}
config_struct! {
impl FilterChainOptionsD3D12 => filter_chain_d3d12_opt_t {
impl FilterChainOptions => filter_chain_d3d12_opt_t {
0 => [force_hlsl_pipeline, force_no_mipmaps, disable_cache];
}
}
@ -136,7 +142,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 +194,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,

View file

@ -0,0 +1,259 @@
use crate::ctypes::{
config_struct, libra_d3d9_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ptr::NonNull;
use std::slice;
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::{FilterChainParameters, Viewport};
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct filter_chain_d3d9_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to explicitly disable mipmap
/// generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
/// Disable the shader object cache. Shaders will be
/// recompiled rather than loaded from the cache.
pub disable_cache: bool,
}
config_struct! {
impl FilterChainOptions => filter_chain_d3d9_opt_t {
0 => [force_no_mipmaps, disable_cache];
}
}
/// Options for each Direct3D 11 shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct frame_d3d9_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptions => frame_d3d9_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `device` must not be null.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_d3d9_filter_chain_create(
preset: *mut libra_shader_preset_t,
device: ManuallyDrop<IDirect3DDevice9>,
options: *const MaybeUninit<filter_chain_d3d9_opt_t>,
out: *mut MaybeUninit<libra_d3d9_filter_chain_t>
) {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset(
*preset,
&device,
options.as_ref(),
)?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
/// struct.
/// - `out` must not be null.
/// - `image` must not be null.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
nopanic fn libra_d3d9_filter_chain_frame(
chain: *mut libra_d3d9_filter_chain_t,
frame_count: usize,
image: ManuallyDrop<IDirect3DTexture9>,
viewport: libra_viewport_t,
out: ManuallyDrop<IDirect3DSurface9>,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d9_opt_t>
) mut |chain| {
assert_some_ptr!(mut chain);
let mvp = if mvp.is_null() {
None
} else {
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: ManuallyDrop::into_inner(out.clone()),
mvp,
};
let options = options.map(FromUninit::from_uninit);
unsafe {
chain.frame(ManuallyDrop::into_inner(image.clone()), &viewport, frame_count, options.as_ref())?;
}
}
}
extern_fn! {
/// Sets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d9_filter_chain_set_param(
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
}
extern_fn! {
/// Gets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d9_filter_chain_get_param(
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
assert_non_null!(param_name);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
}
}
}
extern_fn! {
/// Sets the number of active passes for this chain.
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_set_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
extern_fn! {
/// Gets the number of active passes for this chain.
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_get_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let value = chain.get_enabled_pass_count();
out.write(MaybeUninit::new(value as u32))
}
}
}
extern_fn! {
/// Free a d3d9 filter chain.
///
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_free(chain: *mut libra_d3d9_filter_chain_t) {
assert_non_null!(chain);
unsafe {
let chain_ptr = &mut *chain;
let chain = chain_ptr.take();
drop(Box::from_raw(chain.unwrap().as_ptr()))
};
}
}

View 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>();

View file

@ -3,7 +3,9 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::gl::{GLFramebuffer, GLImage};
use librashader::runtime::gl::{
FilterChain, FilterChainOptions, FrameOptions, GLFramebuffer, GLImage,
};
use std::ffi::CStr;
use std::ffi::{c_char, c_void, CString};
use std::mem::MaybeUninit;
@ -11,8 +13,6 @@ use std::ptr::NonNull;
use std::slice;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::gl::capi::options::FilterChainOptionsGL;
use librashader::runtime::gl::capi::options::FrameOptionsGL;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
@ -64,11 +64,18 @@ pub struct frame_gl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsGL => frame_gl_opt_t {
impl FrameOptions => frame_gl_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -92,7 +99,7 @@ pub struct filter_chain_gl_opt_t {
}
config_struct! {
impl FilterChainOptionsGL => filter_chain_gl_opt_t {
impl FilterChainOptions => filter_chain_gl_opt_t {
0 => [glsl_version, use_dsa, force_no_mipmaps, disable_cache];
}
}
@ -149,7 +156,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::gl::capi::FilterChainGL::load_from_preset(*preset, options.as_ref())?;
let chain = FilterChain::load_from_preset(*preset, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,

View file

@ -8,9 +8,33 @@ pub mod gl;
pub mod vk;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
pub mod d3d11;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
pub mod d3d9;
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub mod d3d12;
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(
target_vendor = "apple",
feature = "runtime-metal",
feature = "__cbindgen_internal_objc"
)
))]
pub mod mtl;

View file

@ -0,0 +1,319 @@
use crate::ctypes::{
config_struct, libra_mtl_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use icrate::Metal::{MTLCommandBuffer, MTLCommandQueue, MTLTexture};
use objc2::runtime::ProtocolObject;
use crate::LIBRASHADER_API_VERSION;
/// An alias to a `id<MTLCommandQueue>` protocol object pointer.
pub type PMTLCommandQueue = *const ProtocolObject<dyn MTLCommandQueue>;
/// An alias to a `id<MTLCommandBuffer>` protocol object pointer.
pub type PMTLCommandBuffer = *const ProtocolObject<dyn MTLCommandBuffer>;
/// An alias to a `id<MTLTexture>` protocol object pointer.
pub type PMTLTexture = *const ProtocolObject<dyn MTLTexture>;
/// Options for each Metal shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct frame_mtl_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptions => frame_mtl_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
/// Options for filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct filter_chain_mtl_opt_t {
/// The librashader API version.
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
}
config_struct! {
impl FilterChainOptions => filter_chain_mtl_opt_t {
0 => [force_no_mipmaps];
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `queue` must be valid for the command buffers
/// that `libra_mtl_filter_chain_frame` will write to.
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_mtl_filter_chain_create(
preset: *mut libra_shader_preset_t,
queue: PMTLCommandQueue,
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
) |queue| {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let queue = queue.as_ref();
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset(*preset, queue, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
extern_fn! {
/// Create the filter chain given the shader preset deferring and GPU-side initialization
/// to the caller. This function therefore requires no external synchronization of the device queue.
///
/// The shader preset is immediately invalidated and must be recreated after
/// the filter chain is created.
///
/// ## Safety:
/// - `queue` must be valid for the command buffers
/// that `libra_mtl_filter_chain_frame` will write to.
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
///
/// The provided command buffer must be ready for recording and contain no prior commands.
/// The caller is responsible for ending the command buffer and immediately submitting it to a
/// graphics queue. The command buffer must be completely executed before calling `libra_mtl_filter_chain_frame`.
fn libra_mtl_filter_chain_create_deferred(
preset: *mut libra_shader_preset_t,
queue: PMTLCommandQueue,
command_buffer: PMTLCommandBuffer,
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
) |queue, command_buffer| {
assert_non_null!(preset);
let preset = unsafe {
let preset_ptr = &mut *preset;
let preset = preset_ptr.take();
Box::from_raw(preset.unwrap().as_ptr())
};
let options = if options.is_null() {
None
} else {
Some(unsafe { options.read() })
};
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = FilterChain::load_from_preset_deferred(*preset,
queue,
command_buffer,
options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
)))))
}
}
}
extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
///
/// ## Safety
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_mtl_opt_t`
/// struct.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
nopanic fn libra_mtl_filter_chain_frame(
chain: *mut libra_mtl_filter_chain_t,
command_buffer: PMTLCommandBuffer,
frame_count: usize,
image: PMTLTexture,
viewport: libra_viewport_t,
output: PMTLTexture,
mvp: *const f32,
opt: *const MaybeUninit<frame_mtl_opt_t>
) |command_buffer, image, output|; mut |chain| {
assert_some_ptr!(mut chain);
let mvp = if mvp.is_null() {
None
} else {
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
};
let opt = if opt.is_null() {
None
} else {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output,
mvp,
};
chain.frame(&image, &viewport, command_buffer, frame_count, opt.as_ref())?;
}
}
extern_fn! {
/// Sets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_mtl_filter_chain_set_param(
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
value: f32
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
}
extern_fn! {
/// Gets a parameter for the filter chain.
///
/// If the parameter does not exist, returns an error.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_mtl_filter_chain_get_param(
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
}
}
}
extern_fn! {
/// Sets the number of active passes for this chain.
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_set_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
value: u32
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
extern_fn! {
/// Gets the number of active passes for this chain.
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_get_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
out: *mut MaybeUninit<u32>
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
unsafe {
out.write(MaybeUninit::new(value as u32))
}
}
}
extern_fn! {
/// Free a Metal filter chain.
///
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_free(
chain: *mut libra_mtl_filter_chain_t
) {
assert_non_null!(chain);
unsafe {
let chain_ptr = &mut *chain;
let chain = chain_ptr.take();
drop(Box::from_raw(chain.unwrap().as_ptr()))
};
}
}

View file

@ -0,0 +1,5 @@
//! C API for the librashader Metal Runtime (`libra_mtl_*`).
mod filter_chain;
pub use filter_chain::*;

View file

@ -3,20 +3,21 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use librashader::runtime::vk::{VulkanImage, VulkanInstance};
use librashader::runtime::vk::{
FilterChain, FilterChainOptions, FrameOptions, VulkanImage, VulkanInstance,
};
use std::ffi::CStr;
use std::ffi::{c_char, c_void};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
use librashader::runtime::vk::capi::options::FilterChainOptionsVulkan;
use librashader::runtime::vk::capi::options::FrameOptionsVulkan;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use ash::vk;
use crate::LIBRASHADER_API_VERSION;
pub use ash::vk::PFN_vkGetInstanceProcAddr;
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
@ -87,17 +88,24 @@ impl From<libra_device_vk_t> for VulkanInstance {
#[derive(Default, Debug, Clone)]
pub struct frame_vk_opt_t {
/// The librashader API version.
pub version: usize,
pub version: LIBRASHADER_API_VERSION,
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
pub total_subframes: u32,
/// The current sub frame. Default is 1.
pub current_subframe: u32,
}
config_struct! {
impl FrameOptionsVulkan => frame_vk_opt_t {
impl FrameOptions => frame_vk_opt_t {
0 => [clear_history, frame_direction];
1 => [rotation, total_subframes, current_subframe]
}
}
@ -106,22 +114,23 @@ config_struct! {
#[derive(Default, Debug, Clone)]
pub struct filter_chain_vk_opt_t {
/// The librashader API version.
pub version: usize,
pub version: LIBRASHADER_API_VERSION,
/// The number of frames in flight to keep. If zero, defaults to three.
pub frames_in_flight: u32,
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
/// Use explicit render pass objects It is recommended if possible to use dynamic rendering,
/// Use dynamic rendering over explicit render pass objects.
/// It is recommended if possible to use dynamic rendering,
/// because render-pass mode will create new framebuffers per pass.
pub use_render_pass: bool,
pub use_dynamic_rendering: bool,
/// Disable the shader object cache. Shaders will be
/// recompiled rather than loaded from the cache.
pub disable_cache: bool,
}
config_struct! {
impl FilterChainOptionsVulkan => filter_chain_vk_opt_t {
0 => [frames_in_flight, force_no_mipmaps, use_render_pass, disable_cache];
impl FilterChainOptions => filter_chain_vk_opt_t {
0 => [frames_in_flight, force_no_mipmaps, use_dynamic_rendering, disable_cache];
}
}
@ -133,7 +142,7 @@ extern_fn! {
///
/// ## Safety:
/// - The handles provided in `vulkan` must be valid for the command buffers that
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
/// `libra_vk_filter_chain_frame` will write to.
/// created with the `VK_KHR_dynamic_rendering` extension.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
@ -161,7 +170,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset(*preset, vulkan, options.as_ref())?;
let chain = FilterChain::load_from_preset(*preset, vulkan, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
@ -179,8 +188,7 @@ extern_fn! {
///
/// ## Safety:
/// - The handles provided in `vulkan` must be valid for the command buffers that
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
/// created with the `VK_KHR_dynamic_rendering` extension.
/// `libra_vk_filter_chain_frame` will write to.
/// - `preset` must be either null, or valid and aligned.
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
@ -212,7 +220,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset_deferred(*preset,
let chain = FilterChain::load_from_preset_deferred(*preset,
vulkan,
command_buffer,
options.as_ref())?;

View file

@ -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::*;

View file

@ -13,7 +13,11 @@ pub type LIBRASHADER_ABI_VERSION = usize;
/// versions must remain backwards compatible.
/// ## API Versions
/// - API version 0: 0.1.0
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
/// - API version 1: 0.2.0
/// - Added rotation, total_subframes, current_subframes to frame options
/// - Added preset context API
/// - Added Metal runtime API
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 1;
/// The current version of the librashader ABI.
/// Used by the loader to check ABI compatibility.

View file

@ -0,0 +1,263 @@
//! librashader preset wildcard context C API (`libra_preset_ctx_*`).
use crate::ctypes::{libra_preset_ctx_t, LIBRA_PRESET_CTX_ORIENTATION, LIBRA_PRESET_CTX_RUNTIME};
use crate::error::{assert_non_null, assert_some_ptr};
use librashader::presets::context::{
ContextItem, PresetExtension, Rotation, ShaderExtension, WildcardContext,
};
use std::ffi::{c_char, CStr};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use crate::ffi::extern_fn;
const _: () = crate::assert_thread_safe::<WildcardContext>();
extern_fn! {
/// Create a wildcard context
///
/// The C API does not allow directly setting certain variables
///
/// - `PRESET_DIR` and `PRESET` are inferred on preset creation.
/// - `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always set to `slang` and `slangp` for librashader.
/// - `VID-FINAL-ROT` is automatically calculated as the sum of `VID-USER-ROT` and `CORE-REQ-ROT` if either are present.
///
/// These automatically inferred variables, as well as all other variables can be overridden with
/// `libra_preset_ctx_set_param`, but the expected string values must be provided.
/// See https://github.com/libretro/RetroArch/pull/15023 for a list of expected string values.
///
/// No variables can be removed once added to the context, however subsequent calls to set the same
/// variable will overwrite the expected variable.
/// ## Safety
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_preset_ctx_t`.
/// ## Returns
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
fn libra_preset_ctx_create(
out: *mut MaybeUninit<libra_preset_ctx_t>
) {
assert_non_null!(out);
unsafe {
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
WildcardContext::new(),
)))));
}
}
}
extern_fn! {
/// Free the wildcard context.
///
/// If `context` is null, this function does nothing. The resulting value in `context` then becomes
/// null.
///
/// ## Safety
/// - `context` must be a valid and aligned pointer to a `libra_preset_ctx_t`
fn libra_preset_ctx_free(context: *mut libra_preset_ctx_t) {
assert_non_null!(context);
unsafe {
let context_ptr = &mut *context;
let context = context_ptr.take();
drop(Box::from_raw(context.unwrap().as_ptr()));
}
}
}
extern_fn! {
/// Set the core name (`CORE`) variable in the context
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_core_name(
context: *mut libra_preset_ctx_t,
name: *const c_char,
) |name|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreName(String::from(name)));
}
}
extern_fn! {
/// Set the content directory (`CONTENT-DIR`) variable in the context.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_content_dir(
context: *mut libra_preset_ctx_t,
name: *const c_char,
) |name|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::ContentDirectory(String::from(name)));
}
}
extern_fn! {
/// Set a custom string variable in context.
///
/// If the path contains this variable when loading a preset, it will be replaced with the
/// provided contents.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_param(
context: *mut libra_preset_ctx_t,
name: *const c_char,
value: *const c_char,
) |name, value|; mut |context| {
let name = unsafe {
CStr::from_ptr(name)
};
let name = name.to_str()?;
let value = unsafe {
CStr::from_ptr(value)
};
let value = value.to_str()?;
assert_some_ptr!(mut context);
context.append_item(ContextItem::ExternContext(String::from(name), String::from(value)));
}
}
extern_fn! {
/// Set the graphics runtime (`VID-DRV`) variable in the context.
///
/// Note that librashader only supports the following runtimes.
///
/// - Vulkan
/// - GLCore
/// - Direct3D11
/// - Direct3D12
///
/// This will also set the appropriate video driver extensions.
///
/// For librashader, `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always `slang` and `slangp`.
/// To override this, use `libra_preset_ctx_set_param`.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
/// - `name` must be null or a valid and aligned pointer to a string.
fn libra_preset_ctx_set_runtime(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_RUNTIME,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::VideoDriverPresetExtension(
PresetExtension::Slangp,
));
context.append_item(ContextItem::VideoDriverShaderExtension(
ShaderExtension::Slang,
));
context.append_item(ContextItem::VideoDriver(value.into()));
}
}
extern_fn! {
/// Set the core requested rotation (`CORE-REQ-ROT`) variable in the context.
///
/// Rotation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_core_rotation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreRequestedRotation(Rotation::from(value)))
}
}
extern_fn! {
/// Set the user rotation (`VID-USER-ROT`) variable in the context.
///
/// Rotation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_user_rotation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::UserRotation(Rotation::from(value)))
}
}
extern_fn! {
/// Set the screen orientation (`SCREEN-ORIENT`) variable in the context.
///
/// Orientation is represented by quarter rotations around the unit circle.
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_screen_orientation(
context: *mut libra_preset_ctx_t,
value: u32,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::ScreenOrientation(Rotation::from(value)))
}
}
extern_fn! {
/// Set whether or not to allow rotation (`VID-ALLOW-CORE-ROT`) variable in the context.
///
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_allow_rotation(
context: *mut libra_preset_ctx_t,
value: bool,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::AllowCoreRotation(value.into()))
}
}
extern_fn! {
/// Set the view aspect orientation (`VIEW-ASPECT-ORIENT`) variable in the context.
//////
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_view_aspect_orientation(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_ORIENTATION,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::ViewAspectOrientation(value.into()))
}
}
extern_fn! {
/// Set the core aspect orientation (`CORE-ASPECT-ORIENT`) variable in the context.
//////
/// ## Safety
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
fn libra_preset_ctx_set_core_aspect_orientation(
context: *mut libra_preset_ctx_t,
value: LIBRA_PRESET_CTX_ORIENTATION,
) mut |context| {
assert_some_ptr!(mut context);
context.append_item(ContextItem::CoreAspectOrientation(value.into()))
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-common"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,26 +14,35 @@ description = "RetroArch shaders for all."
[features]
default = []
opengl = ["gl"]
d3d9 = ["windows"]
d3d11 = ["windows", "dxgi"]
d3d12 = ["windows", "dxgi"]
dxgi = ["windows"]
vulkan = ["ash"]
wgpu = ["wgpu-types"]
metal = ["icrate"]
[dependencies]
gl = { version = "0.14.0", optional = true }
ash = { version = "0.37", optional = true }
wgpu-types = { version = "0.19.0", optional = true }
wgpu-types = { version = "0.20.0", optional = true }
num-traits = "0.2.15"
rustc-hash = "1.1.0"
halfbrown = "0.2.4"
[target.'cfg(windows)'.dependencies.windows]
optional = true
version = "0.48.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.icrate]
optional = true
version = "0.1.0"
features = ["Metal", "Metal_all"]

View file

@ -0,0 +1,77 @@
use crate::{FilterMode, ImageFormat, WrapMode};
use windows::Win32::Graphics::Direct3D9;
//
impl From<ImageFormat> for Direct3D9::D3DFORMAT {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => Direct3D9::D3DFMT_UNKNOWN,
ImageFormat::R8Unorm => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8Uint => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8Sint => Direct3D9::D3DFMT_R8G8B8,
ImageFormat::R8G8B8A8Unorm => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Uint => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Sint => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::R8G8B8A8Srgb => Direct3D9::D3DFMT_A8R8G8B8,
ImageFormat::A2B10G10R10UnormPack32 => Direct3D9::D3DFMT_A2B10G10R10,
ImageFormat::A2B10G10R10UintPack32 => Direct3D9::D3DFMT_A2B10G10R10,
ImageFormat::R16Sfloat => Direct3D9::D3DFMT_R16F,
ImageFormat::R16G16Uint => Direct3D9::D3DFMT_G16R16,
ImageFormat::R16G16Sint => Direct3D9::D3DFMT_G16R16,
ImageFormat::R16G16Sfloat => Direct3D9::D3DFMT_G16R16F,
ImageFormat::R16G16B16A16Uint => Direct3D9::D3DFMT_A16B16G16R16,
ImageFormat::R16G16B16A16Sint => Direct3D9::D3DFMT_A16B16G16R16,
ImageFormat::R16G16B16A16Sfloat => Direct3D9::D3DFMT_A16B16G16R16F,
ImageFormat::R32Sfloat => Direct3D9::D3DFMT_R32F,
_ => Direct3D9::D3DFMT_UNKNOWN,
}
}
}
//
impl From<Direct3D9::D3DFORMAT> for ImageFormat {
fn from(format: Direct3D9::D3DFORMAT) -> Self {
match format {
Direct3D9::D3DFMT_R8G8B8 => ImageFormat::R8Unorm,
Direct3D9::D3DFMT_A8R8G8B8 => ImageFormat::R8G8B8A8Unorm,
Direct3D9::D3DFMT_A2B10G10R10 => ImageFormat::A2B10G10R10UnormPack32,
Direct3D9::D3DFMT_R16F => ImageFormat::R16Sfloat,
Direct3D9::D3DFMT_G16R16 => ImageFormat::R16G16Uint,
Direct3D9::D3DFMT_G16R16F => ImageFormat::R16G16Sfloat,
Direct3D9::D3DFMT_A16B16G16R16 => ImageFormat::R16G16B16A16Uint,
Direct3D9::D3DFMT_A16B16G16R16F => ImageFormat::R16G16B16A16Sfloat,
Direct3D9::D3DFMT_R32F => ImageFormat::R32Sfloat,
_ => ImageFormat::Unknown,
}
}
}
impl From<WrapMode> for Direct3D9::D3DTEXTUREADDRESS {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => Direct3D9::D3DTADDRESS_BORDER,
WrapMode::ClampToEdge => Direct3D9::D3DTADDRESS_CLAMP,
WrapMode::Repeat => Direct3D9::D3DTADDRESS_WRAP,
WrapMode::MirroredRepeat => Direct3D9::D3DTADDRESS_MIRROR,
}
}
}
impl From<FilterMode> for Direct3D9::D3DTEXTUREFILTER {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => Direct3D9::D3DFILTER_LINEAR,
FilterMode::Nearest => Direct3D9::D3DFILTER_NEAREST,
}
}
}
// impl FilterMode {
// /// Get the mipmap filtering mode for the given combination.
// pub fn d3d9_mip(&self, mip: FilterMode) -> Direct3D9::D3DTEXTUREFILTER {
// match (self, mip) {
// (FilterMode::Linear, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPLINEAR,
// (FilterMode::Linear, FilterMode::Nearest) => Direct3D9::D3DFILTER_LINEARMIPNEAREST,
// (FilterMode::Nearest, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPNEAREST,
// _ => Direct3D9::D3DFILTER_MIPNEAREST
// }
// }
// }

View file

@ -16,6 +16,10 @@ pub mod wgpu;
#[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;
@ -24,14 +28,26 @@ pub mod d3d11;
#[cfg(all(target_os = "windows", feature = "d3d12"))]
pub mod d3d12;
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub mod metal;
mod viewport;
#[doc(hidden)]
pub mod map;
pub use viewport::Viewport;
use num_traits::AsPrimitive;
use std::convert::Infallible;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum ShaderStorage {
Path(std::path::PathBuf),
String(String),
}
#[repr(u32)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
/// Supported image formats for textures.

View file

@ -0,0 +1,5 @@
/// Fast optimized hash map type for small sets.
pub type FastHashMap<K, V> =
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
pub use halfbrown;

View file

@ -0,0 +1,92 @@
use crate::{FilterMode, ImageFormat, Size, WrapMode};
use icrate::Metal;
impl From<ImageFormat> for Metal::MTLPixelFormat {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => 0 as Metal::MTLPixelFormat,
ImageFormat::R8Unorm => Metal::MTLPixelFormatR8Unorm,
ImageFormat::R8Uint => Metal::MTLPixelFormatR8Uint,
ImageFormat::R8Sint => Metal::MTLPixelFormatR8Sint,
ImageFormat::R8G8Unorm => Metal::MTLPixelFormatRG8Unorm,
ImageFormat::R8G8Uint => Metal::MTLPixelFormatRG8Uint,
ImageFormat::R8G8Sint => Metal::MTLPixelFormatRG8Sint,
ImageFormat::R8G8B8A8Unorm => Metal::MTLPixelFormatRGBA8Unorm,
ImageFormat::R8G8B8A8Uint => Metal::MTLPixelFormatRGBA8Uint,
ImageFormat::R8G8B8A8Sint => Metal::MTLPixelFormatRGBA8Sint,
ImageFormat::R8G8B8A8Srgb => Metal::MTLPixelFormatRGBA8Unorm_sRGB,
ImageFormat::A2B10G10R10UnormPack32 => Metal::MTLPixelFormatRGB10A2Unorm,
ImageFormat::A2B10G10R10UintPack32 => Metal::MTLPixelFormatRGB10A2Uint,
ImageFormat::R16Uint => Metal::MTLPixelFormatR16Uint,
ImageFormat::R16Sint => Metal::MTLPixelFormatR16Sint,
ImageFormat::R16Sfloat => Metal::MTLPixelFormatR16Float,
ImageFormat::R16G16Uint => Metal::MTLPixelFormatRG16Uint,
ImageFormat::R16G16Sint => Metal::MTLPixelFormatRG16Sint,
ImageFormat::R16G16Sfloat => Metal::MTLPixelFormatRG16Float,
ImageFormat::R16G16B16A16Uint => Metal::MTLPixelFormatRGBA16Uint,
ImageFormat::R16G16B16A16Sint => Metal::MTLPixelFormatRGBA16Sint,
ImageFormat::R16G16B16A16Sfloat => Metal::MTLPixelFormatRGBA16Float,
ImageFormat::R32Uint => Metal::MTLPixelFormatR32Uint,
ImageFormat::R32Sint => Metal::MTLPixelFormatR32Sint,
ImageFormat::R32Sfloat => Metal::MTLPixelFormatR32Float,
ImageFormat::R32G32Uint => Metal::MTLPixelFormatRG32Uint,
ImageFormat::R32G32Sint => Metal::MTLPixelFormatRG32Sint,
ImageFormat::R32G32Sfloat => Metal::MTLPixelFormatRG32Float,
ImageFormat::R32G32B32A32Uint => Metal::MTLPixelFormatRGBA32Uint,
ImageFormat::R32G32B32A32Sint => Metal::MTLPixelFormatRGBA32Sint,
ImageFormat::R32G32B32A32Sfloat => Metal::MTLPixelFormatRGBA32Float,
}
}
}
impl From<Metal::MTLViewport> for Size<u32> {
fn from(value: Metal::MTLViewport) -> Self {
Size {
width: value.width as u32,
height: value.height as u32,
}
}
}
impl From<Size<u32>> for Metal::MTLViewport {
fn from(value: Size<u32>) -> Self {
Metal::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 Metal::MTLSamplerAddressMode {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => Metal::MTLSamplerAddressModeClampToBorderColor,
WrapMode::ClampToEdge => Metal::MTLSamplerAddressModeClampToEdge,
WrapMode::Repeat => Metal::MTLSamplerAddressModeRepeat,
WrapMode::MirroredRepeat => Metal::MTLSamplerAddressModeMirrorRepeat,
}
}
}
impl From<FilterMode> for Metal::MTLSamplerMinMagFilter {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => Metal::MTLSamplerMinMagFilterLinear,
_ => Metal::MTLSamplerMipFilterNearest,
}
}
}
impl FilterMode {
/// Get the mipmap filtering mode for the given combination.
pub fn mtl_mip(&self, mip: FilterMode) -> Metal::MTLSamplerMipFilter {
match self {
FilterMode::Linear => Metal::MTLSamplerMipFilterLinear,
FilterMode::Nearest => Metal::MTLSamplerMipFilterNearest,
}
}
}

View file

@ -94,58 +94,6 @@ impl From<wgpu_types::Extent3d> for Size<u32> {
}
}
//
// impl From<Size<u32>> for vk::Extent2D {
// fn from(value: Size<u32>) -> Self {
// vk::Extent2D {
// width: value.width,
// height: value.height,
// }
// }
// }
//
//
// impl From<vk::Extent2D> for Size<u32> {
// fn from(value: vk::Extent2D) -> Self {
// Size {
// width: value.width,
// height: value.height,
// }
// }
// }
//
// impl From<vk::Viewport> for Size<u32> {
// fn from(value: vk::Viewport) -> Self {
// Size {
// width: value.width as u32,
// height: value.height as u32,
// }
// }
// }
//
// impl From<&vk::Viewport> for Size<u32> {
// fn from(value: &vk::Viewport) -> Self {
// Size {
// width: value.width as u32,
// height: value.height as u32,
// }
// }
// }
//
// impl From<Size<u32>> for vk::Viewport {
// fn from(value: Size<u32>) -> Self {
// vk::Viewport {
// x: 0.0,
// y: 0.0,
// width: value.width as f32,
// height: value.height as f32,
// min_depth: 0.0,
// max_depth: 1.0,
// }
// }
// }
impl From<FilterMode> for wgpu_types::FilterMode {
fn from(value: FilterMode) -> Self {
match value {

View file

@ -3,7 +3,7 @@ name = "librashader-preprocess"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,8 +14,7 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.7" }
rustc-hash = "1.1.0"
librashader-common = { path = "../librashader-common", version = "0.2.7" }
encoding_rs = "0.8.31"
[features]
@ -23,6 +22,5 @@ default = [ "line_directives" ]
line_directives = []
[dev-dependencies]
librashader-presets = "0.1.0-rc.3"
glob = "0.3.1"
rayon = "1.6.1"

View file

@ -15,9 +15,8 @@ mod stage;
use crate::include::read_source;
pub use error::*;
use librashader_common::map::FastHashMap;
use librashader_common::ImageFormat;
use rustc_hash::FxHashMap;
use std::path::Path;
/// The source file for a single shader pass.
#[derive(Debug, Clone, PartialEq)]
@ -32,7 +31,7 @@ pub struct ShaderSource {
pub name: Option<String>,
/// The list of shader parameters found in the shader source.
pub parameters: FxHashMap<String, ShaderParameter>,
pub parameters: FastHashMap<String, ShaderParameter>,
/// The image format the shader expects.
pub format: ImageFormat,
@ -58,8 +57,8 @@ pub struct ShaderParameter {
impl ShaderSource {
/// Load the source file at the given path, resolving includes relative to the location of the
/// source file.
pub fn load(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
load_shader_source(path)
pub fn load(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
load_shader_source(file)
}
}
@ -78,11 +77,17 @@ impl SourceOutput for String {
}
}
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
let source = read_source(path)?;
pub(crate) fn load_shader_source(
file: &librashader_common::ShaderStorage,
) -> Result<ShaderSource, PreprocessError> {
let source = match file {
librashader_common::ShaderStorage::Path(path) => read_source(path)?,
librashader_common::ShaderStorage::String(s) => s.to_string(),
};
let meta = pragma::parse_pragma_meta(&source)?;
let text = stage::process_stages(&source)?;
let parameters = FxHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
let parameters = FastHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
Ok(ShaderSource {
vertex: text.vertex,

View file

@ -132,4 +132,16 @@ mod test {
step: 0.25
}, parse_parameter_string(r#"#pragma parameter exc "orizontal correction hack (games where players stay at center)" 0.0 -10.0 10.0 0.25"#).unwrap())
}
#[test]
fn parses_parameter_pragma_test() {
assert_eq!(ShaderParameter {
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".to_string(),
description: " Scanline Dir Multiplier".to_string(),
initial: 100.0,
minimum: 25.0,
maximum: 1600.0,
step: 25.0
}, parse_parameter_string(r#"#pragma parameter HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR " Scanline Dir Multiplier" 100 25 1600 25"#).unwrap())
}
}

View file

@ -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;
}

View file

@ -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
);
}
})
}
}
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,8 +15,14 @@ description = "RetroArch shaders for all."
thiserror = "1.0.37"
nom = "7.1.1"
nom_locate = "4.0.0"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.7" }
librashader-common = { path = "../librashader-common", version = "0.2.7" }
num-traits = "0.2"
once_cell = "1"
# we don't need unicode
regex = { version = "1", default-features = false, features = ["perf"] }
rustversion = "1.0"
os_str_bytes = { version = "6", features = ["conversions"] }
[features]
parse_legacy_glsl = []

View file

@ -0,0 +1,459 @@
// pub use librashader_presets_context::*;
//! Shader preset wildcard replacement context handling.
//!
//! Implements wildcard replacement of shader paths specified in
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
use librashader_common::map::FastHashMap;
use once_cell::sync::Lazy;
use regex::bytes::Regex;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Add;
use std::path::{Component, Path, PathBuf};
/// Valid video driver or runtime. This list is non-exhaustive.
#[repr(u32)]
#[non_exhaustive]
#[derive(Debug, Copy, Clone)]
pub enum VideoDriver {
/// None (`null`)
None = 0,
/// OpenGL Core (`glcore`)
GlCore,
/// Legacy OpenGL (`gl`)
Gl,
/// Vulkan (`vulkan`)
Vulkan,
/// Direct3D 9 (`d3d9_hlsl`)
Direct3D9Hlsl,
/// Direct3D 11 (`d3d11`)
Direct3D11,
/// Direct3D12 (`d3d12`)
Direct3D12,
/// Metal (`metal`)
Metal,
}
impl Display for VideoDriver {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
VideoDriver::None => f.write_str("null"),
VideoDriver::GlCore => f.write_str("glcore"),
VideoDriver::Gl => f.write_str("gl"),
VideoDriver::Vulkan => f.write_str("vulkan"),
VideoDriver::Direct3D11 => f.write_str("d3d11"),
VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
VideoDriver::Direct3D12 => f.write_str("d3d12"),
VideoDriver::Metal => f.write_str("metal"),
}
}
}
/// Valid extensions for shader extensions.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum ShaderExtension {
/// `.slang`
Slang = 0,
/// `.glsl`
Glsl,
/// `.cg`
Cg,
}
impl Display for ShaderExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ShaderExtension::Slang => f.write_str("slang"),
ShaderExtension::Glsl => f.write_str("glsl"),
ShaderExtension::Cg => f.write_str("cg"),
}
}
}
/// Valid extensions for shader presets
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum PresetExtension {
/// `.slangp`
Slangp = 0,
/// `.glslp`
Glslp,
/// `.cgp`
Cgp,
}
impl Display for PresetExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PresetExtension::Slangp => f.write_str("slangp"),
PresetExtension::Glslp => f.write_str("glslp"),
PresetExtension::Cgp => f.write_str("cgp"),
}
}
}
/// Rotation of the viewport.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Rotation {
/// Zero
Zero = 0,
/// 90 degrees
Right = 1,
/// 180 degrees
Straight = 2,
/// 270 degrees
Reflex = 3,
}
impl From<u32> for Rotation {
fn from(value: u32) -> Self {
let value = value % 4;
match value {
0 => Rotation::Zero,
1 => Rotation::Right,
2 => Rotation::Straight,
3 => Rotation::Reflex,
_ => unreachable!(),
}
}
}
impl Add for Rotation {
type Output = Rotation;
fn add(self, rhs: Self) -> Self::Output {
let lhs = self as u32;
let out = lhs + rhs as u32;
Rotation::from(out)
}
}
impl Display for Rotation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Rotation::Zero => f.write_str("0"),
Rotation::Right => f.write_str("90"),
Rotation::Straight => f.write_str("180"),
Rotation::Reflex => f.write_str("270"),
}
}
}
/// Orientation of aspect ratios
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Orientation {
/// Vertical orientation.
Vertical = 0,
/// Horizontal orientation.
Horizontal,
}
impl Display for Orientation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Orientation::Vertical => f.write_str("VERT"),
Orientation::Horizontal => f.write_str("HORZ"),
}
}
}
/// An item representing a variable that can be replaced in a path preset.
#[derive(Debug, Clone)]
pub enum ContextItem {
/// The content directory of the game (`CONTENT-DIR`)
ContentDirectory(String),
/// The name of the libretro core (`CORE`)
CoreName(String),
/// The filename of the game (`GAME`)
GameName(String),
/// The name of the preset (`PRESET`)
Preset(String),
/// The name of the preset directory (`PRESET_DIR`)
PresetDirectory(String),
/// The video driver (runtime) (`VID-DRV`)
VideoDriver(VideoDriver),
/// The extension of shader types supported by the driver (`VID-DRV-SHADER-EXT`)
VideoDriverShaderExtension(ShaderExtension),
/// The extension of shader presets supported by the driver (`VID-DRV-PRESET-EXT`)
VideoDriverPresetExtension(PresetExtension),
/// The rotation that the core is requesting (`CORE-REQ-ROT`)
CoreRequestedRotation(Rotation),
/// Whether or not to allow core-requested rotation (`VID-ALLOW-CORE-ROT`)
AllowCoreRotation(bool),
/// The rotation the user is requesting (`VID-USER-ROT`)
UserRotation(Rotation),
/// The final rotation (`VID-FINAL-ROT`) calculated by the sum of `VID-USER-ROT` and `CORE-REQ-ROT`
FinalRotation(Rotation),
/// The user-adjusted screen orientation (`SCREEN-ORIENT`)
ScreenOrientation(Rotation),
/// The orientation of the viewport aspect ratio (`VIEW-ASPECT-ORIENT`)
ViewAspectOrientation(Orientation),
/// The orientation of the aspect ratio requested by the core (`CORE-ASPECT-ORIENT`)
CoreAspectOrientation(Orientation),
/// An external, arbitrary context variable.
ExternContext(String, String),
}
impl ContextItem {
fn toggle_str(v: bool) -> &'static str {
if v {
"ON"
} else {
"OFF"
}
}
pub fn key(&self) -> &str {
match self {
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
ContextItem::CoreName(_) => "CORE",
ContextItem::GameName(_) => "GAME",
ContextItem::Preset(_) => "PRESET",
ContextItem::PresetDirectory(_) => "PRESET_DIR",
ContextItem::VideoDriver(_) => "VID-DRV",
ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
ContextItem::UserRotation(_) => "VID-USER-ROT",
ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
ContextItem::ExternContext(key, _) => key,
}
}
}
impl Display for ContextItem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ContextItem::ContentDirectory(v) => f.write_str(v),
ContextItem::CoreName(v) => f.write_str(v),
ContextItem::GameName(v) => f.write_str(v),
ContextItem::Preset(v) => f.write_str(v),
ContextItem::PresetDirectory(v) => f.write_str(v),
ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::CoreRequestedRotation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
"{}-{}",
self.key(),
ContextItem::toggle_str(*v)
)),
ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ViewAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::CoreAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
}
}
}
/// A preset wildcard context.
///
/// Any items added after will have higher priority
/// when passed to the shader preset parser.
///
/// When passed to the preset parser, the preset parser
/// will automatically add inferred items at lowest priority.
///
/// Any items added by the user will override the automatically
/// inferred items.
#[derive(Debug, Clone)]
pub struct WildcardContext(VecDeque<ContextItem>);
impl WildcardContext {
/// Create a new wildcard context.
pub fn new() -> Self {
Self(VecDeque::new())
}
/// Prepend an item to the context builder.
pub fn prepend_item(&mut self, item: ContextItem) {
self.0.push_front(item);
}
/// Append an item to the context builder.
/// The new item will take precedence over all items added before it.
pub fn append_item(&mut self, item: ContextItem) {
self.0.push_back(item);
}
/// Prepend sensible defaults for the given video driver.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
self.prepend_item(ContextItem::VideoDriverPresetExtension(
PresetExtension::Slangp,
));
self.prepend_item(ContextItem::VideoDriverShaderExtension(
ShaderExtension::Slang,
));
self.prepend_item(ContextItem::VideoDriver(video_driver));
}
/// Prepend default entries from the path of the preset.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
if let Some(preset_name) = path.file_stem() {
let preset_name = preset_name.to_string_lossy();
self.prepend_item(ContextItem::Preset(preset_name.into()))
}
if let Some(preset_dir_name) = path.parent().and_then(|p| {
if !p.is_dir() {
return None;
};
p.file_name()
}) {
let preset_dir_name = preset_dir_name.to_string_lossy();
self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
}
}
pub fn to_hashmap(mut self) -> FastHashMap<String, String> {
let mut map = FastHashMap::default();
let last_user_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::UserRotation(_)));
let last_core_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
let final_rot = match (last_core_rot, last_user_rot) {
(Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
(None, Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*c))
}
(Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*u + *c))
}
_ => None,
};
if let Some(final_rot) = final_rot {
self.prepend_item(final_rot);
}
for item in self.0 {
map.insert(String::from(item.key()), item.to_string());
}
map
}
}
#[rustversion::since(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use std::ffi::{OsStr, OsString};
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = path.as_encoded_bytes();
let replaced =
WILDCARD_REGEX.replace_all(haystack, |caps: &regex::bytes::Captures| {
let Some(name) = caps.get(1) else {
return caps[0].to_vec();
};
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
return caps[0].to_vec();
};
if let Some(replacement) = context.get(key) {
return OsString::from(replacement.to_string()).into_encoded_bytes();
}
return caps[0].to_vec();
});
// SAFETY: The original source is valid encoded bytes, and our replacement is
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
new_path.push(unsafe { OsStr::from_encoded_bytes_unchecked(&replaced.as_ref()) })
}
_ => new_path.push(component),
}
}
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
if let Ok(true) = new_path.try_exists() {
*path = new_path;
}
}
#[rustversion::before(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use os_str_bytes::RawOsStr;
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
let path_str = RawOsStr::new(path.as_os_str());
let path_bytes = path_str.to_raw_bytes();
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(&path_bytes) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = RawOsStr::new(path);
let haystack = haystack.to_raw_bytes();
let replaced =
WILDCARD_REGEX.replace_all(&haystack, |caps: &regex::bytes::Captures| {
let Some(name) = caps.get(1) else {
return caps[0].to_vec();
};
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
return caps[0].to_vec();
};
if let Some(replacement) = context.get(key) {
return RawOsStr::from_str(replacement).to_raw_bytes().to_vec();
}
return caps[0].to_vec();
});
// SAFETY: The original source is valid encoded bytes, and our replacement is
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
new_path.push(RawOsStr::assert_cow_from_raw_bytes(&replaced.as_ref()).to_os_str())
}
_ => new_path.push(component),
}
}
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
if let Ok(true) = new_path.try_exists() {
*path = new_path;
}
}

View file

@ -148,7 +148,7 @@ where
#[cfg(test)]
mod test {
use crate::MakeExtractIf;
use crate::extract_if::MakeExtractIf;
#[test]
fn drain_filter_empty() {
let mut vec: Vec<i32> = vec![];

View file

@ -8,12 +8,15 @@
//!
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
#![allow(stable_features)]
#![allow(unstable_name_collisions)]
pub mod context;
mod error;
mod extract_if;
mod parse;
mod preset;
pub use context::WildcardContext;
pub use error::*;
pub use preset::*;

View file

@ -0,0 +1,404 @@
//! Shader preset wildcard replacement context handling.
//!
//! Implements wildcard replacement of shader paths specified in
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
use once_cell::sync::Lazy;
use regex::bytes::Regex;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::ffi::{OsStr, OsString};
use std::fmt::{Debug, Display, Formatter};
use std::ops::Add;
use std::path::{Component, Path, PathBuf};
/// Valid video driver or runtime. This list is non-exhaustive.
#[repr(u32)]
#[non_exhaustive]
#[derive(Debug, Copy, Clone)]
pub enum VideoDriver {
/// None (`null`)
None = 0,
/// OpenGL Core (`glcore`)
GlCore,
/// Legacy OpenGL (`gl`)
Gl,
/// Vulkan (`vulkan`)
Vulkan,
/// Direct3D 9 (`d3d9_hlsl`)
Direct3D9Hlsl,
/// Direct3D 11 (`d3d11`)
Direct3D11,
/// Direct3D12 (`d3d12`)
Direct3D12,
/// Metal (`metal`)
Metal,
}
impl Display for VideoDriver {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
VideoDriver::None => f.write_str("null"),
VideoDriver::GlCore => f.write_str("glcore"),
VideoDriver::Gl => f.write_str("gl"),
VideoDriver::Vulkan => f.write_str("vulkan"),
VideoDriver::Direct3D11 => f.write_str("d3d11"),
VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
VideoDriver::Direct3D12 => f.write_str("d3d12"),
VideoDriver::Metal => f.write_str("metal"),
}
}
}
/// Valid extensions for shader extensions.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum ShaderExtension {
/// `.slang`
Slang = 0,
/// `.glsl`
Glsl,
/// `.cg`
Cg,
}
impl Display for ShaderExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ShaderExtension::Slang => f.write_str("slang"),
ShaderExtension::Glsl => f.write_str("glsl"),
ShaderExtension::Cg => f.write_str("cg"),
}
}
}
/// Valid extensions for shader presets
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum PresetExtension {
/// `.slangp`
Slangp = 0,
/// `.glslp`
Glslp,
/// `.cgp`
Cgp,
}
impl Display for PresetExtension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PresetExtension::Slangp => f.write_str("slangp"),
PresetExtension::Glslp => f.write_str("glslp"),
PresetExtension::Cgp => f.write_str("cgp"),
}
}
}
/// Rotation of the viewport.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Rotation {
/// Zero
Zero = 0,
/// 90 degrees
Right = 1,
/// 180 degrees
Straight = 2,
/// 270 degrees
Reflex = 3,
}
impl From<u32> for Rotation {
fn from(value: u32) -> Self {
let value = value % 4;
match value {
0 => Rotation::Zero,
1 => Rotation::Right,
2 => Rotation::Straight,
3 => Rotation::Reflex,
_ => unreachable!(),
}
}
}
impl Add for Rotation {
type Output = Rotation;
fn add(self, rhs: Self) -> Self::Output {
let lhs = self as u32;
let out = lhs + rhs as u32;
Rotation::from(out)
}
}
impl Display for Rotation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Rotation::Zero => f.write_str("0"),
Rotation::Right => f.write_str("90"),
Rotation::Straight => f.write_str("180"),
Rotation::Reflex => f.write_str("270"),
}
}
}
/// Orientation of aspect ratios
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum Orientation {
/// Vertical orientation.
Vertical = 0,
/// Horizontal orientation.
Horizontal,
}
impl Display for Orientation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Orientation::Vertical => f.write_str("VERT"),
Orientation::Horizontal => f.write_str("HORZ"),
}
}
}
/// An item representing a variable that can be replaced in a path preset.
#[derive(Debug, Clone)]
pub enum ContextItem {
/// The content directory of the game (`CONTENT-DIR`)
ContentDirectory(String),
/// The name of the libretro core (`CORE`)
CoreName(String),
/// The filename of the game (`GAME`)
GameName(String),
/// The name of the preset (`PRESET`)
Preset(String),
/// The name of the preset directory (`PRESET_DIR`)
PresetDirectory(String),
/// The video driver (runtime) (`VID-DRV`)
VideoDriver(VideoDriver),
/// The extension of shader types supported by the driver (`VID-DRV-SHADER-EXT`)
VideoDriverShaderExtension(ShaderExtension),
/// The extension of shader presets supported by the driver (`VID-DRV-PRESET-EXT`)
VideoDriverPresetExtension(PresetExtension),
/// The rotation that the core is requesting (`CORE-REQ-ROT`)
CoreRequestedRotation(Rotation),
/// Whether or not to allow core-requested rotation (`VID-ALLOW-CORE-ROT`)
AllowCoreRotation(bool),
/// The rotation the user is requesting (`VID-USER-ROT`)
UserRotation(Rotation),
/// The final rotation (`VID-FINAL-ROT`) calculated by the sum of `VID-USER-ROT` and `CORE-REQ-ROT`
FinalRotation(Rotation),
/// The user-adjusted screen orientation (`SCREEN-ORIENT`)
ScreenOrientation(Rotation),
/// The orientation of the viewport aspect ratio (`VIEW-ASPECT-ORIENT`)
ViewAspectOrientation(Orientation),
/// The orientation of the aspect ratio requested by the core (`CORE-ASPECT-ORIENT`)
CoreAspectOrientation(Orientation),
/// An external, arbitrary context variable.
ExternContext(String, String),
}
impl ContextItem {
fn toggle_str(v: bool) -> &'static str {
if v {
"ON"
} else {
"OFF"
}
}
pub fn key(&self) -> &str {
match self {
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
ContextItem::CoreName(_) => "CORE",
ContextItem::GameName(_) => "GAME",
ContextItem::Preset(_) => "PRESET",
ContextItem::PresetDirectory(_) => "PRESET_DIR",
ContextItem::VideoDriver(_) => "VID-DRV",
ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
ContextItem::UserRotation(_) => "VID-USER-ROT",
ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
ContextItem::ExternContext(key, _) => key,
}
}
}
impl Display for ContextItem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ContextItem::ContentDirectory(v) => f.write_str(v),
ContextItem::CoreName(v) => f.write_str(v),
ContextItem::GameName(v) => f.write_str(v),
ContextItem::Preset(v) => f.write_str(v),
ContextItem::PresetDirectory(v) => f.write_str(v),
ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::CoreRequestedRotation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
"{}-{}",
self.key(),
ContextItem::toggle_str(*v)
)),
ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
ContextItem::ViewAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::CoreAspectOrientation(v) => {
f.write_fmt(format_args!("{}-{}", self.key(), v))
}
ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
}
}
}
/// A preset wildcard context.
///
/// Any items added after will have higher priority
/// when passed to the shader preset parser.
///
/// When passed to the preset parser, the preset parser
/// will automatically add inferred items at lowest priority.
///
/// Any items added by the user will override the automatically
/// inferred items.
#[derive(Debug, Clone)]
pub struct WildcardContext(VecDeque<ContextItem>);
impl WildcardContext {
/// Create a new wildcard context.
pub fn new() -> Self {
Self(VecDeque::new())
}
/// Prepend an item to the context builder.
pub fn prepend_item(&mut self, item: ContextItem) {
self.0.push_front(item);
}
/// Append an item to the context builder.
/// The new item will take precedence over all items added before it.
pub fn append_item(&mut self, item: ContextItem) {
self.0.push_back(item);
}
/// Prepend sensible defaults for the given video driver.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
self.prepend_item(ContextItem::VideoDriverPresetExtension(
PresetExtension::Slangp,
));
self.prepend_item(ContextItem::VideoDriverShaderExtension(
ShaderExtension::Slang,
));
self.prepend_item(ContextItem::VideoDriver(video_driver));
}
/// Prepend default entries from the path of the preset.
///
/// Any values added, either previously or afterwards will not be overridden.
pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
if let Some(preset_name) = path.file_stem() {
let preset_name = preset_name.to_string_lossy();
self.prepend_item(ContextItem::Preset(preset_name.into()))
}
if let Some(preset_dir_name) = path.parent().and_then(|p| {
if !p.is_dir() {
return None;
};
p.file_name()
}) {
let preset_dir_name = preset_dir_name.to_string_lossy();
self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
}
}
pub(crate) fn to_hashmap(mut self) -> FxHashMap<String, String> {
let mut map = FxHashMap::default();
let last_user_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::UserRotation(_)));
let last_core_rot = self
.0
.iter()
.rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
let final_rot = match (last_core_rot, last_user_rot) {
(Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
(None, Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*c))
}
(Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
Some(ContextItem::FinalRotation(*u + *c))
}
_ => None,
};
if let Some(final_rot) = final_rot {
self.prepend_item(final_rot);
}
for item in self.0 {
map.insert(String::from(item.key()), item.to_string());
}
map
}
}
pub(crate) fn apply_context(path: &mut PathBuf, context: &FxHashMap<String, String>) {
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
if context.is_empty() {
return;
}
// Don't want to do any extra work if there's no match.
if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
return;
}
let mut new_path = PathBuf::with_capacity(path.capacity());
for component in path.components() {
match component {
Component::Normal(path) => {
let haystack = path.as_encoded_bytes();
let replaced =
WILDCARD_REGEX.replace_all(haystack, |caps: &regex::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;
}
}

View file

@ -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))
}
}

View file

@ -115,7 +115,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let shader = ShaderPassConfig {
id,
name,
name: librashader_common::ShaderStorage::Path(name),
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()),
_ => None,

View file

@ -171,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() {

View file

@ -10,12 +10,14 @@ use nom::IResult;
use num_traits::cast::ToPrimitive;
use crate::parse::token::do_lex;
use librashader_common::map::FastHashMap;
use librashader_common::{FilterMode, WrapMode};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::context::{apply_context, WildcardContext};
use crate::extract_if::MakeExtractIf;
#[derive(Debug)]
@ -150,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();
@ -161,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))?;
@ -176,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);
@ -204,8 +211,16 @@ fn load_child_reference_strings(
Ok(reference_strings.into())
}
pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
pub(crate) fn parse_preset(
path: impl AsRef<Path>,
context: WildcardContext,
) -> Result<Vec<Value>, ParsePresetError> {
let path = path.as_ref();
let mut path = path.to_path_buf();
let context = context.to_hashmap();
apply_context(&mut path, &context);
let path = path
.canonicalize()
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
@ -216,12 +231,14 @@ pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetErr
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
let tokens = super::token::do_lex(&contents)?;
parse_values(tokens, path)
parse_values(tokens, path, context)
}
// prereq: root_path must be contextualized
pub fn parse_values(
mut tokens: Vec<Token>,
root_path: impl AsRef<Path>,
context: FastHashMap<String, String>,
) -> Result<Vec<Value>, ParsePresetError> {
let mut root_path = root_path.as_ref().to_path_buf();
if root_path.is_relative() {
@ -239,7 +256,9 @@ pub fn parse_values(
.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() {
@ -596,13 +615,14 @@ pub fn parse_values(
#[cfg(test)]
mod test {
use crate::parse::value::parse_preset;
use crate::WildcardContext;
use std::path::PathBuf;
#[test]
pub fn parse_basic() {
let root =
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
let basic = parse_preset(root);
let basic = parse_preset(root, WildcardContext::new());
eprintln!("{basic:?}");
assert!(basic.is_ok());
}

View file

@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
/// The index of the shader pass relative to its parent preset.
pub id: i32,
/// The fully qualified path to the shader pass source file.
pub name: PathBuf,
pub name: librashader_common::ShaderStorage,
/// The alias of the shader pass if available.
pub alias: Option<String>,
/// The filtering mode that this shader pass should expect.
@ -63,6 +63,8 @@ pub enum ScaleType {
Absolute,
/// Scale by the size of the viewport.
Viewport,
/// Scale by the size of the original input quad.
Original,
}
/// The scaling factor for framebuffer scaling.
@ -119,6 +121,7 @@ impl FromStr for ScaleType {
"source" => Ok(ScaleType::Input),
"viewport" => Ok(ScaleType::Viewport),
"absolute" => Ok(ScaleType::Absolute),
"original" => Ok(ScaleType::Original),
_ => Err(ParsePresetError::InvalidScaleType(s.to_string())),
}
}

View file

@ -1,4 +1,5 @@
use glob::glob;
use librashader_presets::context::{ContextItem, VideoDriver, WildcardContext};
use librashader_presets::ShaderPreset;
#[test]
@ -17,3 +18,17 @@ fn parses_problematic() {
let path = "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_NDS_DREZ/NDS-[DREZ]-[Native]-[ADV]-[Guest]-[Night].slangp";
ShaderPreset::try_parse(path).expect(&format!("Failed to parse {}", path));
}
#[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));
}

View file

@ -3,7 +3,7 @@ name = "librashader-reflect"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,35 +12,37 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
glslang = "0.2"
glslang = "0.3"
bytemuck = "1.13.0"
thiserror = "1.0.37"
bitflags = "1.3.2"
rustc-hash = "1.1.0"
bitflags = "2.4.2"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.7" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.7" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.7" }
librashader-common = { path = "../librashader-common", version = "0.2.7" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.7" }
librashader-presets = { path = "../librashader-presets", version = "0.2.7" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true }
naga = { version = "0.19.0", features = ["spv-in", "wgsl-out"], optional = true }
rspirv = { version = "0.12.0+sdk-1.3.268.0", optional = true }
spirv = { version = "0.3.0+sdk-1.3.268.0", optional = true}
naga = { version = "0.20.0", optional = true }
rspirv = { version = "0.12.0", optional = true }
spirv = { version = "0.3.0", optional = true}
serde = { version = "1.0", features = ["derive"], optional = true }
indexmap = { version = "2.1.0", features = [] }
matches = { version = "0.1.10", features = [] }
rustc-hash = "1.1.0"
[target.'cfg(windows)'.dependencies.spirv-to-dxil]
version = "0.4"
version = "0.4.7"
optional = true
[features]
default = ["cross", "wgsl", "serialize"]
dxil = ["cross", "spirv-to-dxil"]
wgsl = ["cross", "naga", "spirv", "rspirv"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl" ]
default = ["cross", "naga", "serialize", "wgsl", "msl"]
dxil = ["spirv_cross/hlsl", "spirv-to-dxil"]
wgsl = ["cross", "naga/wgsl-out", "spirv", "rspirv"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl", "spirv_cross/msl" ]
naga = [ "rspirv", "spirv", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
serialize = [ "serde" ]
msl = [ "spirv_cross/msl", "naga/msl-out" ]

View file

@ -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)?,
})
}
}

View file

@ -1,22 +1,22 @@
use crate::back::spirv::WriteSpirV;
use crate::back::targets::{OutputTarget, DXIL};
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
use crate::reflect::cross::SpirvCross;
use crate::reflect::ReflectShader;
pub use spirv_to_dxil::DxilObject;
pub use spirv_to_dxil::ShaderModel;
use spirv_to_dxil::{
PushConstantBufferConfig, RuntimeConfig, RuntimeDataBufferConfig, ShaderStage, ValidatorVersion,
};
use crate::back::targets::{OutputTarget, DXIL};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::reflect::cross::GlslReflect;
use crate::reflect::ReflectShader;
impl OutputTarget for DXIL {
type Output = DxilObject;
}
impl FromCompilation<GlslangCompilation> for DXIL {
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
@ -24,17 +24,15 @@ impl FromCompilation<GlslangCompilation> for DXIL {
+ ReflectShader;
fn from_compilation(
compile: GlslangCompilation,
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
// we can just reuse WriteSpirV as the backend.
backend: WriteSpirV {
reflect,
vertex,
fragment,
vertex: compile.vertex,
fragment: compile.fragment,
},
})
}

View file

@ -0,0 +1,35 @@
use crate::back::targets::GLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The GLSL version to target.
pub use spirv_cross::glsl::Version as GlslVersion;
use crate::reflect::cross::glsl::GlslReflect;
/// The context for a GLSL compilation via spirv-cross.
pub struct CrossGlslContext {
/// A map of bindings of sampler names to binding locations.
pub sampler_bindings: Vec<(String, u32)>,
/// The compiled program artifact after compilation.
pub artifact: CompiledProgram<spirv_cross::glsl::Target>,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: GlslReflect::try_from(&compile)?,
})
}
}

View file

@ -0,0 +1,143 @@
use crate::back::targets::HLSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::hlsl::HlslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::ReflectShader;
/// The HLSL shader model version to target.
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
/// Buffer assignment information
#[derive(Debug, Clone)]
pub struct HlslBufferAssignment {
/// The name of the buffer
pub name: String,
/// The id of the buffer
pub id: u32,
}
/// Buffer assignment information
#[derive(Debug, Clone, Default)]
pub struct HlslBufferAssignments {
/// Buffer assignment information for UBO
pub ubo: Option<HlslBufferAssignment>,
/// Buffer assignment information for Push
pub push: Option<HlslBufferAssignment>,
}
impl HlslBufferAssignments {
fn find_mangled_id(mangled_name: &str) -> Option<u32> {
if !mangled_name.starts_with("_") {
return None;
}
let Some(next_underscore) = mangled_name[1..].find("_") else {
return None;
};
mangled_name[1..next_underscore + 1].parse().ok()
}
fn find_mangled_name(buffer_name: &str, uniform_name: &str, mangled_name: &str) -> bool {
// name prependded
if mangled_name[buffer_name.len()..].starts_with("_")
&& &mangled_name[buffer_name.len() + 1..] == uniform_name
{
return true;
}
return false;
}
// Check if the mangled name matches.
pub fn contains_uniform(&self, uniform_name: &str, mangled_name: &str) -> bool {
let is_likely_id_mangled = mangled_name.starts_with("_");
if !mangled_name.ends_with(uniform_name) {
return false;
}
if let Some(ubo) = &self.ubo {
if is_likely_id_mangled {
if let Some(id) = Self::find_mangled_id(mangled_name) {
if id == ubo.id {
return true;
}
}
}
// name prependded
if Self::find_mangled_name(&ubo.name, uniform_name, mangled_name) {
return true;
}
}
if let Some(push) = &self.push {
if is_likely_id_mangled {
if let Some(id) = Self::find_mangled_id(mangled_name) {
if id == push.id {
return true;
}
}
}
// name prependded
if Self::find_mangled_name(&push.name, uniform_name, mangled_name) {
return true;
}
}
return false;
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
pub vertex_buffers: HlslBufferAssignments,
pub fragment_buffers: HlslBufferAssignments,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: HlslReflect::try_from(&compile)?,
})
}
}
#[cfg(test)]
mod test {
use crate::back::hlsl::HlslBufferAssignments;
#[test]
pub fn mangled_id_test() {
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_MVP"), Some(19));
assert_eq!(HlslBufferAssignments::find_mangled_id("_19"), None);
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_"), Some(19));
assert_eq!(HlslBufferAssignments::find_mangled_id("19_"), None);
assert_eq!(HlslBufferAssignments::find_mangled_id("_19MVP"), None);
assert_eq!(
HlslBufferAssignments::find_mangled_id("_19_29_MVP"),
Some(19)
);
}
#[test]
pub fn mangled_name_test() {
assert!(HlslBufferAssignments::find_mangled_name(
"params",
"MVP",
"params_MVP"
));
}
}

View file

@ -1,7 +1,9 @@
pub mod cross;
#[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;
@ -41,26 +43,26 @@ pub trait CompileShader<T: OutputTarget> {
///
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`](crate::back::FromCompilation) implement
/// for a given target that also implement [`CompileShader`](crate::back::CompileShader) for that target.
pub trait CompileReflectShader<T: OutputTarget, C>:
pub trait CompileReflectShader<T: OutputTarget, C, S>:
CompileShader<
T,
Options = <T as FromCompilation<C>>::Options,
Context = <T as FromCompilation<C>>::Context,
Options = <T as FromCompilation<C, S>>::Options,
Context = <T as FromCompilation<C, S>>::Context,
> + ReflectShader
where
T: FromCompilation<C>,
T: FromCompilation<C, S>,
{
}
impl<T, C, O> CompileReflectShader<T, C> for O
impl<T, C, O, S> CompileReflectShader<T, C, S> for O
where
T: OutputTarget,
T: FromCompilation<C>,
T: FromCompilation<C, S>,
O: ReflectShader,
O: CompileShader<
T,
Options = <T as FromCompilation<C>>::Options,
Context = <T as FromCompilation<C>>::Context,
Options = <T as FromCompilation<C, S>>::Options,
Context = <T as FromCompilation<C, S>>::Context,
>,
{
}
@ -81,8 +83,14 @@ where
}
}
/// A trait for reflectable compilations that can be transformed into an object ready for reflection or compilation.
pub trait FromCompilation<T> {
/// A trait for reflectable compilations that can be transformed
/// into an object ready for reflection or compilation.
///
/// `T` is the compiled reflectable form of the shader.
/// `S` is the semantics under which the shader is reflected.
///
/// librashader currently supports two semantics, [`SpirvCross`](crate::reflect::cross::SpirvCross)
pub trait FromCompilation<T, S> {
/// The target that the transformed object is expected to compile for.
type Target: OutputTarget;
/// Options provided to the compiler.
@ -118,11 +126,11 @@ where
#[cfg(test)]
mod test {
use crate::front::GlslangCompilation;
use crate::front::{Glslang, ShaderInputCompiler};
use librashader_preprocess::ShaderSource;
pub fn test() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let _cross = GlslangCompilation::compile(&result).unwrap();
let _cross = Glslang::compile(&result).unwrap();
}
}

View file

@ -0,0 +1,70 @@
use crate::back::targets::MSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::cross::msl::MslReflect;
use crate::reflect::cross::{CompiledProgram, SpirvCross};
use crate::reflect::naga::{Naga, NagaReflect};
use crate::reflect::ReflectShader;
use naga::back::msl::TranslationInfo;
use naga::Module;
/// The HLSL shader model version to target.
pub use spirv_cross::msl::Version as MslVersion;
/// Compiler options for MSL
#[derive(Debug, Default, Clone)]
pub struct MslNagaCompileOptions {
// pub write_pcb_as_ubo: bool,
pub sampler_bind_group: u32,
}
/// The context for a MSL compilation via spirv-cross.
pub struct CrossMslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross::msl::Target>,
}
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: MslReflect::try_from(&compile)?,
})
}
}
/// The naga module for a shader after compilation
pub struct NagaMslModule {
pub translation_info: TranslationInfo,
pub module: Module,
}
pub struct NagaMslContext {
pub vertex: NagaMslModule,
pub fragment: NagaMslModule,
pub next_free_binding: u32,
}
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}

View file

@ -1,10 +1,13 @@
use crate::back::targets::SPIRV;
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::reflect::cross::GlslReflect;
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
use crate::reflect::cross::SpirvCross;
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
use crate::reflect::semantics::ShaderSemantics;
use crate::reflect::{ReflectShader, ShaderReflection};
use naga::Module;
pub(crate) struct WriteSpirV {
// rely on GLSL to provide out reflection but we don't actually need the AST.
@ -13,7 +16,7 @@ pub(crate) struct WriteSpirV {
pub(crate) fragment: Vec<u32>,
}
impl FromCompilation<GlslangCompilation> for SPIRV {
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
@ -21,7 +24,7 @@ impl FromCompilation<GlslangCompilation> for SPIRV {
+ ReflectShader;
fn from_compilation(
compile: GlslangCompilation,
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
@ -61,3 +64,30 @@ impl CompileShader<SPIRV> for WriteSpirV {
})
}
}
/// The context for a SPIRV compilation via Naga
pub struct NagaSpirvContext {
pub fragment: Module,
pub vertex: Module,
}
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}
pub struct NagaSpirvOptions {
pub lowering: NagaLoweringOptions,
pub version: (u8, u8),
}

View file

@ -5,17 +5,22 @@ pub trait OutputTarget {
}
/// Shader compiler target for GLSL.
#[derive(Debug)]
pub struct GLSL;
/// Shader compiler target for HLSL.
#[derive(Debug)]
pub struct HLSL;
/// Shader compiler target for SPIR-V.
#[derive(Debug)]
pub struct SPIRV;
/// Shader compiler target for MSL.
#[derive(Debug)]
pub struct MSL;
/// Shader compiler target for DXIL.
///
/// The resulting DXIL object is always unvalidated and
/// must be validated using platform APIs before usage.
#[derive(Debug)]
pub struct DXIL;
/// Shader compiler target for WGSL.
@ -34,6 +39,9 @@ impl OutputTarget for HLSL {
impl OutputTarget for WGSL {
type Output = String;
}
impl OutputTarget for MSL {
type Output = String;
}
impl OutputTarget for SPIRV {
type Output = Vec<u32>;
}
@ -42,9 +50,9 @@ impl OutputTarget for SPIRV {
mod test {
use crate::back::targets::GLSL;
use crate::back::FromCompilation;
use crate::front::GlslangCompilation;
use crate::front::SpirvCompilation;
#[allow(dead_code)]
pub fn test_compile(value: GlslangCompilation) {
pub fn test_compile(value: SpirvCompilation) {
let _x = GLSL::from_compilation(value).unwrap();
}
}

View file

@ -0,0 +1,95 @@
use crate::back::targets::WGSL;
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
use crate::error::ShaderReflectError;
use crate::front::SpirvCompilation;
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
use crate::reflect::ReflectShader;
use naga::Module;
/// The context for a WGSL compilation via Naga
pub struct NagaWgslContext {
pub fragment: Module,
pub vertex: Module,
}
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: NagaReflect::try_from(&compile)?,
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::WGSL;
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::naga::NagaLoweringOptions;
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut wgsl = WGSL::from_compilation(compilation).unwrap();
wgsl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = wgsl
.compile(NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})
.unwrap();
println!("{}", compiled.fragment);
// println!("{}", compiled.fragment);
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// let module = loader.module();
//
// let outputs: Vec<&Instruction> = module
// .types_global_values
// .iter()
// .filter(|i| i.class.opcode == Op::Variable)
// .collect();
//
// println!("{outputs:#?}");
}
}

View file

@ -1,227 +0,0 @@
mod lower_samplers;
use crate::back::targets::WGSL;
use crate::back::wgsl::lower_samplers::LowerCombinedImageSamplerPass;
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::reflect::naga::NagaReflect;
use crate::reflect::ReflectShader;
use naga::back::wgsl::WriterFlags;
use naga::valid::{Capabilities, ValidationFlags};
use naga::{AddressSpace, Module};
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
/// The context for a WGSL compilation via Naga
pub struct NagaWgslContext {
pub fragment: Module,
pub vertex: Module,
}
/// Compiler options for WGSL
#[derive(Debug, Default, Clone)]
pub struct WgslCompileOptions {
pub write_pcb_as_ubo: bool,
pub sampler_bind_group: u32,
}
impl FromCompilation<GlslangCompilation> for WGSL {
type Target = WGSL;
type Options = WgslCompileOptions;
type Context = NagaWgslContext;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
fn lower_fragment_shader(words: &[u32]) -> Vec<u32> {
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(words, &mut loader).unwrap();
let module = loader.module();
let mut builder = Builder::new_from_module(module);
let mut pass = LowerCombinedImageSamplerPass::new(&mut builder);
pass.ensure_op_type_sampler();
pass.do_pass();
let module = builder.module();
module.assemble()
}
let options = naga::front::spv::Options {
adjust_coordinate_space: true,
strict_capabilities: false,
block_ctx_dump_prefix: None,
};
let vertex =
naga::front::spv::parse_u8_slice(bytemuck::cast_slice(&compile.vertex), &options)?;
let fragment = lower_fragment_shader(&compile.fragment);
let fragment = naga::front::spv::parse_u8_slice(bytemuck::cast_slice(&fragment), &options)?;
Ok(CompilerBackend {
backend: NagaReflect { vertex, fragment },
})
}
}
impl CompileShader<WGSL> for NagaReflect {
type Options = WgslCompileOptions;
type Context = NagaWgslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
fn write_wgsl(module: &Module) -> Result<String, ShaderCompileError> {
let mut valid =
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
let info = valid.validate(&module)?;
let wgsl = naga::back::wgsl::write_string(&module, &info, WriterFlags::EXPLICIT_TYPES)?;
Ok(wgsl)
}
if options.write_pcb_as_ubo {
for (_, gv) in self.fragment.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.space = AddressSpace::Uniform;
}
}
for (_, gv) in self.vertex.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.space = AddressSpace::Uniform;
}
}
} else {
for (_, gv) in self.fragment.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.binding = None;
}
}
}
// Reassign shit.
let images = self
.fragment
.global_variables
.iter()
.filter(|&(_, gv)| {
let ty = &self.fragment.types[gv.ty];
match ty.inner {
naga::TypeInner::Image { .. } => true,
naga::TypeInner::BindingArray { base, .. } => {
let ty = &self.fragment.types[base];
matches!(ty.inner, naga::TypeInner::Image { .. })
}
_ => false,
}
})
.map(|(_, gv)| (gv.binding.clone(), gv.space))
.collect::<naga::FastHashSet<_>>();
self.fragment
.global_variables
.iter_mut()
.filter(|(_, gv)| {
let ty = &self.fragment.types[gv.ty];
match ty.inner {
naga::TypeInner::Sampler { .. } => true,
naga::TypeInner::BindingArray { base, .. } => {
let ty = &self.fragment.types[base];
matches!(ty.inner, naga::TypeInner::Sampler { .. })
}
_ => false,
}
})
.for_each(|(_, gv)| {
if images.contains(&(gv.binding.clone(), gv.space)) {
if let Some(binding) = &mut gv.binding {
binding.group = options.sampler_bind_group;
}
}
});
let fragment = write_wgsl(&self.fragment)?;
let vertex = write_wgsl(&self.vertex)?;
Ok(ShaderCompilerOutput {
vertex,
fragment,
context: NagaWgslContext {
fragment: self.fragment,
vertex: self.vertex,
},
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::WGSL;
use crate::back::wgsl::WgslCompileOptions;
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use librashader_preprocess::ShaderSource;
use rustc_hash::FxHashMap;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
let mut wgsl = WGSL::from_compilation(compilation).unwrap();
wgsl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = wgsl
.compile(WgslCompileOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})
.unwrap();
println!("{}", compiled.fragment);
// println!("{}", compiled.fragment);
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// let module = loader.module();
//
// let outputs: Vec<&Instruction> = module
// .types_global_values
// .iter()
// .filter(|i| i.class.opcode == Op::Variable)
// .collect();
//
// println!("{outputs:#?}");
}
}

View file

@ -11,29 +11,40 @@ pub enum ShaderCompileError {
NagaCompileError(Vec<naga::front::glsl::Error>),
/// Compilation error from glslang.
#[error("glslang")]
#[error("error when compiling with glslang: {0}")]
GlslangError(#[from] glslang::error::GlslangError),
/// Error when initializing the glslang compiler.
#[error("glslang init")]
#[error("error when initializing glslang")]
CompilerInitError,
/// Error when transpiling from spirv-cross.
#[error("cross")]
#[error("spirv-cross error: {0:?}")]
SpirvCrossCompileError(#[from] spirv_cross::ErrorCode),
/// Error when transpiling from spirv-to-dxil
#[cfg(all(target_os = "windows", feature = "dxil"))]
#[error("spirv-to-dxil")]
#[error("spirv-to-dxil error: {0:?}")]
SpirvToDxilCompileError(#[from] spirv_to_dxil::SpirvToDxilError),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-wgsl")]
#[cfg(all(feature = "wgsl", feature = "naga"))]
#[error("naga error when compiling wgsl: {0:?}")]
NagaWgslError(#[from] naga::back::wgsl::Error),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-wgsl")]
#[cfg(feature = "naga")]
#[error("naga error when compiling spirv: {0:?}")]
NagaSpvError(#[from] naga::back::spv::Error),
/// Error when transpiling from naga
#[cfg(all(feature = "naga", feature = "msl"))]
#[error("naga error when compiling msl: {0:?}")]
NagaMslError(#[from] naga::back::msl::Error),
/// Error when transpiling from naga
#[cfg(any(feature = "naga", feature = "wgsl"))]
#[error("naga validation error: {0}")]
NagaValidationError(#[from] naga::WithSpan<naga::valid::ValidationError>),
}
@ -75,23 +86,23 @@ pub enum SemanticsErrorKind {
#[derive(Error, Debug)]
pub enum ShaderReflectError {
/// Reflection error from spirv-cross.
#[error("spirv")]
#[error("spirv cross error: {0}")]
SpirvCrossError(#[from] spirv_cross::ErrorCode),
/// Error when validating vertex shader semantics.
#[error("error when verifying vertex semantics")]
#[error("error when verifying vertex semantics: {0:?}")]
VertexSemanticError(SemanticsErrorKind),
/// Error when validating fragment shader semantics.
#[error("error when verifying texture semantics")]
#[error("error when verifying texture semantics {0:?}")]
FragmentSemanticError(SemanticsErrorKind),
/// The vertex and fragment shader must have the same UBO binding location.
#[error("vertex and fragment shader must have same binding")]
#[error("vertex and fragment shader must have same UBO binding. declared {vertex} in vertex, got {fragment} in fragment")]
MismatchedUniformBuffer { vertex: u32, fragment: u32 },
/// The filter chain was found to be non causal. A pass tried to access the target output
/// in the future.
#[error("filter chain is non causal")]
#[error("filter chain is non causal: tried to access target {target} in pass {pass}")]
NonCausalFilterChain { pass: usize, target: usize },
/// The offset of the given uniform did not match up in both the vertex and fragment shader.
#[error("mismatched offset")]
#[error("the offset of {semantic} was declared as {expected} but found as {received} in pass {pass}")]
MismatchedOffset {
semantic: String,
expected: usize,
@ -100,7 +111,7 @@ pub enum ShaderReflectError {
pass: usize,
},
/// The size of the given uniform did not match up in both the vertex and fragment shader.
#[error("mismatched component")]
#[error("the size of {semantic} was found declared as {vertex} in vertex shader but found as {fragment} in fragment in pass {pass}")]
MismatchedSize {
semantic: String,
vertex: u32,
@ -108,12 +119,16 @@ pub enum ShaderReflectError {
pass: usize,
},
/// The binding number is already in use.
#[error("the binding is already in use")]
#[error("binding {0} is already in use")]
BindingInUse(u32),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-spv")]
#[cfg(feature = "naga")]
#[error("naga spirv error: {0}")]
NagaInputError(#[from] naga::front::spv::Error),
/// Error when transpiling from naga
#[cfg(feature = "naga")]
#[error("naga validation error: {0}")]
NagaReflectError(#[from] naga::WithSpan<naga::valid::ValidationError>),
}
#[cfg(feature = "unstable-naga")]

View file

@ -1,67 +1,54 @@
use crate::error::ShaderCompileError;
use glslang::input::{CompilerOptions, ShaderInput};
use glslang::limits::ResourceLimits;
use glslang::{CompilerOptions, ShaderInput};
use librashader_preprocess::ShaderSource;
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use crate::front::spirv_passes::{link_input_outputs, load_module};
use crate::front::{ShaderInputCompiler, SpirvCompilation};
/// A reflectable shader compilation via glslang.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct GlslangCompilation {
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
/// glslang compiler
pub struct Glslang;
impl GlslangCompilation {
/// Tries to compile SPIR-V from the provided shader source.
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
impl ShaderInputCompiler<SpirvCompilation> for Glslang {
fn compile(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
compile_spirv(source)
}
}
impl TryFrom<&ShaderSource> for GlslangCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
GlslangCompilation::compile(source)
}
}
pub(crate) fn compile_spirv(
source: &ShaderSource,
) -> Result<GlslangCompilation, ShaderCompileError> {
pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
let compiler = glslang::Compiler::acquire().ok_or(ShaderCompileError::CompilerInitError)?;
let options = CompilerOptions {
source_language: glslang::SourceLanguage::GLSL,
target: glslang::Target::Vulkan {
version: glslang::VulkanVersion::Vulkan1_0,
spirv_version: glslang::SpirvVersion::SPIRV1_0,
},
version_profile: None,
};
let limits = ResourceLimits::default();
let options = CompilerOptions::default();
let vertex = glslang::input::ShaderSource::from(source.vertex.as_str());
let vertex = ShaderInput::new(
&vertex,
&limits,
glslang::ShaderStage::Vertex,
&options,
None,
);
let vertex = glslang::ShaderSource::from(source.vertex.as_str());
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None)?;
let vertex = compiler.create_shader(vertex)?;
let fragment = glslang::input::ShaderSource::from(source.fragment.as_str());
let fragment = ShaderInput::new(
&fragment,
&limits,
glslang::ShaderStage::Fragment,
&options,
None,
);
let fragment = glslang::ShaderSource::from(source.fragment.as_str());
let fragment = ShaderInput::new(&fragment, glslang::ShaderStage::Fragment, &options, None)?;
let fragment = compiler.create_shader(fragment)?;
let vertex = Vec::from(vertex.compile()?);
let fragment = Vec::from(fragment.compile()?);
let vertex = vertex.compile()?;
let fragment = fragment.compile()?;
Ok(GlslangCompilation { vertex, fragment })
let vertex = load_module(&vertex);
let fragment = load_module(&fragment);
let mut fragment = Builder::new_from_module(fragment);
let mut pass = link_input_outputs::LinkInputs::new(&vertex, &mut fragment);
pass.do_pass();
let vertex = vertex.assemble();
let fragment = fragment.module().assemble();
Ok(SpirvCompilation { vertex, fragment })
}
#[cfg(test)]

View file

@ -1,18 +1,49 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use serde::{Deserialize, Serialize};
pub(crate) mod spirv_passes;
mod glslang;
pub use crate::front::glslang::GlslangCompilation;
/// The output of a shader compiler that is reflectable.
pub trait ShaderReflectObject: Sized {
/// The compiler that produces this reflect object.
type Compiler;
}
pub use crate::front::glslang::Glslang;
/// Trait for types that can compile shader sources into a compilation unit.
pub trait ShaderCompilation: Sized {
pub trait ShaderInputCompiler<O: ShaderReflectObject>: Sized {
/// Compile the input shader source file into a compilation unit.
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError>;
fn compile(source: &ShaderSource) -> Result<O, ShaderCompileError>;
}
impl<T: for<'a> TryFrom<&'a ShaderSource, Error = ShaderCompileError>> ShaderCompilation for T {
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
source.try_into()
/// Marker trait for types that are the reflectable outputs of a shader compilation.
impl ShaderReflectObject for SpirvCompilation {
type Compiler = Glslang;
}
/// A reflectable shader compilation via glslang.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct SpirvCompilation {
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
impl SpirvCompilation {
/// Tries to compile SPIR-V from the provided shader source.
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
glslang::compile_spirv(source)
}
}
impl TryFrom<&ShaderSource> for SpirvCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
Glslang::compile(source)
}
}

View file

@ -0,0 +1,151 @@
use rspirv::dr::{Builder, Module, Operand};
use rustc_hash::{FxHashMap, FxHashSet};
use spirv::{Decoration, Op, StorageClass};
pub struct LinkInputs<'a> {
pub frag_builder: &'a mut Builder,
pub inputs: FxHashSet<spirv::Word>,
}
impl<'a> LinkInputs<'a> {
fn find_location(module: &Module, id: spirv::Word) -> Option<u32> {
module.annotations.iter().find_map(|op| {
if op.class.opcode != Op::Decorate {
return None;
}
let Some(Operand::Decoration(Decoration::Location)) = op.operands.get(1) else {
return None;
};
let Some(&Operand::IdRef(target)) = op.operands.get(0) else {
return None;
};
if target != id {
return None;
}
let Some(&Operand::LiteralBit32(binding)) = op.operands.get(2) else {
return None;
};
return Some(binding);
})
}
pub fn new(vert: &'a Module, frag: &'a mut Builder) -> Self {
let mut inputs = FxHashSet::default();
let mut bindings = FxHashMap::default();
for global in frag.module_ref().types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Input)
{
if let Some(id) = global.result_id {
let Some(location) = Self::find_location(frag.module_ref(), id) else {
continue;
};
inputs.insert(id);
bindings.insert(location, id);
}
}
}
for global in vert.types_global_values.iter() {
if global.class.opcode == spirv::Op::Variable
&& global.operands[0] == Operand::StorageClass(StorageClass::Output)
{
if let Some(id) = global.result_id {
let Some(location) = Self::find_location(vert, id) else {
continue;
};
if let Some(frag_ref) = bindings.get(&location) {
// if something is bound to the same location in the vertex shader,
// we're good.
inputs.remove(&frag_ref);
}
}
}
}
Self {
frag_builder: frag,
inputs,
}
}
pub fn do_pass(&mut self) {
let functions = &self.frag_builder.module_ref().functions;
// literally if it has any reference at all we can keep it
for function in functions {
for param in &function.parameters {
for op in &param.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
}
for block in &function.blocks {
for inst in &block.instructions {
for op in &inst.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
}
}
}
// ok well guess we dont
self.frag_builder.module_mut().debug_names.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
}
return true;
});
self.frag_builder.module_mut().annotations.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
}
return true;
});
for entry_point in self.frag_builder.module_mut().entry_points.iter_mut() {
entry_point.operands.retain(|op| {
if let Some(word) = op.id_ref_any() {
if self.inputs.contains(&word) {
return false;
}
}
return true;
})
}
self.frag_builder
.module_mut()
.types_global_values
.retain(|instr| {
let Some(id) = instr.result_id else {
return true;
};
!self.inputs.contains(&id)
});
}
}

View file

@ -818,10 +818,11 @@ impl<'a> LowerCombinedImageSamplerPass<'a> {
function_call_operands.push(operand);
function_call_operands.push(Operand::IdRef(op_access_chain_sampler));
let original_type = self
.find_global_instruction(original_result_type)
.cloned()
.expect("huh");
let Some(original_type) =
self.find_global_instruction(original_result_type).cloned()
else {
continue;
};
let sampled_image = CombinedImageSampler {
sampler_variable: op_access_chain_sampler,

View file

@ -0,0 +1,10 @@
pub mod link_input_outputs;
pub mod lower_samplers;
// Load SPIR-V as an rspirv module
pub(crate) fn load_module(words: &[u32]) -> rspirv::dr::Module {
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(words, &mut loader).unwrap();
let module = loader.module();
module
}

View file

@ -13,10 +13,11 @@
//! use librashader_presets::ShaderPreset;
//! use librashader_reflect::back::{CompileReflectShader, FromCompilation};
//! use librashader_reflect::back::targets::SPIRV;
//! use librashader_reflect::front::GlslangCompilation;
//! use librashader_reflect::front::{Glslang, ShaderInputCompiler, SpirvCompilation};
//! use librashader_reflect::reflect::cross::SpirvCross;
//! use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
//! use librashader_reflect::reflect::semantics::ShaderSemantics;
//! type Artifact = impl CompileReflectShader<SPIRV, GlslangCompilation>;
//! type Artifact = impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>;
//! type ShaderPassMeta = ShaderPassArtifact<Artifact>;
//!
//! // Compile single shader
@ -24,15 +25,15 @@
//! source: &ShaderSource,
//! ) -> Result<Artifact, Box<dyn Error>>
//! {
//! let compilation = GlslangCompilation::compile(&source)?;
//! let spirv = SPIRV::from_compilation(artifact)?;
//! let compilation = SpirvCompilation::try_from(&source);
//! let spirv = SPIRV::from_compilation(compilation)?;
//! Ok(spirv)
//! }
//!
//! // Compile preset
//! pub fn compile_preset(preset: ShaderPreset) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), Box<dyn Error>>
//! {
//! let (passes, semantics) = SPIRV::compile_preset_passes::<GlslangCompilation, Box<dyn Error>>(
//! let (passes, semantics) = SPIRV::compile_preset_passes::<SpirvCompilation, SpirvCross, Box<dyn Error>>(
//! preset.shaders, &preset.textures)?;
//! Ok((passes, semantics))
//! }
@ -43,9 +44,8 @@
//! [naga](https://docs.rs/naga/latest/naga/index.html), a pure-Rust shader compiler, when it has
//! matured enough to support [the features librashader needs](https://github.com/gfx-rs/naga/issues/1012).
//!
//! In the meanwhile, the only supported compilation type is [GlslangCompilation](crate::front::GlslangCompilation),
//! In the meanwhile, the only supported compilation type is [GlslangCompilation](crate::front::SpirvCompilation),
//! which does transpilation via [glslang](https://github.com/KhronosGroup/glslang) and [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross).
#![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)]
#![feature(let_chains)]

View file

@ -0,0 +1,139 @@
use crate::back::glsl::CrossGlslContext;
use crate::back::targets::GLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
pub(crate) type GlslReflect = CrossReflect<spirv_cross::glsl::Target>;
impl CompileShader<GLSL> for CrossReflect<spirv_cross::glsl::Target> {
type Options = spirv_cross::glsl::Version;
type Context = CrossGlslContext;
fn compile(
mut self,
version: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
let mut options: spirv_cross::glsl::CompilerOptions = Default::default();
options.version = version;
options.fragment.default_float_precision = spirv_cross::glsl::Precision::High;
options.fragment.default_int_precision = spirv_cross::glsl::Precision::High;
options.enable_420_pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
for res in &vertex_resources.stage_outputs {
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
// self.vertex
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &fragment_resources.stage_inputs {
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
// self.fragment
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.fragment
.unset_decoration(res.id, Decoration::Location)?;
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &vertex_resources.uniform_buffers {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &fragment_resources.push_constant_buffers {
self.fragment
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &fragment_resources.uniform_buffers {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
}
let mut texture_fixups = Vec::new();
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
let mut name = res.name;
name.push('\0');
texture_fixups.push((name, binding));
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}

View file

@ -0,0 +1,119 @@
use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssignments};
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
pub(crate) type HlslReflect = CrossReflect<spirv_cross::hlsl::Target>;
impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::V5_0);
let mut options = spirv_cross::hlsl::CompilerOptions::default();
options.shader_model = sm;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
// todo: options
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
let mut vertex_buffer_assignment = HlslBufferAssignments::default();
let mut fragment_buffer_assignment = HlslBufferAssignments::default();
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = vertex_resources.uniform_buffers.first() {
vertex_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = vertex_resources.push_constant_buffers.first() {
vertex_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = fragment_resources.uniform_buffers.first() {
fragment_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = fragment_resources.push_constant_buffers.first() {
fragment_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.clone(),
id: buf.id,
})
}
if sm == HlslShaderModel::V3_0 {
for res in &fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?;
// self.fragment
// .unset_decoration(res.id, Decoration::Binding)?;
}
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
vertex_buffers: vertex_buffer_assignment,
fragment_buffers: fragment_buffer_assignment,
},
})
}
}

View file

@ -1,5 +1,9 @@
use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation;
pub mod glsl;
pub mod hlsl;
pub mod msl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
@ -7,22 +11,31 @@ use crate::reflect::semantics::{
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader};
use std::fmt::Debug;
use std::ops::Deref;
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
use spirv_cross::{glsl, hlsl, ErrorCode};
use spirv_cross::ErrorCode;
use crate::back::cross::{CrossGlslContext, CrossHlslContext, HlslShaderModel};
use crate::back::targets::{GLSL, HLSL};
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
/// Reflect shaders under SPIRV-Cross semantics.
#[derive(Debug)]
pub struct SpirvCross;
// This is "probably" OK.
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T> {}
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T>
where
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
}
pub(crate) struct CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
vertex: Ast<T>,
fragment: Ast<T>,
@ -49,9 +62,6 @@ where
pub fragment: CompiledAst<T>,
}
pub(crate) type HlslReflect = CrossReflect<hlsl::Target>;
pub(crate) type GlslReflect = CrossReflect<glsl::Target>;
impl ValidateTypeSemantics<Type> for UniqueSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let (Type::Float {
@ -84,7 +94,10 @@ impl ValidateTypeSemantics<Type> for UniqueSemantics {
UniqueSemantics::MVP => {
matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 4
}
UniqueSemantics::FrameCount => {
UniqueSemantics::FrameCount
| UniqueSemantics::Rotation
| UniqueSemantics::TotalSubFrames
| UniqueSemantics::CurrentSubFrame => {
matches!(ty, Type::UInt { .. }) && vecsize == 1 && columns == 1
}
UniqueSemantics::FrameDirection => {
@ -134,7 +147,7 @@ impl ValidateTypeSemantics<Type> for TextureSemantics {
}
}
impl<T> TryFrom<&GlslangCompilation> for CrossReflect<T>
impl<T> TryFrom<&SpirvCompilation> for CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
@ -142,7 +155,7 @@ where
{
type Error = ShaderReflectError;
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
fn try_from(value: &SpirvCompilation) -> Result<Self, Self::Error> {
let vertex_module = Module::from_words(&value.vertex);
let fragment_module = Module::from_words(&value.fragment);
@ -722,184 +735,27 @@ where
}
}
impl CompileShader<GLSL> for CrossReflect<glsl::Target> {
type Options = glsl::Version;
type Context = CrossGlslContext;
fn compile(
mut self,
version: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
let mut options: glsl::CompilerOptions = Default::default();
options.version = version;
options.fragment.default_float_precision = glsl::Precision::High;
options.fragment.default_int_precision = glsl::Precision::High;
options.enable_420_pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
for res in &vertex_resources.stage_inputs {
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &vertex_resources.stage_outputs {
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
// self.vertex
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in &fragment_resources.stage_inputs {
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
// self.fragment
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
self.fragment
.unset_decoration(res.id, Decoration::Location)?;
}
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &vertex_resources.uniform_buffers {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
}
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in &fragment_resources.push_constant_buffers {
self.fragment
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
}
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in &fragment_resources.uniform_buffers {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
}
let mut texture_fixups = Vec::new();
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.fragment
.unset_decoration(res.id, Decoration::Binding)?;
let mut name = res.name;
name.push('\0');
texture_fixups.push((name, binding));
}
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
impl CompileShader<HLSL> for CrossReflect<hlsl::Target> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::V5_0);
let mut options = hlsl::CompilerOptions::default();
options.shader_model = sm;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
#[cfg(test)]
mod test {
use crate::reflect::cross::CrossReflect;
use crate::reflect::ReflectShader;
use rustc_hash::FxHashMap;
use crate::back::CompileShader;
use crate::front::GlslangCompilation;
use crate::back::hlsl::CrossHlslContext;
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::front::{Glslang, ShaderInputCompiler};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use spirv_cross::glsl;
use spirv_cross::glsl::{CompilerOptions, Version};
use spirv_cross::hlsl::ShaderModel;
use spirv_cross::{glsl, hlsl};
#[test]
pub fn test_into() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
@ -910,9 +766,9 @@ mod test {
}),
);
}
let spirv = GlslangCompilation::compile(&result).unwrap();
let mut reflect = CrossReflect::<glsl::Target>::try_from(&spirv).unwrap();
let _shader_reflection = reflect
let spirv = Glslang::compile(&result).unwrap();
let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
let shader_reflection = reflect
.reflect(
0,
&ShaderSemantics {
@ -921,10 +777,20 @@ mod test {
},
)
.unwrap();
let mut opts = CompilerOptions::default();
opts.version = Version::V4_60;
opts.enable_420_pack_extension = false;
// let compiled: ShaderCompilerOutput<String, CrossWgslContext> = <CrossReflect<glsl::Target> as CompileShader<WGSL>>::compile(reflect, Version::V3_30).unwrap();
let mut opts = hlsl::CompilerOptions::default();
opts.shader_model = ShaderModel::V3_0;
let compiled: ShaderCompilerOutput<String, CrossHlslContext> =
<CrossReflect<hlsl::Target> as CompileShader<HLSL>>::compile(
reflect,
Some(ShaderModel::V3_0),
)
.unwrap();
println!("{:?}", shader_reflection.meta);
println!("{}", compiled.fragment);
println!("{}", compiled.vertex);
// // eprintln!("{shader_reflection:#?}");
// eprintln!("{}", compiled.fragment)
// let mut loader = rspirv::dr::Loader::new();

View file

@ -0,0 +1,155 @@
use crate::back::msl::CrossMslContext;
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::msl;
use spirv_cross::msl::{ResourceBinding, ResourceBindingLocation};
use spirv_cross::spirv::{Ast, Decoration, ExecutionModel};
use std::collections::BTreeMap;
pub(crate) type MslReflect = CrossReflect<spirv_cross::msl::Target>;
impl CompileShader<MSL> for CrossReflect<spirv_cross::msl::Target> {
type Options = Option<spirv_cross::msl::Version>;
type Context = CrossMslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossMslContext>, ShaderCompileError> {
let version = options.unwrap_or(msl::Version::V2_0);
let mut vert_options = spirv_cross::msl::CompilerOptions::default();
let mut frag_options = spirv_cross::msl::CompilerOptions::default();
vert_options.version = version;
frag_options.version = version;
fn set_bindings(
ast: &Ast<msl::Target>,
stage: ExecutionModel,
binding_map: &mut BTreeMap<ResourceBindingLocation, ResourceBinding>,
) -> Result<(), ShaderCompileError> {
let resources = ast.get_shader_resources()?;
for resource in &resources.push_constant_buffers {
let location = ResourceBindingLocation {
stage,
desc_set: msl::PUSH_CONSTANT_DESCRIPTOR_SET,
binding: msl::PUSH_CONSTANT_BINDING,
};
let overridden = ResourceBinding {
buffer_id: ast.get_decoration(resource.id, Decoration::Binding)?,
texture_id: 0,
sampler_id: 0,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
};
binding_map.insert(location, overridden);
}
for resource in resources
.uniform_buffers
.iter()
.chain(resources.sampled_images.iter())
{
let binding = ast.get_decoration(resource.id, Decoration::Binding)?;
let location = ResourceBindingLocation {
stage,
desc_set: ast.get_decoration(resource.id, Decoration::DescriptorSet)?,
binding,
};
let overridden = ResourceBinding {
buffer_id: binding,
texture_id: binding,
sampler_id: binding,
base_type: None,
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
};
binding_map.insert(location, overridden);
}
Ok(())
}
set_bindings(
&self.vertex,
ExecutionModel::Vertex,
&mut vert_options.resource_binding_overrides,
)?;
set_bindings(
&self.fragment,
ExecutionModel::Fragment,
&mut frag_options.resource_binding_overrides,
)?;
self.vertex.set_compiler_options(&vert_options)?;
self.fragment.set_compiler_options(&frag_options)?;
Ok(ShaderCompilerOutput {
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossMslContext {
artifact: CompiledProgram {
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::{MSL, WGSL};
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::cross::SpirvCross;
use crate::reflect::naga::{Naga, NagaLoweringOptions};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use rustc_hash::FxHashMap;
use spirv_cross::msl;
use std::io::Write;
#[test]
pub fn test_into() {
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl =
<MSL as FromCompilation<_, SpirvCross>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!("{}", compiled.vertex);
}
}

View file

@ -12,7 +12,7 @@ pub mod presets;
mod helper;
#[cfg(feature = "wgsl")]
#[cfg(feature = "naga")]
pub mod naga;
/// A trait for compilation outputs that can provide reflection information.

View file

@ -1,10 +1,20 @@
pub mod msl;
pub mod spirv;
pub mod wgsl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use std::fmt::Debug;
use crate::front::SpirvCompilation;
use naga::{
AddressSpace, Binding, GlobalVariable, Handle, ImageClass, Module, ResourceBinding, Scalar,
ScalarKind, TypeInner, VectorSize,
AddressSpace, Binding, Expression, GlobalVariable, Handle, ImageClass, Module, ResourceBinding,
Scalar, ScalarKind, StructMember, TypeInner, VectorSize,
};
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
use rustc_hash::FxHashSet;
use crate::front::spirv_passes::lower_samplers;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderSemantics, TextureBinding,
@ -14,12 +24,125 @@ use crate::reflect::semantics::{
};
use crate::reflect::{align_uniform_size, ReflectShader, ShaderReflection};
/// Reflect under Naga semantics
///
/// The Naga reflector will lower combined image samplers to split,
/// with the same bind point on descriptor group 1.
#[derive(Debug)]
pub struct Naga;
#[derive(Debug)]
pub(crate) struct NagaReflect {
pub(crate) vertex: Module,
pub(crate) fragment: Module,
}
/// Options to lower samplers and pcbs
#[derive(Debug, Default, Clone)]
pub struct NagaLoweringOptions {
/// Whether to write the PCB as a UBO.
pub write_pcb_as_ubo: bool,
/// The bind group to assign samplers to. This is to ensure that samplers will
/// maintain the same bindings as textures.
pub sampler_bind_group: u32,
}
impl NagaReflect {
pub fn do_lowering(&mut self, options: &NagaLoweringOptions) {
if options.write_pcb_as_ubo {
for (_, gv) in self.fragment.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.space = AddressSpace::Uniform;
}
}
for (_, gv) in self.vertex.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.space = AddressSpace::Uniform;
}
}
} else {
for (_, gv) in self.fragment.global_variables.iter_mut() {
if gv.space == AddressSpace::PushConstant {
gv.binding = None;
}
}
}
// Reassign shit.
let images = self
.fragment
.global_variables
.iter()
.filter(|&(_, gv)| {
let ty = &self.fragment.types[gv.ty];
match ty.inner {
naga::TypeInner::Image { .. } => true,
naga::TypeInner::BindingArray { base, .. } => {
let ty = &self.fragment.types[base];
matches!(ty.inner, naga::TypeInner::Image { .. })
}
_ => false,
}
})
.map(|(_, gv)| (gv.binding.clone(), gv.space))
.collect::<naga::FastHashSet<_>>();
self.fragment
.global_variables
.iter_mut()
.filter(|(_, gv)| {
let ty = &self.fragment.types[gv.ty];
match ty.inner {
naga::TypeInner::Sampler { .. } => true,
naga::TypeInner::BindingArray { base, .. } => {
let ty = &self.fragment.types[base];
matches!(ty.inner, naga::TypeInner::Sampler { .. })
}
_ => false,
}
})
.for_each(|(_, gv)| {
if images.contains(&(gv.binding.clone(), gv.space)) {
if let Some(binding) = &mut gv.binding {
binding.group = options.sampler_bind_group;
}
}
});
}
}
impl TryFrom<&SpirvCompilation> for NagaReflect {
type Error = ShaderReflectError;
fn try_from(compile: &SpirvCompilation) -> Result<Self, Self::Error> {
fn lower_fragment_shader(builder: &mut Builder) {
let mut pass = lower_samplers::LowerCombinedImageSamplerPass::new(builder);
pass.ensure_op_type_sampler();
pass.do_pass();
}
let options = naga::front::spv::Options {
adjust_coordinate_space: true,
strict_capabilities: false,
block_ctx_dump_prefix: None,
};
let vertex = crate::front::spirv_passes::load_module(&compile.vertex);
let fragment = crate::front::spirv_passes::load_module(&compile.fragment);
let mut fragment = Builder::new_from_module(fragment);
lower_fragment_shader(&mut fragment);
let vertex = vertex.assemble();
let fragment = fragment.module().assemble();
let vertex = naga::front::spv::parse_u8_slice(bytemuck::cast_slice(&vertex), &options)?;
let fragment = naga::front::spv::parse_u8_slice(bytemuck::cast_slice(&fragment), &options)?;
Ok(NagaReflect { vertex, fragment })
}
}
impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
fn validate_type(&self, ty: &&TypeInner) -> Option<TypeInfo> {
let (TypeInner::Vector { .. } | TypeInner::Scalar { .. } | TypeInner::Matrix { .. }) = *ty
@ -38,7 +161,10 @@ impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
});
}
}
UniqueSemantics::FrameCount => {
UniqueSemantics::FrameCount
| UniqueSemantics::Rotation
| UniqueSemantics::CurrentSubFrame
| UniqueSemantics::TotalSubFrames => {
// Uint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, width }) if *kind == ScalarKind::Uint && *width == 4)
{
@ -49,7 +175,7 @@ impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
}
}
UniqueSemantics::FrameDirection => {
// Uint32 == width 4
// iint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, width }) if *kind == ScalarKind::Sint && *width == 4)
{
return Some(TypeInfo {
@ -238,13 +364,13 @@ impl NagaReflect {
let binding = self.get_next_binding(0);
// Reassign to UBO later if we want during compilation.
if let Some(vertex_pcb) = vertex_pcb {
let ubo = &mut self.vertex.global_variables[vertex_pcb];
ubo.binding = Some(ResourceBinding { group: 0, binding });
let pcb = &mut self.vertex.global_variables[vertex_pcb];
pcb.binding = Some(ResourceBinding { group: 0, binding });
}
if let Some(fragment_pcb) = fragment_pcb {
let ubo = &mut self.fragment.global_variables[fragment_pcb];
ubo.binding = Some(ResourceBinding { group: 0, binding });
let pcb = &mut self.fragment.global_variables[fragment_pcb];
pcb.binding = Some(ResourceBinding { group: 0, binding });
};
match (vertex_pcb, fragment_pcb) {
@ -474,6 +600,41 @@ impl NagaReflect {
Ok(())
}
fn collect_uniform_names(
module: &Module,
buffer_handle: Handle<GlobalVariable>,
blame: SemanticErrorBlame,
) -> Result<FxHashSet<&StructMember>, ShaderReflectError> {
let mut names = FxHashSet::default();
let ubo = &module.global_variables[buffer_handle];
let TypeInner::Struct { members, .. } = &module.types[ubo.ty].inner else {
return Err(blame.error(SemanticsErrorKind::InvalidResourceType));
};
// struct access is AccessIndex
for (_, fun) in module.functions.iter() {
for (_, expr) in fun.expressions.iter() {
let &Expression::AccessIndex { base, index } = expr else {
continue;
};
let &Expression::GlobalVariable(base) = &fun.expressions[base] else {
continue;
};
if base == buffer_handle {
let member = members
.get(index as usize)
.ok_or(blame.error(SemanticsErrorKind::InvalidRange(index)))?;
names.insert(member);
}
}
}
Ok(names)
}
fn reflect_buffer_struct_members(
module: &Module,
resource: Handle<GlobalVariable>,
@ -483,7 +644,10 @@ impl NagaReflect {
offset_type: UniformMemberBlock,
blame: SemanticErrorBlame,
) -> Result<(), ShaderReflectError> {
let reachable = Self::collect_uniform_names(&module, resource, blame)?;
let resource = &module.global_variables[resource];
let TypeInner::Struct { members, .. } = &module.types[resource.ty].inner else {
return Err(blame.error(SemanticsErrorKind::InvalidResourceType));
};
@ -492,6 +656,11 @@ impl NagaReflect {
let Some(name) = member.name.clone() else {
return Err(blame.error(SemanticsErrorKind::InvalidRange(member.offset)));
};
if !reachable.contains(member) {
continue;
}
let member_type = &module.types[member.ty].inner;
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
@ -756,7 +925,6 @@ impl ReflectShader for NagaReflect {
});
let push_constant = self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
let mut meta = BindingMeta::default();
if let Some(ubo) = vertex_ubo {
@ -837,6 +1005,10 @@ impl ReflectShader for NagaReflect {
#[cfg(test)]
mod test {
use crate::reflect::semantics::{Semantic, TextureSemantics, UniformSemantic};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use librashader_presets::ShaderPreset;
// #[test]
// pub fn test_into() {
@ -855,4 +1027,19 @@ mod test {
//
// println!("{outputs:#?}");
// }
// #[test]
// pub fn mega_bezel_reflect() {
// let preset = ShaderPreset::try_parse(
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
// )
// .unwrap();
//
// let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
// let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
//
//
//
//
// }
}

View file

@ -0,0 +1,207 @@
use crate::back::msl::{MslVersion, NagaMslContext, NagaMslModule};
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
use naga::back::msl::{
BindSamplerTarget, BindTarget, EntryPointResources, Options, PipelineOptions, TranslationInfo,
};
use naga::valid::{Capabilities, ValidationFlags};
use naga::{Module, TypeInner};
use spirv_cross::msl::Version;
fn msl_version_to_naga_msl(version: MslVersion) -> (u8, u8) {
match version {
Version::V1_0 => (1, 0),
Version::V1_1 => (1, 1),
Version::V1_2 => (1, 2),
Version::V2_0 => (2, 0),
Version::V2_1 => (2, 1),
Version::V2_2 => (2, 2),
Version::V2_3 => (2, 3),
_ => (0, 0),
}
}
impl CompileShader<MSL> for NagaReflect {
type Options = Option<crate::back::msl::MslVersion>;
type Context = NagaMslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
// https://github.com/libretro/RetroArch/blob/434e94c782af2e4d4277a24b7ed8e5fc54870088/gfx/drivers_shader/slang_process.cpp#L524
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::V2_0));
let mut vert_options = Options {
lang_version,
per_entry_point_map: Default::default(),
inline_samplers: vec![],
spirv_cross_compatibility: true,
fake_missing_bindings: false,
bounds_check_policies: Default::default(),
zero_initialize_workgroup_memory: false,
};
let mut frag_options = vert_options.clone();
fn write_msl(
module: &Module,
options: Options,
) -> Result<(String, TranslationInfo), ShaderCompileError> {
let mut valid =
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
let info = valid.validate(&module)?;
let pipeline_options = PipelineOptions {
allow_and_force_point_size: false,
};
let msl = naga::back::msl::write_string(&module, &info, &options, &pipeline_options)?;
Ok(msl)
}
fn generate_bindings(module: &Module) -> EntryPointResources {
let mut resources = EntryPointResources::default();
let binding_map = &mut resources.resources;
// Don't set PCB because they'll be gone after lowering..
// resources.push_constant_buffer = Some(1u8);
for (_, variable) in module.global_variables.iter() {
let Some(binding) = &variable.binding else {
continue;
};
let Ok(ty) = module.types.get_handle(variable.ty) else {
continue;
};
match ty.inner {
TypeInner::Sampler { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: None,
texture: None,
sampler: Some(BindSamplerTarget::Resource(binding.binding as u8)),
binding_array_size: None,
mutable: false,
},
);
}
TypeInner::Struct { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: Some(binding.binding as u8),
texture: None,
sampler: None,
binding_array_size: None,
mutable: false,
},
);
}
TypeInner::Image { .. } => {
binding_map.insert(
binding.clone(),
BindTarget {
buffer: None,
texture: Some(binding.binding as u8),
sampler: None,
binding_array_size: None,
mutable: false,
},
);
}
_ => continue,
}
}
resources
}
self.do_lowering(&NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
});
frag_options
.per_entry_point_map
.insert(String::from("main"), generate_bindings(&self.fragment));
vert_options
.per_entry_point_map
.insert(String::from("main"), generate_bindings(&self.vertex));
let fragment = write_msl(&self.fragment, frag_options)?;
let vertex = write_msl(&self.vertex, vert_options)?;
let vertex_binding = self.get_next_binding(0);
Ok(ShaderCompilerOutput {
vertex: vertex.0,
fragment: fragment.0,
context: NagaMslContext {
fragment: NagaMslModule {
translation_info: fragment.1,
module: self.fragment,
},
vertex: NagaMslModule {
translation_info: vertex.1,
module: self.vertex,
},
next_free_binding: vertex_binding,
},
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::MSL;
use crate::back::{CompileShader, FromCompilation};
use crate::reflect::naga::{Naga, NagaLoweringOptions};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use spirv_cross::msl;
#[test]
pub fn test_into() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!(
"{:?}",
compiled.context.fragment.translation_info.entry_point_names
);
}
}

View file

@ -0,0 +1,54 @@
use crate::back::spirv::{NagaSpirvContext, NagaSpirvOptions};
use crate::back::targets::SPIRV;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::NagaReflect;
use naga::back::spv::PipelineOptions;
use naga::valid::{Capabilities, ValidationFlags};
use naga::Module;
impl CompileShader<SPIRV> for NagaReflect {
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
fn write_spv(
module: &Module,
stage: naga::ShaderStage,
version: (u8, u8),
) -> Result<Vec<u32>, ShaderCompileError> {
let mut valid =
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
let info = valid.validate(&module)?;
let mut options = naga::back::spv::Options::default();
options.lang_version = version;
let spv = naga::back::spv::write_vec(
&module,
&info,
&options,
Some(&PipelineOptions {
shader_stage: stage,
entry_point: "main".to_string(),
}),
)?;
Ok(spv)
}
self.do_lowering(&options.lowering);
let fragment = write_spv(&self.fragment, naga::ShaderStage::Fragment, options.version)?;
let vertex = write_spv(&self.vertex, naga::ShaderStage::Vertex, options.version)?;
Ok(ShaderCompilerOutput {
vertex,
fragment,
context: NagaSpirvContext {
fragment: self.fragment,
vertex: self.vertex,
},
})
}
}

View file

@ -0,0 +1,41 @@
use crate::back::targets::WGSL;
use crate::back::wgsl::NagaWgslContext;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
use naga::back::wgsl::WriterFlags;
use naga::valid::{Capabilities, ModuleInfo, ValidationFlags, Validator};
use naga::Module;
impl CompileShader<WGSL> for NagaReflect {
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
fn compile(
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
fn write_wgsl(module: &Module, info: &ModuleInfo) -> Result<String, ShaderCompileError> {
let wgsl = naga::back::wgsl::write_string(&module, &info, WriterFlags::empty())?;
Ok(wgsl)
}
self.do_lowering(&options);
let mut valid = Validator::new(ValidationFlags::all(), Capabilities::empty());
let vertex_info = valid.validate(&self.vertex)?;
let fragment_info = valid.validate(&self.fragment)?;
let fragment = write_wgsl(&self.fragment, &fragment_info)?;
let vertex = write_wgsl(&self.vertex, &vertex_info)?;
Ok(ShaderCompilerOutput {
vertex,
fragment,
context: NagaWgslContext {
fragment: self.fragment,
vertex: self.vertex,
},
})
}
}

View file

@ -1,13 +1,13 @@
use crate::back::targets::OutputTarget;
use crate::back::{CompilerBackend, FromCompilation};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::ShaderCompilation;
use crate::front::{ShaderInputCompiler, ShaderReflectObject};
use crate::reflect::semantics::{
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
};
use librashader_common::map::FastHashMap;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ShaderPassConfig, TextureConfig};
use rustc_hash::FxHashMap;
/// Artifacts of a reflected and compiled shader pass.
///
@ -18,11 +18,12 @@ use rustc_hash::FxHashMap;
/// ```rust
/// #![feature(type_alias_impl_trait)]
/// use librashader_reflect::back::CompileReflectShader;
/// use librashader_reflect::back::targets::SPIRV;
/// use librashader_reflect::front::GlslangCompilation;
/// use librashader_reflect::back::targets::{GLSL, SPIRV};
/// use librashader_reflect::front::SpirvCompilation;
/// use librashader_reflect::reflect::cross::SpirvCross;
/// use librashader_reflect::reflect::presets::ShaderPassArtifact;
///
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, GlslangCompilation>>;
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>>;
/// ```
///
/// This allows a runtime to not name the backing type of the compiled artifact if not necessary.
@ -35,57 +36,59 @@ impl<T: OutputTarget> CompilePresetTarget for T {}
pub trait CompilePresetTarget: OutputTarget {
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<C, E>(
fn compile_preset_passes<I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<Self as FromCompilation<C>>::Output>>,
Vec<ShaderPassArtifact<<Self as FromCompilation<I, R>>::Output>>,
ShaderSemantics,
),
E,
>
where
I: ShaderReflectObject,
Self: Sized,
Self: FromCompilation<C>,
C: ShaderCompilation,
Self: FromCompilation<I, R>,
I::Compiler: ShaderInputCompiler<I>,
E: From<PreprocessError>,
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
compile_preset_passes::<Self, C, E>(passes, textures)
compile_preset_passes::<Self, I, R, E>(passes, textures)
}
}
/// Compile passes of a shader preset given the applicable
/// shader output target, compilation type, and resulting error.
fn compile_preset_passes<T, C, E>(
fn compile_preset_passes<T, I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<T as FromCompilation<C>>::Output>>,
Vec<ShaderPassArtifact<<T as FromCompilation<I, R>>::Output>>,
ShaderSemantics,
),
E,
>
where
I: ShaderReflectObject,
T: OutputTarget,
T: FromCompilation<C>,
C: ShaderCompilation,
T: FromCompilation<I, R>,
I::Compiler: ShaderInputCompiler<I>,
E: From<PreprocessError>,
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FxHashMap<String, Semantic<TextureSemantics>> = Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
let passes = passes
.into_iter()
.map(|shader| {
let source: ShaderSource = ShaderSource::load(&shader.name)?;
let compiled = C::compile(&source)?;
let compiled = I::Compiler::compile(&source)?;
let reflect = T::from_compilation(compiled)?;
for parameter in source.parameters.values() {
@ -116,8 +119,8 @@ where
/// Insert the available semantics for the input pass config into the provided semantic maps.
fn insert_pass_semantics(
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
config: &ShaderPassConfig,
) {
let Some(alias) = &config.alias else {
@ -167,8 +170,8 @@ fn insert_pass_semantics(
/// Insert the available semantics for the input texture config into the provided semantic maps.
fn insert_lut_semantics(
textures: &[TextureConfig],
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
) {
for (index, texture) in textures.iter().enumerate() {
texture_semantics.insert(

View file

@ -1,5 +1,5 @@
use bitflags::bitflags;
use rustc_hash::FxHashMap;
use librashader_common::map::FastHashMap;
use std::str::FromStr;
/// The maximum number of bindings allowed in a shader.
@ -40,11 +40,18 @@ pub enum UniqueSemantics {
/// The frame count, possibly with shader-defined modulo.
FrameCount = 3,
// int, frame direction
/// The frame direction.
/// The direction in time where frames are rendered
FrameDirection = 4,
//int, rotation (glUniform1i(uni->rotation, retroarch_get_rotation());)
/// The rotation index (0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg)
Rotation = 5,
/// Total number of subframes.
TotalSubFrames = 6,
/// The current subframe (default 1)
CurrentSubFrame = 7,
/// A user defined float parameter.
// float, user defined parameter, array
FloatParameter = 5,
FloatParameter = 8,
}
impl UniqueSemantics {
@ -64,6 +71,9 @@ impl UniqueSemantics {
UniqueSemantics::FinalViewport => UniformType::Vec4,
UniqueSemantics::FrameCount => UniformType::Unsigned,
UniqueSemantics::FrameDirection => UniformType::Signed,
UniqueSemantics::Rotation => UniformType::Unsigned,
UniqueSemantics::TotalSubFrames => UniformType::Unsigned,
UniqueSemantics::CurrentSubFrame => UniformType::Unsigned,
UniqueSemantics::FloatParameter => UniformType::Float,
}
}
@ -162,6 +172,7 @@ pub struct Semantic<T, I = usize> {
bitflags! {
/// The pipeline stage for which a uniform is bound.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct BindingStage: u8 {
const NONE = 0b00000000;
const VERTEX = 0b00000001;
@ -310,7 +321,7 @@ pub trait TextureSemanticMap {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
}
impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
@ -342,7 +353,7 @@ impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
}
}
impl TextureSemanticMap for FxHashMap<String, Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
@ -379,7 +390,7 @@ pub trait UniqueSemanticMap {
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
}
impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
match self.get(name) {
// existing uniforms in the semantic map have priority
@ -404,6 +415,18 @@ impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
semantics: UniqueSemantics::FrameDirection,
index: (),
}),
"Rotation" => Some(Semantic {
semantics: UniqueSemantics::Rotation,
index: (),
}),
"TotalSubFrames" => Some(Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
}),
"CurrentSubFrame" => Some(Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
}),
_ => None,
},
Some(UniformSemantic::Unique(variable)) => Some(*variable),
@ -425,9 +448,9 @@ pub enum UniformSemantic {
#[derive(Debug, Clone)]
pub struct ShaderSemantics {
/// A map of uniform names to filter chain semantics.
pub uniform_semantics: FxHashMap<String, UniformSemantic>,
pub uniform_semantics: FastHashMap<String, UniformSemantic>,
/// A map of texture names to filter chain semantics.
pub texture_semantics: FxHashMap<String, Semantic<TextureSemantics>>,
pub texture_semantics: FastHashMap<String, Semantic<TextureSemantics>>,
}
/// The binding of a uniform after the shader has been linked.
@ -460,11 +483,11 @@ impl From<Semantic<TextureSemantics>> for UniformBinding {
#[derive(Debug, Default)]
pub struct BindingMeta {
/// A map of parameter names to uniform binding metadata.
pub parameter_meta: FxHashMap<String, VariableMeta>,
pub parameter_meta: FastHashMap<String, VariableMeta>,
/// A map of unique semantics to uniform binding metadata.
pub unique_meta: FxHashMap<UniqueSemantics, VariableMeta>,
pub unique_meta: FastHashMap<UniqueSemantics, VariableMeta>,
/// A map of texture semantics to texture binding points.
pub texture_meta: FxHashMap<Semantic<TextureSemantics>, TextureBinding>,
pub texture_meta: FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
/// A map of texture semantics to texture size uniform binding metadata.
pub texture_size_meta: FxHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
pub texture_size_meta: FastHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
}

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d11"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.7"
version = "0.2.7"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,22 +12,20 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.2.0-beta.7" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.7" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.7" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.7" }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.7" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.7", features = ["d3d"] }
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.2.7" }
librashader-presets = { path = "../librashader-presets", version = "0.2.7" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.7" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.7" }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.7" }
librashader-cache = { path = "../librashader-cache", version = "0.2.7", features = ["d3d"] }
thiserror = "1.0.37"
rustc-hash = "1.1.0"
bytemuck = "1.12.3"
rayon = "1.6.1"
array-concat = "0.5.2"
[target.'cfg(windows)'.dependencies.windows]
version = "0.48.0"
workspace = true
features = [
"Win32_Foundation",
"Win32_Graphics_Dxgi_Common",
@ -39,7 +37,7 @@ features = [
]
[target.'cfg(windows)'.dev-dependencies.windows]
version = "0.48.0"
workspace = true
features = [
"Win32_Foundation",
"Win32_Graphics_Dxgi_Common",
@ -51,6 +49,7 @@ features = [
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_UI",
]
[[test]]

View file

@ -2,7 +2,7 @@ use crate::error;
use crate::error::assume_d3d11_init;
use array_concat::concat_arrays;
use bytemuck::offset_of;
use librashader_runtime::quad::QuadType;
use librashader_runtime::quad::{QuadType, VertexInput};
use windows::core::PCSTR;
use windows::Win32::Graphics::Direct3D::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
use windows::Win32::Graphics::Direct3D11::{
@ -12,63 +12,45 @@ use windows::Win32::Graphics::Direct3D11::{
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R32G32_FLOAT;
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
struct D3D11Vertex {
position: [f32; 2],
texcoord: [f32; 2],
color: [f32; 4],
}
const CLEAR: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
const OFFSCREEN_VBO_DATA: [D3D11Vertex; 4] = [
D3D11Vertex {
position: [-1.0, -1.0],
const OFFSCREEN_VBO_DATA: [VertexInput; 4] = [
VertexInput {
position: [-1.0, -1.0, 0.0, 1.0],
texcoord: [0.0, 1.0],
color: CLEAR,
},
D3D11Vertex {
position: [-1.0, 1.0],
VertexInput {
position: [-1.0, 1.0, 0.0, 1.0],
texcoord: [0.0, 0.0],
color: CLEAR,
},
D3D11Vertex {
position: [1.0, -1.0],
VertexInput {
position: [1.0, -1.0, 0.0, 1.0],
texcoord: [1.0, 1.0],
color: CLEAR,
},
D3D11Vertex {
position: [1.0, 1.0],
VertexInput {
position: [1.0, 1.0, 0.0, 1.0],
texcoord: [1.0, 0.0],
color: CLEAR,
},
];
const FINAL_VBO_DATA: [D3D11Vertex; 4] = [
D3D11Vertex {
position: [0.0, 0.0],
const FINAL_VBO_DATA: [VertexInput; 4] = [
VertexInput {
position: [0.0, 0.0, 0.0, 1.0],
texcoord: [0.0, 1.0],
color: CLEAR,
},
D3D11Vertex {
position: [0.0, 1.0],
VertexInput {
position: [0.0, 1.0, 0.0, 1.0],
texcoord: [0.0, 0.0],
color: CLEAR,
},
D3D11Vertex {
position: [1.0, 0.0],
VertexInput {
position: [1.0, 0.0, 0.0, 1.0],
texcoord: [1.0, 1.0],
color: CLEAR,
},
D3D11Vertex {
position: [1.0, 1.0],
VertexInput {
position: [1.0, 1.0, 0.0, 1.0],
texcoord: [1.0, 0.0],
color: CLEAR,
},
];
static VBO_DATA: &[D3D11Vertex; 8] = &concat_arrays!(OFFSCREEN_VBO_DATA, FINAL_VBO_DATA);
static VBO_DATA: &[VertexInput; 8] = &concat_arrays!(OFFSCREEN_VBO_DATA, FINAL_VBO_DATA);
pub(crate) struct DrawQuad {
stride: u32,
@ -81,9 +63,9 @@ impl DrawQuad {
let mut vbo = None;
device.CreateBuffer(
&D3D11_BUFFER_DESC {
ByteWidth: 2 * std::mem::size_of::<[D3D11Vertex; 4]>() as u32,
ByteWidth: 2 * std::mem::size_of::<[VertexInput; 4]>() as u32,
Usage: D3D11_USAGE_IMMUTABLE,
BindFlags: D3D11_BIND_VERTEX_BUFFER,
BindFlags: D3D11_BIND_VERTEX_BUFFER.0 as u32,
CPUAccessFlags: Default::default(),
MiscFlags: Default::default(),
StructureByteStride: 0,
@ -99,7 +81,7 @@ impl DrawQuad {
Ok(DrawQuad {
vbo,
stride: std::mem::size_of::<D3D11Vertex>() as u32,
stride: std::mem::size_of::<VertexInput>() as u32,
})
}
}
@ -136,7 +118,7 @@ impl DrawQuad {
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: offset_of!(D3D11Vertex, position) as u32,
AlignedByteOffset: offset_of!(VertexInput, position) as u32,
InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
@ -145,7 +127,7 @@ impl DrawQuad {
SemanticIndex: 1,
Format: DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: offset_of!(D3D11Vertex, texcoord) as u32,
AlignedByteOffset: offset_of!(VertexInput, texcoord) as u32,
InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},

View file

@ -13,8 +13,6 @@ pub enum FilterChainError {
Direct3DOperationError(&'static str),
#[error("direct3d driver error")]
Direct3DError(#[from] windows::core::Error),
#[error("SPIRV reflection error")]
SpirvCrossReflectError(#[from] spirv_cross::ErrorCode),
#[error("shader preset parse error")]
ShaderPresetError(#[from] ParsePresetError),
#[error("shader preprocess error")]

View file

@ -1,14 +1,14 @@
use crate::texture::{D3D11InputView, InputTexture, LutTexture};
use crate::texture::{D3D11InputView, InputTexture};
use librashader_common::{ImageFormat, Size, Viewport};
use librashader_common::map::FastHashMap;
use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig};
use librashader_reflect::back::targets::HLSL;
use librashader_reflect::back::{CompileReflectShader, CompileShader};
use librashader_reflect::front::GlslangCompilation;
use librashader_reflect::front::SpirvCompilation;
use librashader_reflect::reflect::semantics::ShaderSemantics;
use librashader_reflect::reflect::ReflectShader;
use librashader_runtime::image::{Image, ImageError, UVDirection};
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::path::Path;
@ -18,12 +18,15 @@ use crate::error::{assume_d3d11_init, FilterChainError};
use crate::filter_pass::{ConstantBufferBinding, FilterPass};
use crate::framebuffer::OwnedImage;
use crate::graphics_pipeline::D3D11State;
use crate::luts::LutTexture;
use crate::options::{FilterChainOptionsD3D11, FrameOptionsD3D11};
use crate::samplers::SamplerSet;
use crate::util::d3d11_compile_bound_shader;
use crate::{error, util, D3D11OutputView};
use librashader_cache::cache_shader_object;
use librashader_cache::CachedCompilation;
use librashader_presets::context::VideoDriver;
use librashader_reflect::reflect::cross::SpirvCross;
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
use librashader_runtime::binding::{BindingUtil, TextureInput};
use librashader_runtime::framebuffer::FramebufferInit;
@ -34,15 +37,14 @@ use librashader_runtime::uniforms::UniformStorage;
use rayon::prelude::*;
use windows::Win32::Graphics::Direct3D11::{
ID3D11Buffer, ID3D11Device, ID3D11DeviceContext, D3D11_BIND_CONSTANT_BUFFER, D3D11_BUFFER_DESC,
D3D11_CPU_ACCESS_WRITE, D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_RESOURCE_MISC_FLAG,
D3D11_RESOURCE_MISC_GENERATE_MIPS, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
D3D11_USAGE_DYNAMIC,
D3D11_CPU_ACCESS_WRITE, D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_RESOURCE_MISC_GENERATE_MIPS,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, D3D11_USAGE_DYNAMIC,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM;
pub struct FilterMutable {
pub(crate) passes_enabled: usize,
pub(crate) parameters: FxHashMap<String, f32>,
pub(crate) parameters: FastHashMap<String, f32>,
}
/// A Direct3D 11 filter chain.
@ -53,6 +55,7 @@ pub struct FilterChainD3D11 {
feedback_framebuffers: Box<[OwnedImage]>,
history_framebuffers: VecDeque<OwnedImage>,
state: D3D11State,
default_options: FrameOptionsD3D11,
}
pub(crate) struct Direct3D11 {
@ -62,7 +65,7 @@ pub(crate) struct Direct3D11 {
pub(crate) struct FilterCommon {
pub(crate) d3d11: Direct3D11,
pub(crate) luts: FxHashMap<usize, LutTexture>,
pub(crate) luts: FastHashMap<usize, LutTexture>,
pub samplers: SamplerSet,
pub output_textures: Box<[Option<InputTexture>]>,
pub feedback_textures: Box<[Option<InputTexture>]>,
@ -73,18 +76,22 @@ pub(crate) struct FilterCommon {
}
type ShaderPassMeta =
ShaderPassArtifact<impl CompileReflectShader<HLSL, GlslangCompilation> + Send>;
ShaderPassArtifact<impl CompileReflectShader<HLSL, SpirvCompilation, SpirvCross> + Send>;
fn compile_passes(
shaders: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
disable_cache: bool,
) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), FilterChainError> {
let (passes, semantics) = if !disable_cache {
HLSL::compile_preset_passes::<CachedCompilation<GlslangCompilation>, FilterChainError>(
HLSL::compile_preset_passes::<
CachedCompilation<SpirvCompilation>,
SpirvCross,
FilterChainError,
>(shaders, &textures)?
} else {
HLSL::compile_preset_passes::<SpirvCompilation, SpirvCross, FilterChainError>(
shaders, &textures,
)?
} else {
HLSL::compile_preset_passes::<GlslangCompilation, FilterChainError>(shaders, &textures)?
};
Ok((passes, semantics))
@ -98,7 +105,7 @@ impl FilterChainD3D11 {
options: Option<&FilterChainOptionsD3D11>,
) -> error::Result<FilterChainD3D11> {
// load passes from preset
let preset = ShaderPreset::try_parse(path)?;
let preset = ShaderPreset::try_parse_with_driver_context(path, VideoDriver::Direct3D11)?;
unsafe { Self::load_from_preset(preset, device, options) }
}
@ -196,6 +203,7 @@ impl FilterChainD3D11 {
draw_quad,
},
state,
default_options: Default::default(),
})
}
}
@ -208,9 +216,9 @@ impl FilterChainD3D11 {
&D3D11_BUFFER_DESC {
ByteWidth: size,
Usage: D3D11_USAGE_DYNAMIC,
BindFlags: D3D11_BIND_CONSTANT_BUFFER,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE,
MiscFlags: D3D11_RESOURCE_MISC_FLAG(0),
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
StructureByteStride: 0,
},
None,
@ -347,8 +355,8 @@ impl FilterChainD3D11 {
device: &ID3D11Device,
context: &ID3D11DeviceContext,
textures: &[TextureConfig],
) -> error::Result<FxHashMap<usize, LutTexture>> {
let mut luts = FxHashMap::default();
) -> error::Result<FastHashMap<usize, LutTexture>> {
let mut luts = FastHashMap::default();
let images = textures
.par_iter()
.map(|texture| Image::load(&texture.path, UVDirection::TopLeft))
@ -361,9 +369,9 @@ impl FilterChainD3D11 {
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Usage: D3D11_USAGE_DEFAULT,
MiscFlags: if texture.mipmap {
D3D11_RESOURCE_MISC_GENERATE_MIPS
D3D11_RESOURCE_MISC_GENERATE_MIPS.0 as u32
} else {
D3D11_RESOURCE_MISC_FLAG(0)
0
},
..Default::default()
};
@ -409,7 +417,7 @@ impl FilterChainD3D11 {
return Ok(());
}
let frame_direction = options.map_or(1, |f| f.frame_direction);
let options = options.unwrap_or(&self.default_options);
let filter = passes[0].config.filter;
let wrap_mode = passes[0].config.wrap_mode;
@ -434,6 +442,7 @@ impl FilterChainD3D11 {
OwnedImage::scale_framebuffers(
source.size(),
viewport.output.size,
original.view.size,
&mut self.output_framebuffers,
&mut self.feedback_framebuffers,
passes,
@ -472,7 +481,7 @@ impl FilterChainD3D11 {
index,
&self.common,
pass.config.get_frame_count(frame_count),
frame_direction,
options,
viewport,
&original,
&source,
@ -501,7 +510,7 @@ impl FilterChainD3D11 {
passes_len - 1,
&self.common,
pass.config.get_frame_count(frame_count),
frame_direction,
options,
viewport,
&original,
&source,

View file

@ -1,5 +1,8 @@
use crate::filter_chain::FilterCommon;
use crate::options::FrameOptionsD3D11;
use crate::texture::InputTexture;
use librashader_common::map::FastHashMap;
use librashader_common::{ImageFormat, Size, Viewport};
use librashader_preprocess::ShaderSource;
use librashader_presets::ShaderPassConfig;
@ -7,9 +10,8 @@ use librashader_reflect::reflect::semantics::{
BindingStage, MemberOffset, TextureBinding, UniformBinding,
};
use librashader_reflect::reflect::ShaderReflection;
use rustc_hash::FxHashMap;
use librashader_runtime::binding::{BindSemantics, TextureInput};
use librashader_runtime::binding::{BindSemantics, TextureInput, UniformInputs};
use librashader_runtime::filter_pass::FilterPassMeta;
use librashader_runtime::quad::QuadType;
use librashader_runtime::render_target::RenderTarget;
@ -37,7 +39,7 @@ pub struct FilterPass {
pub vertex_layout: ID3D11InputLayout,
pub pixel_shader: ID3D11PixelShader,
pub uniform_bindings: FxHashMap<UniformBinding, MemberOffset>,
pub uniform_bindings: FastHashMap<UniformBinding, MemberOffset>,
pub uniform_storage: UniformStorage,
pub uniform_buffer: Option<ConstantBufferBinding>,
@ -100,7 +102,7 @@ impl FilterPass {
parent: &FilterCommon,
mvp: &[f32; 16],
frame_count: u32,
frame_direction: i32,
options: &FrameOptionsD3D11,
fb_size: Size<u32>,
viewport_size: Size<u32>,
mut descriptors: (
@ -115,11 +117,16 @@ impl FilterPass {
&parent.samplers,
&mut self.uniform_storage,
&mut descriptors,
UniformInputs {
mvp,
frame_count,
frame_direction,
fb_size,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
frame_direction: options.frame_direction,
framebuffer_size: fb_size,
viewport_size,
},
original,
source,
&self.uniform_bindings,
@ -141,7 +148,7 @@ impl FilterPass {
pass_index: usize,
parent: &FilterCommon,
frame_count: u32,
frame_direction: i32,
options: &FrameOptionsD3D11,
viewport: &Viewport<D3D11OutputView>,
original: &InputTexture,
source: &InputTexture,
@ -168,7 +175,7 @@ impl FilterPass {
parent,
output.mvp,
frame_count,
frame_direction,
options,
output.output.size,
viewport.output.size,
descriptors,

View file

@ -10,7 +10,7 @@ use windows::Win32::Graphics::Direct3D::D3D_SRV_DIMENSION_TEXTURE2D;
use windows::Win32::Graphics::Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11RenderTargetView, ID3D11ShaderResourceView,
ID3D11Texture2D, D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_BOX,
D3D11_CPU_ACCESS_FLAG, D3D11_FORMAT_SUPPORT_RENDER_TARGET, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE,
D3D11_FORMAT_SUPPORT_RENDER_TARGET, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE,
D3D11_FORMAT_SUPPORT_TEXTURE2D, D3D11_RENDER_TARGET_VIEW_DESC, D3D11_RENDER_TARGET_VIEW_DESC_0,
D3D11_RESOURCE_MISC_GENERATE_MIPS, D3D11_RTV_DIMENSION_TEXTURE2D,
D3D11_SHADER_RESOURCE_VIEW_DESC, D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_TEX2D_RTV,
@ -69,9 +69,10 @@ impl OwnedImage {
format: ImageFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
original_size: &Size<u32>,
should_mipmap: bool,
) -> error::Result<Size<u32>> {
let size = source_size.scale_viewport(scaling, *viewport_size);
let size = source_size.scale_viewport(scaling, *viewport_size, *original_size);
if self.size != size
|| (should_mipmap && self.max_mipmap == 1)
@ -99,7 +100,7 @@ impl OwnedImage {
pub fn clear(&mut self, ctx: &ID3D11DeviceContext) -> error::Result<()> {
let rtv = self.create_render_target_view()?;
unsafe {
ctx.ClearRenderTargetView(&rtv, CLEAR.as_ptr());
ctx.ClearRenderTargetView(&rtv, CLEAR);
}
Ok(())
}
@ -239,9 +240,9 @@ fn default_desc(size: Size<u32>, format: DXGI_FORMAT, mip_levels: u32) -> D3D11_
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
CPUAccessFlags: D3D11_CPU_ACCESS_FLAG(0),
MiscFlags: D3D11_RESOURCE_MISC_GENERATE_MIPS,
BindFlags: (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET).0 as u32,
CPUAccessFlags: 0,
MiscFlags: D3D11_RESOURCE_MISC_GENERATE_MIPS.0 as u32,
}
}
@ -255,9 +256,17 @@ impl ScaleFramebuffer for OwnedImage {
format: ImageFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
original_size: &Size<u32>,
should_mipmap: bool,
_context: &Self::Context,
) -> Result<Size<u32>, Self::Error> {
self.scale(scaling, format, viewport_size, source_size, should_mipmap)
self.scale(
scaling,
format,
viewport_size,
source_size,
original_size,
should_mipmap,
)
}
}

View file

@ -84,7 +84,7 @@ impl D3D11State {
let mut saved_blend_mask = 0;
context.OMGetBlendState(
Some(&mut saved_blend),
Some(saved_blend_factor.as_mut_ptr()),
Some(&mut saved_blend_factor),
Some(&mut saved_blend_mask),
);
let saved_rs = context.RSGetState().ok();
@ -113,7 +113,7 @@ impl Drop for D3D11StateSaveGuard<'_> {
self.ctx.RSSetState(self.saved_rs.as_ref());
self.ctx.OMSetBlendState(
self.saved_blend.as_ref(),
Some(self.saved_blend_factor.as_ptr()),
Some(&self.saved_blend_factor),
self.saved_blend_mask,
);
}

View file

@ -13,14 +13,17 @@ mod filter_chain;
mod filter_pass;
mod framebuffer;
mod graphics_pipeline;
mod parameters;
mod samplers;
mod texture;
mod util;
pub mod error;
mod luts;
pub mod options;
use librashader_runtime::impl_filter_chain_parameters;
impl_filter_chain_parameters!(FilterChainD3D11);
pub use filter_chain::FilterChainD3D11;
pub use texture::D3D11InputView;
pub use texture::D3D11OutputView;

View file

@ -0,0 +1,152 @@
use crate::error::assume_d3d11_init;
use crate::texture::InputTexture;
use crate::{error, D3D11InputView};
use librashader_common::{FilterMode, WrapMode};
use librashader_runtime::image::Image;
use librashader_runtime::scaling::MipmapSize;
use windows::Win32::Graphics::Direct3D::D3D_SRV_DIMENSION_TEXTURE2D;
use windows::Win32::Graphics::Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET,
D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_CPU_ACCESS_WRITE,
D3D11_RESOURCE_MISC_GENERATE_MIPS, D3D11_SHADER_RESOURCE_VIEW_DESC,
D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_SUBRESOURCE_DATA, D3D11_TEX2D_SRV,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DYNAMIC, D3D11_USAGE_STAGING,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_SAMPLE_DESC;
#[derive(Debug, Clone)]
pub(crate) struct LutTexture {
// The handle to the Texture2D must be kept alive.
#[allow(dead_code)]
pub handle: ID3D11Texture2D,
#[allow(dead_code)]
pub desc: D3D11_TEXTURE2D_DESC,
pub image: InputTexture,
}
impl AsRef<InputTexture> for LutTexture {
fn as_ref(&self) -> &InputTexture {
&self.image
}
}
impl LutTexture {
pub fn new(
device: &ID3D11Device,
context: &ID3D11DeviceContext,
source: &Image,
desc: D3D11_TEXTURE2D_DESC,
filter: FilterMode,
wrap_mode: WrapMode,
) -> error::Result<LutTexture> {
let mut desc = D3D11_TEXTURE2D_DESC {
Width: source.size.width,
Height: source.size.height,
// todo: set this to 0
MipLevels: if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS.0 as u32) != 0 {
0
} else {
1
},
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: if desc.Usage == D3D11_USAGE_DYNAMIC {
D3D11_CPU_ACCESS_WRITE.0 as u32
} else {
0
},
..desc
};
desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE.0 as u32;
// determine number of mipmaps required
if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS.0 as u32) != 0 {
desc.BindFlags |= D3D11_BIND_RENDER_TARGET.0 as u32;
desc.MipLevels = source.size.calculate_miplevels();
}
// Don't need to determine format support because LUTs are always DXGI_FORMAT_R8G8B8A8_UNORM
// since we load them with the Image module.
unsafe {
let mut handle = None;
device.CreateTexture2D(&desc, None, Some(&mut handle))?;
assume_d3d11_init!(handle, "CreateTexture2D");
// need a staging texture to defer mipmap generation
let mut staging = None;
device.CreateTexture2D(
&D3D11_TEXTURE2D_DESC {
MipLevels: 1,
BindFlags: 0,
MiscFlags: 0,
Usage: D3D11_USAGE_STAGING,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
..desc
},
Some(&D3D11_SUBRESOURCE_DATA {
pSysMem: source.bytes.as_ptr().cast(),
SysMemPitch: source.pitch as u32,
SysMemSlicePitch: 0,
}),
Some(&mut staging),
)?;
assume_d3d11_init!(staging, "CreateTexture2D");
context.CopySubresourceRegion(
&handle,
0,
0,
0,
0,
&staging,
0,
Some(&D3D11_BOX {
left: 0,
top: 0,
front: 0,
right: source.size.width,
bottom: source.size.height,
back: 1,
}),
);
let mut srv = None;
device.CreateShaderResourceView(
&handle,
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),
)?;
assume_d3d11_init!(srv, "CreateShaderResourceView");
if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS.0 as u32) != 0 {
context.GenerateMips(&srv)
}
Ok(LutTexture {
handle,
desc,
image: InputTexture {
view: D3D11InputView {
handle: srv,
size: source.size,
},
filter,
wrap_mode,
},
})
}
}
}

View file

@ -1,15 +1,7 @@
//! Direct3D 11 shader runtime options.
/// Options for each Direct3D 11 shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct FrameOptionsD3D11 {
/// 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,
}
use librashader_runtime::impl_default_frame_options;
impl_default_frame_options!(FrameOptionsD3D11);
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]

View file

@ -1,40 +0,0 @@
use crate::FilterChainD3D11;
use librashader_runtime::parameters::FilterChainParameters;
use std::collections::hash_map::Iter;
impl FilterChainParameters for FilterChainD3D11 {
fn get_enabled_pass_count(&self) -> usize {
self.common.config.passes_enabled
}
fn set_enabled_pass_count(&mut self, count: usize) {
self.common.config.passes_enabled = count
}
fn enumerate_parameters(&self) -> Iter<String, f32> {
self.common.config.parameters.iter()
}
fn get_parameter(&self, parameter: &str) -> Option<f32> {
self.common
.config
.parameters
.get::<str>(parameter.as_ref())
.copied()
}
fn set_parameter(&mut self, parameter: &str, new_value: f32) -> Option<f32> {
if let Some(value) = self
.common
.config
.parameters
.get_mut::<str>(parameter.as_ref())
{
let old = *value;
*value = new_value;
Some(old)
} else {
None
}
}
}

View file

@ -1,12 +1,12 @@
use crate::error::{assume_d3d11_init, Result};
use librashader_common::map::FastHashMap;
use librashader_common::{FilterMode, WrapMode};
use rustc_hash::FxHashMap;
use windows::Win32::Graphics::Direct3D11::{
ID3D11Device, ID3D11SamplerState, D3D11_COMPARISON_NEVER, D3D11_FLOAT32_MAX,
D3D11_SAMPLER_DESC, D3D11_TEXTURE_ADDRESS_MODE,
};
pub struct SamplerSet {
samplers: FxHashMap<(WrapMode, FilterMode), ID3D11SamplerState>,
samplers: FastHashMap<(WrapMode, FilterMode), ID3D11SamplerState>,
}
impl SamplerSet {
@ -18,7 +18,7 @@ impl SamplerSet {
}
pub fn new(device: &ID3D11Device) -> Result<SamplerSet> {
let mut samplers = FxHashMap::default();
let mut samplers = FastHashMap::default();
let wrap_modes = &[
WrapMode::ClampToBorder,
WrapMode::ClampToEdge,

View file

@ -1,19 +1,7 @@
use librashader_common::{FilterMode, Size, WrapMode};
use librashader_runtime::image::Image;
use librashader_runtime::scaling::MipmapSize;
use windows::Win32::Graphics::Direct3D::D3D_SRV_DIMENSION_TEXTURE2D;
use windows::Win32::Graphics::Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11RenderTargetView, ID3D11ShaderResourceView,
ID3D11Texture2D, D3D11_BIND_FLAG, D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE,
D3D11_BOX, D3D11_CPU_ACCESS_FLAG, D3D11_CPU_ACCESS_WRITE, D3D11_RESOURCE_MISC_FLAG,
D3D11_RESOURCE_MISC_GENERATE_MIPS, D3D11_SHADER_RESOURCE_VIEW_DESC,
D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_SUBRESOURCE_DATA, D3D11_TEX2D_SRV,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DYNAMIC, D3D11_USAGE_STAGING,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_SAMPLE_DESC;
use crate::error::{assume_d3d11_init, Result};
use crate::error::Result;
use crate::framebuffer::OwnedImage;
use librashader_common::{FilterMode, Size, WrapMode};
use windows::Win32::Graphics::Direct3D11::{ID3D11RenderTargetView, ID3D11ShaderResourceView};
/// An image view for use as a shader resource.
///
@ -66,140 +54,3 @@ impl AsRef<InputTexture> for InputTexture {
self
}
}
#[derive(Debug, Clone)]
pub(crate) struct LutTexture {
// The handle to the Texture2D must be kept alive.
#[allow(dead_code)]
pub handle: ID3D11Texture2D,
#[allow(dead_code)]
pub desc: D3D11_TEXTURE2D_DESC,
pub image: InputTexture,
}
impl AsRef<InputTexture> for LutTexture {
fn as_ref(&self) -> &InputTexture {
&self.image
}
}
impl LutTexture {
pub fn new(
device: &ID3D11Device,
context: &ID3D11DeviceContext,
source: &Image,
desc: D3D11_TEXTURE2D_DESC,
filter: FilterMode,
wrap_mode: WrapMode,
) -> Result<LutTexture> {
let mut desc = D3D11_TEXTURE2D_DESC {
Width: source.size.width,
Height: source.size.height,
// todo: set this to 0
MipLevels: if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS).0 != 0 {
0
} else {
1
},
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: if desc.Usage == D3D11_USAGE_DYNAMIC {
D3D11_CPU_ACCESS_WRITE
} else {
D3D11_CPU_ACCESS_FLAG(0)
},
..desc
};
desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE;
// determine number of mipmaps required
if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS).0 != 0 {
desc.BindFlags |= D3D11_BIND_RENDER_TARGET;
desc.MipLevels = source.size.calculate_miplevels();
}
// Don't need to determine format support because LUTs are always DXGI_FORMAT_R8G8B8A8_UNORM
// since we load them with the Image module.
unsafe {
let mut handle = None;
device.CreateTexture2D(&desc, None, Some(&mut handle))?;
assume_d3d11_init!(handle, "CreateTexture2D");
// need a staging texture to defer mipmap generation
let mut staging = None;
device.CreateTexture2D(
&D3D11_TEXTURE2D_DESC {
MipLevels: 1,
BindFlags: D3D11_BIND_FLAG(0),
MiscFlags: D3D11_RESOURCE_MISC_FLAG(0),
Usage: D3D11_USAGE_STAGING,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE,
..desc
},
Some(&D3D11_SUBRESOURCE_DATA {
pSysMem: source.bytes.as_ptr().cast(),
SysMemPitch: source.pitch as u32,
SysMemSlicePitch: 0,
}),
Some(&mut staging),
)?;
assume_d3d11_init!(staging, "CreateTexture2D");
context.CopySubresourceRegion(
&handle,
0,
0,
0,
0,
&staging,
0,
Some(&D3D11_BOX {
left: 0,
top: 0,
front: 0,
right: source.size.width,
bottom: source.size.height,
back: 1,
}),
);
let mut srv = None;
device.CreateShaderResourceView(
&handle,
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),
)?;
assume_d3d11_init!(srv, "CreateShaderResourceView");
if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS).0 != 0 {
context.GenerateMips(&srv)
}
Ok(LutTexture {
handle,
desc,
image: InputTexture {
view: D3D11InputView {
handle: srv,
size: source.size,
},
filter,
wrap_mode,
},
})
}
}
}

Some files were not shown because too many files have changed in this diff Show more