Compare commits

..

4 commits

228 changed files with 8051 additions and 14125 deletions

View file

@ -38,24 +38,10 @@ jobs:
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4.4.0
uses: actions/upload-artifact@v3.1.2
with:
name: ${{ format('librashader-{0}-{1}-{2}', matrix.output, github.sha, matrix.profile) }}
path: ${{ format('target/{0}/librashader.*', matrix.profile) }}
- name: Install Ubuntu librashader CLI build dependencies
if: matrix.profile == 'release' && matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update || true
sudo apt-get -y install xorg-dev
- name: Build librashader CLI
if: matrix.profile == 'release'
run: cargo build -p librashader-cli --release
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-{0}-{1}', matrix.output, github.sha) }}
path: ${{ format('target/{0}/librashader-cli*', matrix.profile) }}
build-ubuntu-arm64:
strategy:
matrix:
@ -79,23 +65,14 @@ jobs:
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
sudo apt-get update || true
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu xorg-dev libx11-dev:arm64 libxrandr-dev:arm64
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@v4.4.0
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) }}
- name: Build librashader CLI
if: matrix.profile == 'release'
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo build -p librashader-cli --release --target aarch64-unknown-linux-gnu
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-aarch64-ubuntu-{0}', github.sha) }}
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader-cli', matrix.profile) }}
build-windows-arm64:
strategy:
matrix:
@ -109,43 +86,13 @@ jobs:
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
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@v4.4.0
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) }}
- name: Build librashader CLI
if: matrix.profile == 'release'
run: cargo build -p librashader-cli --release --target aarch64-pc-windows-msvc
- name: Upload librashader-cli
uses: actions/upload-artifact@v4.4.0
if: matrix.profile == 'release'
with:
name: ${{ format('librashader-cli-aarch64-pc-windows-msvc-{0}', github.sha) }}
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader-cli.exe', matrix.profile) }}
build-windows-7:
strategy:
matrix:
profile: ['release', 'optimized']
fail-fast: false
runs-on: windows-latest
name: x86_64-win7-windows
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rust-src
- name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target x86_64-win7-windows-msvc -- -Zbuild-std
- name: Upload build artifacts
uses: actions/upload-artifact@v4.4.0
with:
name: ${{ format('librashader-x86_64-win7-windows-{0}-{1}', github.sha, matrix.profile) }}
path: ${{ format('target/x86_64-win7-windows-msvc/{0}/librashader.*', matrix.profile) }}

61
.github/workflows/package-obs.yml vendored Normal file
View file

@ -0,0 +1,61 @@
name: build Linux packages with Open Build Service
on:
pull_request_target:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
approve-obs-build:
name: "approval"
runs-on: ubuntu-latest
steps:
- name: Approve
run: echo OBS build CI test runs need to be approved by a maintainer.
build-obs-binary:
environment:
name: obs-build-env
strategy:
matrix:
include:
- repo: Fedora_40
spec: librashader.spec
can_fail: true
name: Fedora 40 (.rpm)
- repo: xUbuntu_24.04
spec: librashader.spec
can_fail: true
name: Ubuntu 24.04 (.deb)
runs-on: ubuntu-latest
needs: [approve-obs-build]
continue-on-error: ${{ matrix.can_fail }}
name: ${{ matrix.name }}
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
run: |
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
mkdir -p ~/.config/osc
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
- name: Checkout Open Build Service repository
run: |
osc co home:chyyran:librashader/librashader
- name: Copy spec from repository
run: |
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
- name: Vendor sources for Open Build Service
run: |
cd home:chyyran:librashader/librashader/
osc service mr
- name: Build package
run: |
cd home:chyyran:librashader/librashader/
osc build --no-verify --trust-all-projects ${{ matrix.repo }} x86_64 ${{ matrix.spec }}

83
.github/workflows/pr-full-test.yml vendored Normal file
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

@ -13,7 +13,7 @@ jobs:
container: fedora:39
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}

1
.gitignore vendored
View file

@ -13,4 +13,3 @@ librashader_runtime_*.exe
/test/capi-tests/librashader-capi-tests/**/*.so
/test/capi-tests/librashader-capi-tests/**/*.out
/test/Mega_Bezel_Packs/Duimon-Mega-Bezel/
.DS_Store

View file

@ -3,13 +3,38 @@
The following shaders are known to be broken due to various issues.
This list is updated as of [slang-shaders@`33876b3`](https://github.com/libretro/slang-shaders/commit/33876b3578baac8302b6189ac7acbb052013919e)
This list is updated as of [slang-shaders@`356678e`](https://github.com/libretro/slang-shaders/commit/356678ec53ca940a53fa509eff0b65bb63a403bb)
## Broken due to parsing errors
librashader's preset parser is somewhat stricter than RetroArch in what it accepts. All shaders and textures in a preset must
resolve to a fully canonical path to properly parse. The following shaders have broken paths.
* No known broken presets.
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: Missing `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/shaders/linearize.slang`
* `crt/crt-maximus-royale-fast-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/crt-maximus-royale-half-res-mode.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/crt-maximus-royale.slangp`: Missing `crt/shaders/crt-maximus-royale/FrameTextures/16_9/TV_decor_1.png`
* `crt/mame_hlsl.slangp`: Missing `crt/shaders/mame_hlsl/shaders/lut.slang`
* `denoisers/fast-bilateral-super-2xbr-3d-3p.slangp`: Missing `xbr/shaders/super-xbr/super-2xbr-3d-pass0.slang`
* `presets/tvout/tvout+ntsc-256px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout/tvout+ntsc-256px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout/tvout+ntsc-2phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout/tvout+ntsc-2phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout/tvout+ntsc-320px-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout/tvout+ntsc-320px-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout/tvout+ntsc-3phase-composite.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout/tvout+ntsc-3phase-svideo.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout/tvout+ntsc-nes.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-256px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-256px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-2phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-2phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-320px-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-320px-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-2phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-3phase-composite+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-3phase-svideo+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-svideo-3phase.slang`
* `presets/tvout+interlacing/tvout+ntsc-nes+interlacing.slangp`: Missing `ntsc/shaders/ntsc-pass1-composite-3phase.slang`
* `scalefx/shaders/old/scalefx-9x.slangp`: Missing `../stock.slang`
* `scalefx/shaders/old/scalefx.slangp`: Missing `../stock.slang`
librashader's parser is fuzzed with slang-shaders and will accept invalid keys like `mipmap1` or `filter_texture = linear`
to account for shader presets that use these invalid constructs. No known shader presets fail to parse due to syntax errors
@ -19,4 +44,5 @@ that haven't already been accounted for.
The preprocessor resolves `#include` pragmas in each `.slang` shader and recursively flattens files into a single compute unit.
* `bezel/Mega_Bezel/shaders/hyllian/crt-super-xbr/crt-super-xbr.slangp`: GLSL error: `ERROR: common-functions-bezel.inc:63: 'HSM_GetInverseScaledCoord' : no matching overloaded function found `
* `misc/shaders/glass.slang`: Missing `misc/include/img/param_floats.h`.
* Looks like this was moved too deep, it references `../include/img/param_floats.h`, but the shader lives in the `misc/shaders/` folder.

1007
CLI.md

File diff suppressed because it is too large Load diff

1357
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,41 +8,17 @@ members = [
"librashader-runtime",
"librashader-runtime-d3d11",
"librashader-runtime-d3d12",
"librashader-runtime-d3d9",
"librashader-runtime-gl",
"librashader-runtime-vk",
"librashader-runtime-mtl",
"librashader-runtime-wgpu",
"librashader-cache",
"librashader-capi",
"librashader-build-script",
"librashader-cli", "librashader-pack"]
"librashader-build-script", "librashader-runtime-d3d9"]
resolver = "2"
[workspace.dependencies]
windows = "0.58.0"
ash = "0.38"
spirv-cross2 = { version = "0.4", default-features = false }
objc2-metal = { version = "0.2" }
objc2 = { version = "0.5.0" }
glow = { version = "0.14.1" }
glfw = { version = "0.58.0"}
wgpu = { version = "22", default-features = false }
wgpu-types = { version = "22" }
clap = { version = "=4.3.0", features = ["derive"] }
rayon = { version = "1.10.0"}
[workspace.dependencies.image]
version = "0.25.2"
features = [
"gif", "jpeg", "png",
"tga", "pnm", "tiff",
"webp", "bmp", "dds",
]
default-features = false
[workspace.metadata.release]

View file

@ -1,120 +0,0 @@
# Migrating to librashader ABI 2
librashader version 0.5.0 introduces an ABI change to the C ABI that is incompatible with prior versions.
If a version of librashader prior to 0.5.0 was linked via `libashader_ld.h`, it should _correctly_ fail to load when attempting
to load an instance of librashader 0.5.0 or later.
This document is only relevant to consumers of the librashader C ABI. Rust users should consult [docs.rs/librashader](https://docs.rs/librashader/latest/librashader/).
## `LIBRASHADER_CURRENT_ABI` change
`LIBRASHADER_CURRENT_ABI` has changed from `1` to `2`. There is no change to `LIBRASHADER_CURRENT_API`, as new features
have not been added. Option structs should continue to pass `LIBRASHADER_CURRENT_API` for the latest available feature set.
## `SONAME` change
On Linux, the canonical `SONAME` of `librashader.so` has changed from `librashader.so.1` to `librashader.so.2`. This will
affect consumers that link via the linkage table with `-lrashader` or equivalent rather than through `libashader_ld.h`.
## `libra_preset` changes
* The `_internal_alloc` field from `libra_preset_param_list_t` has been removed. This change was made to reduce the potential
surface area for undefined behaviour to occur. If librashader was used correctly, there is no code change required.
## `LIBRA_RUNTIME_VULKAN` changes
The following changes are applicable if `LIBRA_RUNTIME_VULKAN` is defined.
* The `libra_output_image_vk_t` and `libra_source_image_vk_t` structs have been replaced with `libra_image_vk_t`.
* `libra_image_vk_t`has the same layout and semantics for `libra_source_image_vk_t`.
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_vk_t`. The `handle` and `format`
fields retain the same semantics as `libra_output_image_vk_t`.
* A field `queue` of type `VkQueue` was added to `libra_device_vk_t`. This field can be `NULL`. If not `NULL`, it is the handle to the `VkQueue` graphics queue
to use for the filter chain. If `NULL`, a suitable queue will be chosen.
* The `image` and `out` parameters of `libra_vk_filter_chain_frame` has changed from `libra_source_image_vk_t` and `libra_output_image_vk_t`, to `libra_image_vk_t`.
* In `libra_vk_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_vk_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_vk_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_OPENGL` changes
The following changes are applicable if `LIBRA_RUNTIME_OPENGL` is defined.
* The `libra_gl_init_context` function has been removed.
* The function `libra_gl_filter_chain_create` now accepts a `loader` parameter of type `libra_gl_loader_t`. This will be the OpenGL loader used to create the filter chain, previously passed to `libra_gl_init_context`
The filter chain will be created against the current OpenGL context.
* The `libra_output_framebuffer_gl_t` and `libra_source_image_gl_t` structs have been replaced with `libra_image_gl_t`.
* `libra_image_gl_t`has the same layout and semantics for `libra_source_image_gl_t`.
* When passed as `out`, you should now pass what was previously `.width` and `.height` of `libra_viewport_t` to the same fields in `libra_image_gl_t`. The `handle` and `format`
fields retain the same semantics as `libra_output_image_gl_t`.
* The `fbo` field previously in `libra_output_image_gl_t` is no longer necessary. librashader will now internally manage a framebuffer object to write to the provided texture.
* In `libra_gl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_gl_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_gl_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D11` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D11` is defined.
* The `image` parameter of `libra_d3d11_filter_chain_frame` has changed from `libra_source_image_d3d11_t` to `ID3D11ShaderResourceView *`.
* You should now pass what was previously the `.handle` field of `libra_source_image_d3d11_t` field directly as `image` to `libra_d3d11_filter_chain_frame`.
* The `libra_source_image_d3d11_t` struct has been removed.
* In `libra_d3d11_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d11_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d11_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D12` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D12` is defined.
* The lifetime of resources will not be extended past the call to the `libra_d3d12_filter_chain_frame` function. In other words, the refcount for any resources passed into the function
will no longer be changed, and it is explicitly the responsibility of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is submitted.
* The fields `format`, `width`, and `height` have been removed from `libra_source_image_d3d12_t`.
* The field `descriptor` now comes before the field `resource` in the layout of `libra_source_image_d3d12_t`.
* The fields `width` and `height` have been added to `libra_output_image_d3d12_t`.
* You should now pass what was previously `.width` and `.height` of `libra_viewport_t` to these new fields in `libra_output_image_d3d12_t`.
* The `image` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_source_image_d3d12_t` to `libra_image_d3d12_t`.
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_SOURCE_IMAGE`, and `.handle.source` should be the `libra_source_image_d3d12_t`struct.
* The `out` parameter of `libra_d3d12_filter_chain_frame` has changed from `libra_output_image_d3d11_t` to `libra_image_d3d12_t`.
* To maintain the previous behaviour, `.image_type` of the `libra_image_d3d12_t` should be set to `LIBRA_D3D12_IMAGE_TYPE_OUTPUT_IMAGE`, and `.handle.output` should be the `libra_output_image_d3d12_t`struct.
* Any `libra_image_d3d12_t` can now optionally pass only the `ID3D12Resource *` for the texture by setting `.image_type` to `LIBRA_D3D12_IMAGE_TYPE_RESOURCE` and setting `.handle.resource` to the resource pointer.
* If using `LIBRA_D3D12_IMAGE_TYPE_RESOURCE`, shader resource view and render target view descriptors for the input and output images will be internally allocated by the filter chain. This may result in marginally worse performance.
* In `libra_d3d12_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d12_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d12_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_D3D9` changes
The following changes are applicable if `LIBRA_RUNTIME_D3D9` is defined.
* In `libra_d3d9_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_d3d9_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_d3d9_filter_chain_set_param` from any thread [^1].
## `LIBRA_RUNTIME_METAL` changes
The following changes are applicable if `LIBRA_RUNTIME_METAL` is defined.
* In `libra_mtl_filter_chain_frame`, the position of the `viewport` parameter has moved to after the `out` parameter, and its type has changed from `libra_viewport_t` to `libra_viewport_t *`, which is allowed to be `NULL`.
See [`libra_viewport_t` changes](#libra_viewport_t-changes) for more details.
* The `chain` parameter of `libra_mtl_filter_chain_get_param` has been made `const`.
* It is always thread safe to call `libra_mtl_filter_chain_set_param` from any thread [^1].
## `libra_viewport_t` changes
All `viewport` parameters for `libra_*_filter_chain_frame` now take a *pointer* to a `libra_viewport_t` struct. In ABI 1, the semantics of `libra_viewport_t`
was unspecified (but not undefined behaviour) if `width` and `height` did not match the width and height of the output texture.
This caused confusion as to what the actual purpose of the `width` and `height` fields were. The behaviour differed across runtimes:
In some runtimes, it specified the size of the output texture, in others they were used to set the clipping rect for the render target.
In ABI 2, the semantics of `viewport` as a parameter in `libra_*_filter_chain_frame` are as specified.
* If `viewport` is `NULL`, then this will be the same as the **specified** behaviour in ABI 1&mdash;that is, as if the behaviour where `width` and `height` of `libra_viewport_t` were **equal** to that of the output texture.
* In other words, if `viewport` is `NULL`, then it will set the render viewport to be equal to the width and height of the output texture.
* If `viewport` is not `NULL`, then the following occurs.
* The width and height of the viewport rectangle for the output quad will be set to `viewport.width` and `viewport.height` respectively.
* The origin point of the viewport rectangle will be set to (`x`, `y`), **in the native coordinate system of the runtime**.
* This behaviour may change over an _API_ version bump. **API version 1 will always retain the behaviour specified here.**
* The scissor rectangle will be set to the same size and origin as the viewport rectangle.
[^1]: This has been the case since librashader 0.4.0 on ABI 1. ABI 2 codifies this guarantee: any loosening in the thread-safety guarantees of `libra_*_filter_chain_set_param` in the future may only change across an _API_ version bump. **API version 1 will always retain the behaviour specified here.**

119
README.md
View file

@ -1,6 +1,6 @@
# librashader
![Mega Bezel SMOOTH-ADV](https://raw.githubusercontent.com/SnowflakePowered/librashader/master/shader_triangle.png)
![Mega Bezel SMOOTH-ADV](shader_triangle.png)
<small>*Mega Bezel SMOOTH-ADV on DirectX 11*</small>
@ -8,7 +8,7 @@ librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader) [![Docs](https://docs.rs/librashader/badge.svg)](https://docs.rs/librashader) [![build result](https://build.opensuse.org/projects/home:chyyran:librashader/packages/librashader/badge.svg)](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader)
![License](https://img.shields.io/crates/l/librashader)
![Stable rust](https://img.shields.io/badge/rust-1.78-blue.svg) ![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
![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.
@ -18,26 +18,25 @@ Windows and macOS users can grab the latest binaries from [GitHub Releases](http
librashader supports all modern graphics runtimes, including wgpu, Vulkan, OpenGL 3.3+ and 4.6 (with DSA),
Direct3D 11, Direct3D 12, and Metal.
librashader does not support legacy render APIs such as older versions of OpenGL or Direct3D, except for limited
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 9 | 🆗️ |`d3d9` |
| Direct3D 11 | ✅ | `d3d11` |
| Direct3D 12 | ✅ | `d3d12` |
| Metal | ✅ | `metal` |
| wgpu | 🆗 | `wgpu` |
|-------------|------------|---------------------------|
| OpenGL 3.3+ | ✅ | `gl` |
| OpenGL 4.6 | ✅ | `gl` |
| Vulkan | ✅ | `vk` |
| Direct3D 9 | ⚠️ | `d3d9` |
| Direct3D 11 | ✅ | `d3d11` |
| Direct3D 12 | ✅ | `d3d12` |
| Metal | ✅ | `metal` |
| wgpu | 🆗 | `wgpu` |
✅ Full Support &mdash; 🆗 Secondary Support
✅ Full Support &mdash; 🆗 Secondary Support &mdash; ⚠️ Experimental Support
Shader compatibility is not guaranteed on render APIs with secondary support.
wgpu has restrictions on shaders that can not be converted to WGSL, such as those that use `inverse`. Direct3D 9 does not support
shaders that need Direct3D 10+ only features, or shaders that can not be compiled to [Shader Model 3.0](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/shader-model-3).
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
@ -48,6 +47,7 @@ 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`, `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` / `librashader.dylib`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
@ -74,8 +74,6 @@ is not available to OpenGL.
The Metal runtime is **not thread safe**. However you can still defer submission of GPU resource initialization through the
`filter_chain_create_deferred` function.
The Direct3D 9 API is not thread safe, unless `D3DCREATE_MULTITHREADED` is enabled at device creation.
### Quad vertices and rotations
All runtimes 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.
@ -92,39 +90,7 @@ 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.
### Writing a librashader Runtime
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
the runtime.
## Command-line interface
librashader provides a command-line interface to reflect and debug 'slang' shaders and presets.
```
Usage: librashader-cli <COMMAND>
Commands:
render Render a shader preset against an image
compare Compare two runtimes and get a similarity score between the two runtimes rendering the same frame
parse Parse a preset and get a JSON representation of the data
pack Create a serialized preset pack from a shader preset
preprocess Get the raw GLSL output of a preprocessed shader
transpile Transpile a shader in a given preset to the given format
reflect Reflect the shader relative to a preset, giving information about semantics used in a slang shader
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
For more information, see [`CLI.md`](https://github.com/SnowflakePowered/librashader/blob/master/CLI.md).
## Building
### Building
For Rust projects, simply add the crate to your `Cargo.toml`.
@ -144,28 +110,14 @@ This will output a `librashader.dll` or `librashader.so` in the target folder. P
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
the relevant runtime graphics API.
### Writing a librashader Runtime
### Building against stable Rust
While librashader is intended to be used with nightly Rust until [required features](https://github.com/SnowflakePowered/librashader/issues/55) are stabilized, it supports being
built with stable Rust with the `stable` feature.
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
crate for helpers and shared logic used across all librashader runtime implementations. Using these helpers and traits will
ensure that your runtime has consistent behaviour for uniform and texture semantics bindings with the existing librashader runtimes.
```toml
librashader = { features = ["stable"] }
```
If building the C API, the `--stable` flag in the build script will enable the `stable` feature.
```
cargo +stable run -p librashader-build-script -- --profile optimized --stable
```
There are some caveats when building against stable Rust, such that building librashader against nightly Rust is still highly encouraged.
* C headers will not be regenerated when building with the `stable` feature.
* There is a minor performance hit in initial shader compilation when building against stable Rust. This is due to boxed trait objects being used instead of `impl Trait`.
* A higher MSRV is required when building against stable Rust.
When the `trait_alias_impl_trait` feature is stabilized, the `stable` feature will be removed.
These types should not be exposed to the end user in the runtime's public API, and should be kept internal to the implementation of
the runtime.
## Examples
@ -175,7 +127,6 @@ The following Rust examples show how to use each librashader runtime.
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d11/tests/triangle.rs)
* [Direct3D 12](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d12/tests/triangle.rs)
* [wgpu](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-wgpu/tests/hello_triangle.rs)
* [Direct3D 9](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d9/tests/triangle.rs)
Some basic examples on using the C API are also provided.
@ -200,9 +151,6 @@ Please report an issue if you run into a shader that works in RetroArch, but not
`mipmap_input0 = "true"`.
* The preset parser is a substantially stricter implementation that the one in RetroArch. Not all shader presets may be
compatible. If you find this is the case, please file an issue so a workaround can be added.
* Shaders are [pre-linked at the SPIR-V level](https://github.com/SnowflakePowered/librashader/blob/master/librashader-reflect/src/front/spirv_passes/link_input_outputs.rs) before being
passed to the driver. Unused inputs in the fragment shader are removed, and the corresponding input in the vertex shader
is downgraded to a global variable.
### Runtime specific differences
* OpenGL
* Copying of in-flight framebuffer contents to history is done via `glBlitFramebuffer` rather than drawing a quad into an intermediate FBO.
@ -236,7 +184,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-2-yellowgreen)
![C ABI](https://img.shields.io/badge/ABI%20version-1-yellowgreen)
![C API](https://img.shields.io/badge/API%20version-1-blue)
@ -269,23 +217,14 @@ 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`.
The ABI version was bumped from `1` to `2` with librashader `0.5.0`. See [`MIGRATION-ABI2.md`](https://github.com/SnowflakePowered/librashader/blob/master/MIGRATION-ABI2.md)
for migration instructions.
### MSRV Policy
Building against stable Rust requires the following MSRV.
While librashader requires nightly Rust, the following MSRV policy is enforced for unstable library features.
* All platforms: **1.78**
* Windows and macOS: **latest** nightly
* Linux: **1.76**
When building against nightly Rust, the following MSRV policy is enforced for unstable library features.
* All platforms: **1.78**
A CI job runs weekly to ensure librashader continues to build on nightly.
Note that the MSRV is only intended to ease distribution on Linux when building against nightly Rust or with `RUSTC_BOOTSTRAP=1`, and is allowed to change any time.
It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
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,

View file

@ -33,9 +33,15 @@ libra_gl_filter_chain_t load_gl_filter_chain(libra_gl_loader_t opengl, const cha
std::cout << "Could not load preset\n";
return NULL;
}
// OpenGL runtime needs to be initialized.
if (librashader.gl_init_context(opengl) != NULL) {
std::cout << "Could not initialize OpenGL context\n";
return NULL;
}
libra_gl_filter_chain_t chain;
if (librashader.gl_filter_chain_create(&preset, opengl, NULL, &chain) {
if (librashader.gl_filter_chain_create(&preset, NULL, &chain) {
std::cout << "Could not create OpenGL filter chain\n";
}
return chain;

View file

@ -51,52 +51,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The type of image passed to the image.
enum LIBRA_D3D12_IMAGE_TYPE
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a pointer to a `ID3D12Resource`.
LIBRA_D3D12_IMAGE_TYPE_RESOURCE = 0,
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a `libra_source_image_d3d12_t`
LIBRA_D3D12_IMAGE_TYPE_SOURCE_IMAGE = 1,
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// The image handle is a `libra_output_image_d3d12_t`
LIBRA_D3D12_IMAGE_TYPE_OUTPUT_IMAGE = 2,
#endif
};
#ifndef __cplusplus
typedef int32_t LIBRA_D3D12_IMAGE_TYPE;
#endif // __cplusplus
#endif
/// Error codes for librashader error types.
enum LIBRA_ERRNO
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
/// Error code for an unknown error.
LIBRA_ERRNO_UNKNOWN_ERROR = 0,
/// Error code for an invalid parameter.
LIBRA_ERRNO_INVALID_PARAMETER = 1,
/// Error code for an invalid (non-UTF8) string.
LIBRA_ERRNO_INVALID_STRING = 2,
/// Error code for a preset parser error.
LIBRA_ERRNO_PRESET_ERROR = 3,
/// Error code for a preprocessor error.
LIBRA_ERRNO_PREPROCESS_ERROR = 4,
/// Error code for a shader parameter error.
LIBRA_ERRNO_SHADER_PARAMETER_ERROR = 5,
/// Error code for a reflection error.
LIBRA_ERRNO_REFLECT_ERROR = 6,
/// Error code for a runtime error.
LIBRA_ERRNO_RUNTIME_ERROR = 7,
};
#ifndef __cplusplus
@ -109,35 +76,24 @@ enum LIBRA_PRESET_CTX_ORIENTATION
: uint32_t
#endif // __cplusplus
{
/// Context parameter for vertical orientation.
LIBRA_PRESET_CTX_ORIENTATION_VERTICAL = 0,
/// Context parameter for horizontal orientation.
LIBRA_PRESET_CTX_ORIENTATION_HORIZONTAL,
};
#ifndef __cplusplus
typedef uint32_t LIBRA_PRESET_CTX_ORIENTATION;
#endif // __cplusplus
/// An enum representing graphics runtimes (video drivers) for use in preset contexts.
enum LIBRA_PRESET_CTX_RUNTIME
#ifdef __cplusplus
: uint32_t
#endif // __cplusplus
{
/// No runtime.
LIBRA_PRESET_CTX_RUNTIME_NONE = 0,
/// OpenGL 3.3+
LIBRA_PRESET_CTX_RUNTIME_GL_CORE,
/// Vulkan
LIBRA_PRESET_CTX_RUNTIME_VULKAN,
/// Direct3D 11
LIBRA_PRESET_CTX_RUNTIME_D3D11,
/// Direct3D 12
LIBRA_PRESET_CTX_RUNTIME_D3D12,
/// Metal
LIBRA_PRESET_CTX_RUNTIME_METAL,
/// Direct3D 9
LIBRA_PRESET_CTX_RUNTIME_D3D9_HLSL,
};
#ifndef __cplusplus
typedef uint32_t LIBRA_PRESET_CTX_RUNTIME;
@ -199,10 +155,11 @@ typedef struct libra_preset_param_t {
typedef struct libra_preset_param_list_t {
/// A pointer to the parameter
const struct libra_preset_param_t *parameters;
/// The number of parameters in the list. This field
/// is readonly, and changing it will lead to undefined
/// behaviour on free.
/// The number of parameters in the list.
uint64_t length;
/// For internal use only.
/// Changing this causes immediate undefined behaviour on freeing this parameter list.
uint64_t _internal_alloc;
} libra_preset_param_list_t;
#if defined(LIBRA_RUNTIME_OPENGL)
@ -238,33 +195,43 @@ typedef struct _filter_chain_gl *libra_gl_filter_chain_t;
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// OpenGL parameters for an image.
typedef struct libra_image_gl_t {
/// A texture GLuint to the texture.
/// OpenGL parameters for the source image.
typedef struct libra_source_image_gl_t {
/// A texture GLuint to the source image.
uint32_t handle;
/// The format of the texture.
/// The format of the source image.
uint32_t format;
/// The width of the texture.
/// The width of the source image.
uint32_t width;
/// The height of the texture.
/// The height of the source image.
uint32_t height;
} libra_image_gl_t;
} libra_source_image_gl_t;
#endif
/// Defines the output origin for a rendered frame.
/// Defines the output viewport for a rendered frame.
typedef struct libra_viewport_t {
/// The x offset in the viewport framebuffer to begin rendering from.
float x;
/// The y offset in the viewport framebuffer to begin rendering from.
float y;
/// The width extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by x.
/// The width of the viewport framebuffer.
uint32_t width;
/// The height extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by y.
/// The height of the viewport framebuffer.
uint32_t height;
} libra_viewport_t;
#if defined(LIBRA_RUNTIME_OPENGL)
/// OpenGL parameters for the output framebuffer.
typedef struct libra_output_framebuffer_gl_t {
/// A framebuffer GLuint to the output framebuffer.
uint32_t fbo;
/// A texture GLuint to the logical buffer of the output framebuffer.
uint32_t texture;
/// The format of the output framebuffer.
uint32_t format;
} libra_output_framebuffer_gl_t;
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Options for each OpenGL shader frame.
typedef struct frame_gl_opt_t {
@ -275,7 +242,7 @@ typedef struct frame_gl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -296,9 +263,6 @@ typedef struct libra_device_vk_t {
/// A raw `VkDevice` handle
/// for the device attached to the instance that will perform rendering.
VkDevice device;
/// The queue to use, if this is `NULL`, then
/// a suitable queue will be chosen. This must be a graphics queue.
VkQueue queue;
/// The entry loader for the Vulkan library.
PFN_vkGetInstanceProcAddr entry;
} libra_device_vk_t;
@ -329,17 +293,27 @@ typedef struct _filter_chain_vk *libra_vk_filter_chain_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
/// Vulkan parameters for an image.
typedef struct libra_image_vk_t {
/// A raw `VkImage` handle.
/// Vulkan parameters for the source image.
typedef struct libra_source_image_vk_t {
/// A raw `VkImage` handle to the source image.
VkImage handle;
/// The `VkFormat` of the `VkImage`.
/// The `VkFormat` of the source image.
VkFormat format;
/// The width of the `VkImage`.
/// The width of the source image.
uint32_t width;
/// The height of the `VkImage`.
/// The height of the source image.
uint32_t height;
} libra_image_vk_t;
} libra_source_image_vk_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
/// Vulkan parameters for the output image.
typedef struct libra_output_image_vk_t {
/// A raw `VkImage` handle to the output image.
VkImage handle;
/// The `VkFormat` of the output image.
VkFormat format;
} libra_output_image_vk_t;
#endif
#if defined(LIBRA_RUNTIME_VULKAN)
@ -352,7 +326,7 @@ typedef struct frame_vk_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -380,6 +354,18 @@ typedef struct filter_chain_d3d11_opt_t {
typedef struct _filter_chain_d3d11 *libra_d3d11_filter_chain_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Direct3D 11 parameters for the source image.
typedef struct libra_source_image_d3d11_t {
/// A shader resource view into the source image
ID3D11ShaderResourceView * handle;
/// The width of the source image.
uint32_t width;
/// The height of the source image.
uint32_t height;
} libra_source_image_d3d11_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Options for each Direct3D 11 shader frame.
typedef struct frame_d3d11_opt_t {
@ -390,7 +376,7 @@ typedef struct frame_d3d11_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -428,7 +414,7 @@ typedef struct frame_d3d9_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -462,10 +448,16 @@ typedef struct _filter_chain_d3d12 *libra_d3d12_filter_chain_t;
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Direct3D 12 parameters for the source image.
typedef struct libra_source_image_d3d12_t {
/// A CPU descriptor handle to a shader resource view of the image.
D3D12_CPU_DESCRIPTOR_HANDLE descriptor;
/// The resource containing the image.
ID3D12Resource * resource;
/// A CPU descriptor handle to a shader resource view of the image.
D3D12_CPU_DESCRIPTOR_HANDLE descriptor;
/// The format of the image.
DXGI_FORMAT format;
/// The width of the source image.
uint32_t width;
/// The height of the source image.
uint32_t height;
} libra_source_image_d3d12_t;
#endif
@ -476,38 +468,9 @@ typedef struct libra_output_image_d3d12_t {
D3D12_CPU_DESCRIPTOR_HANDLE descriptor;
/// The format of the image.
DXGI_FORMAT format;
/// The width of the output image.
uint32_t width;
/// The height of the output image.
uint32_t height;
} libra_output_image_d3d12_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// A handle to a Direct3D 12 image.
///
/// This must be either a pointer to a `ID3D12Resource`,
/// or a valid source or output image type.
typedef union libra_image_d3d12_handle_t {
/// A pointer to an `ID3D12Resource`, with descriptors managed by the filter chain.
ID3D12Resource * resource;
/// A source image with externally managed descriptors.
struct libra_source_image_d3d12_t source;
/// An output image with externally managed descriptors.
struct libra_output_image_d3d12_t output;
} libra_image_d3d12_handle_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Tagged union for a Direct3D 12 image
typedef struct libra_image_d3d12_t {
/// The type of the image.
LIBRA_D3D12_IMAGE_TYPE image_type;
/// The handle to the image.
union libra_image_d3d12_handle_t handle;
} libra_image_d3d12_t;
#endif
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Options for each Direct3D 12 shader frame.
typedef struct frame_d3d12_opt_t {
@ -518,7 +481,7 @@ typedef struct frame_d3d12_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -538,7 +501,6 @@ typedef struct filter_chain_mtl_opt_t {
#endif
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// A handle to a Vulkan filter chain.
typedef struct _filter_chain_mtl *libra_mtl_filter_chain_t;
#endif
@ -552,7 +514,7 @@ typedef struct frame_mtl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
int32_t frame_direction;
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
uint32_t rotation;
/// The total number of subframes ran. Default is 1.
uint32_t total_subframes;
@ -561,7 +523,6 @@ typedef struct frame_mtl_opt_t {
} frame_mtl_opt_t;
#endif
/// ABI version type alias.
typedef size_t LIBRASHADER_ABI_VERSION;
/// Function pointer definition for libra_abi_version
@ -586,7 +547,7 @@ typedef libra_error_t (*PFN_libra_preset_set_param)(libra_shader_preset_t *prese
/// Function pointer definition for
///libra_preset_get_param
typedef libra_error_t (*PFN_libra_preset_get_param)(const libra_shader_preset_t *preset,
typedef libra_error_t (*PFN_libra_preset_get_param)(libra_shader_preset_t *preset,
const char *name,
float *value);
@ -596,7 +557,7 @@ typedef libra_error_t (*PFN_libra_preset_print)(libra_shader_preset_t *preset);
/// Function pointer definition for
///libra_preset_get_runtime_params
typedef libra_error_t (*PFN_libra_preset_get_runtime_params)(const libra_shader_preset_t *preset,
typedef libra_error_t (*PFN_libra_preset_get_runtime_params)(libra_shader_preset_t *preset,
struct libra_preset_param_list_t *out);
/// Function pointer definition for
@ -683,11 +644,16 @@ typedef int32_t (*PFN_libra_error_write)(libra_error_t error, char **out);
/// Function pointer definition for libra_error_free_string
typedef int32_t (*PFN_libra_error_free_string)(char **out);
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_init_context
typedef libra_error_t (*PFN_libra_gl_init_context)(libra_gl_loader_t loader);
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_create
typedef libra_error_t (*PFN_libra_gl_filter_chain_create)(libra_shader_preset_t *preset,
libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out);
#endif
@ -697,9 +663,9 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_create)(libra_shader_preset_t
///libra_gl_filter_chain_frame
typedef libra_error_t (*PFN_libra_gl_filter_chain_frame)(libra_gl_filter_chain_t *chain,
size_t frame_count,
struct libra_image_gl_t image,
struct libra_image_gl_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_gl_t image,
struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out,
const float *mvp,
const struct frame_gl_opt_t *opt);
#endif
@ -715,7 +681,7 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_set_param)(libra_gl_filter_cha
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_get_param
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_param)(const libra_gl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_param)(libra_gl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -730,7 +696,7 @@ typedef libra_error_t (*PFN_libra_gl_filter_chain_set_active_pass_count)(libra_g
#if defined(LIBRA_RUNTIME_OPENGL)
/// Function pointer definition for
///libra_gl_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_active_pass_count)(const libra_gl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_gl_filter_chain_get_active_pass_count)(libra_gl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -765,9 +731,9 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_create_deferred)(libra_shader_
typedef libra_error_t (*PFN_libra_vk_filter_chain_frame)(libra_vk_filter_chain_t *chain,
VkCommandBuffer command_buffer,
size_t frame_count,
struct libra_image_vk_t image,
struct libra_image_vk_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_vk_t image,
struct libra_viewport_t viewport,
struct libra_output_image_vk_t out,
const float *mvp,
const struct frame_vk_opt_t *opt);
#endif
@ -783,7 +749,7 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_set_param)(libra_vk_filter_cha
#if defined(LIBRA_RUNTIME_VULKAN)
/// Function pointer definition for
///libra_vk_filter_chain_get_param
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_param)(const libra_vk_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_param)(libra_vk_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -798,7 +764,7 @@ typedef libra_error_t (*PFN_libra_vk_filter_chain_set_active_pass_count)(libra_v
#if defined(LIBRA_RUNTIME_VULKAN)
/// Function pointer definition for
///libra_vk_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_active_pass_count)(const libra_vk_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_vk_filter_chain_get_active_pass_count)(libra_vk_filter_chain_t *chain,
uint32_t *out);
#endif
@ -833,9 +799,9 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_create_deferred)(libra_shad
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_frame)(libra_d3d11_filter_chain_t *chain,
ID3D11DeviceContext * device_context,
size_t frame_count,
ID3D11ShaderResourceView * image,
struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport,
ID3D11RenderTargetView * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d11_opt_t *options);
#endif
@ -851,7 +817,7 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_set_param)(libra_d3d11_filt
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Function pointer definition for
///libra_d3d11_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_param)(const libra_d3d11_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_param)(libra_d3d11_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -866,7 +832,7 @@ typedef libra_error_t (*PFN_libra_d3d11_filter_chain_set_active_pass_count)(libr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Function pointer definition for
///libra_d3d11_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_active_pass_count)(const libra_d3d11_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d11_filter_chain_get_active_pass_count)(libra_d3d11_filter_chain_t *chain,
uint32_t *out);
#endif
@ -891,8 +857,8 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_create)(libra_shader_preset_
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_frame)(libra_d3d9_filter_chain_t *chain,
size_t frame_count,
IDirect3DTexture9 * image,
struct libra_viewport_t viewport,
IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d9_opt_t *options);
#endif
@ -908,7 +874,7 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_set_param)(libra_d3d9_filter
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Function pointer definition for
///libra_d3d9_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_param)(const libra_d3d9_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_param)(libra_d3d9_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -923,7 +889,7 @@ typedef libra_error_t (*PFN_libra_d3d9_filter_chain_set_active_pass_count)(libra
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Function pointer definition for
///libra_d3d9_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_active_pass_count)(const libra_d3d9_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d9_filter_chain_get_active_pass_count)(libra_d3d9_filter_chain_t *chain,
uint32_t *out);
#endif
@ -958,9 +924,9 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_create_deferred)(libra_shad
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_frame)(libra_d3d12_filter_chain_t *chain,
ID3D12GraphicsCommandList * command_list,
size_t frame_count,
struct libra_image_d3d12_t image,
struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport,
struct libra_output_image_d3d12_t out,
const float *mvp,
const struct frame_d3d12_opt_t *options);
#endif
@ -976,7 +942,7 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_set_param)(libra_d3d12_filt
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Function pointer definition for
///libra_d3d12_filter_chain_get_param
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_param)(const libra_d3d12_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_param)(libra_d3d12_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -991,7 +957,7 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_set_active_pass_count)(libr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12))
/// Function pointer definition for
///libra_d3d12_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_active_pass_count)(const libra_d3d12_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_active_pass_count)(libra_d3d12_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1027,8 +993,8 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_frame)(libra_mtl_filter_chain
id<MTLCommandBuffer> command_buffer,
size_t frame_count,
id<MTLTexture> image,
struct libra_viewport_t viewport,
id<MTLTexture> output,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_mtl_opt_t *opt);
#endif
@ -1044,7 +1010,7 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_set_param)(libra_mtl_filter_c
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Function pointer definition for
///libra_mtl_filter_chain_get_param
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_param)(const libra_mtl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_param)(libra_mtl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1059,7 +1025,7 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_set_active_pass_count)(libra_
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Function pointer definition for
///libra_mtl_filter_chain_get_active_pass_count
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_active_pass_count)(const libra_mtl_filter_chain_t *chain,
typedef libra_error_t (*PFN_libra_mtl_filter_chain_get_active_pass_count)(libra_mtl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1092,17 +1058,10 @@ typedef libra_error_t (*PFN_libra_mtl_filter_chain_free)(libra_mtl_filter_chain_
/// ABI versions are not backwards compatible. It is not
/// valid to load a librashader C API instance for any ABI
/// version not equal to LIBRASHADER_CURRENT_ABI.
///
/// ## ABI Versions
/// - ABI version 0: null instance (unloaded)
/// - ABI version 1: 0.1.0
/// - ABI version 2: 0.5.0
/// - Reduced texture size information needed for some runtimes.
/// - Removed wrapper structs for Direct3D 11 SRV and RTV handles.
/// - Removed `gl_context_init`.
/// - Make viewport handling consistent across runtimes, which are now
/// span the output render target if omitted.
#define LIBRASHADER_CURRENT_ABI 2
#define LIBRASHADER_CURRENT_ABI 1
#ifdef __cplusplus
extern "C" {
@ -1196,9 +1155,7 @@ libra_error_t libra_preset_set_param(libra_shader_preset_t *preset, const char *
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` may be a pointer to a uninitialized `float`.
libra_error_t libra_preset_get_param(const libra_shader_preset_t *preset,
const char *name,
float *value);
libra_error_t libra_preset_get_param(libra_shader_preset_t *preset, const char *name, float *value);
/// Pretty print the shader preset.
///
@ -1216,7 +1173,7 @@ libra_error_t libra_preset_print(libra_shader_preset_t *preset);
/// cause undefined behaviour when later freed.
/// - It is safe to call `libra_preset_get_runtime_params` multiple times, however
/// the output struct must only be freed once per call.
libra_error_t libra_preset_get_runtime_params(const libra_shader_preset_t *preset,
libra_error_t libra_preset_get_runtime_params(libra_shader_preset_t *preset,
struct libra_preset_param_list_t *out);
/// Free the runtime parameters.
@ -1240,6 +1197,20 @@ libra_error_t libra_preset_get_runtime_params(const libra_shader_preset_t *prese
/// in undefined behaviour.
libra_error_t libra_preset_free_runtime_params(struct libra_preset_param_list_t preset);
#if defined(LIBRA_RUNTIME_OPENGL)
/// Initialize the OpenGL Context for librashader.
///
/// This only has to be done once throughout the lifetime of the application,
/// unless for whatever reason you switch OpenGL loaders mid-flight.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the GL context is not initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately invalidates previous filter
/// chain objects, and drawing with them causes immediate undefined behaviour.
libra_error_t libra_gl_init_context(libra_gl_loader_t loader);
#endif
#if defined(LIBRA_RUNTIME_OPENGL)
/// Create the filter chain given the shader preset.
///
@ -1251,7 +1222,6 @@ libra_error_t libra_preset_free_runtime_params(struct libra_preset_param_list_t
/// - `options` must be either null, or valid and aligned.
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out);
#endif
@ -1259,23 +1229,6 @@ libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
#if defined(LIBRA_RUNTIME_OPENGL)
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_gl_t`, containing the name of a Texture, format, and size information to
/// to an image that will serve as the source image for the frame.
/// - `out` is a `libra_output_framebuffer_gl_t`, containing the name of a Framebuffer, the name of a Texture, format,
/// and size information for the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -1289,9 +1242,9 @@ libra_error_t libra_gl_filter_chain_create(libra_shader_preset_t *preset,
/// the filter chain.
libra_error_t libra_gl_filter_chain_frame(libra_gl_filter_chain_t *chain,
size_t frame_count,
struct libra_image_gl_t image,
struct libra_image_gl_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_gl_t image,
struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out,
const float *mvp,
const struct frame_gl_opt_t *opt);
#endif
@ -1315,7 +1268,7 @@ libra_error_t libra_gl_filter_chain_set_param(libra_gl_filter_chain_t *chain,
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_gl_filter_chain_get_param(const libra_gl_filter_chain_t *chain,
libra_error_t libra_gl_filter_chain_get_param(libra_gl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1334,7 +1287,7 @@ libra_error_t libra_gl_filter_chain_set_active_pass_count(libra_gl_filter_chain_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
libra_error_t libra_gl_filter_chain_get_active_pass_count(const libra_gl_filter_chain_t *chain,
libra_error_t libra_gl_filter_chain_get_active_pass_count(libra_gl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1344,7 +1297,6 @@ libra_error_t libra_gl_filter_chain_get_active_pass_count(const libra_gl_filter_
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - The context that the filter chain was initialized with **must be current** before freeing the filter chain.
libra_error_t libra_gl_filter_chain_free(libra_gl_filter_chain_t *chain);
#endif
@ -1395,31 +1347,13 @@ libra_error_t libra_vk_filter_chain_create_deferred(libra_shader_preset_t *prese
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
///
/// A pipeline barrier **will not** be created for the final pass. The output image must be
/// in `VK_COLOR_ATTACHMENT_OPTIMAL`, and will remain so after all shader passes. The caller must transition
/// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
///
/// librashader **will not** create a pipeline barrier for the final pass. The output image will
/// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition
/// the output image to the final layout.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `VkCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for recording and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// to an image that will serve as the source image for the frame. The input image must be in
/// the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// - `out` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// for the render target of the frame. The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
/// The output image will remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `libra_vk_filter_chain_frame` **must not be called within a RenderPass**.
/// - `command_buffer` must be a valid handle to a `VkCommandBuffer` that is ready for recording.
@ -1434,9 +1368,9 @@ libra_error_t libra_vk_filter_chain_create_deferred(libra_shader_preset_t *prese
libra_error_t libra_vk_filter_chain_frame(libra_vk_filter_chain_t *chain,
VkCommandBuffer command_buffer,
size_t frame_count,
struct libra_image_vk_t image,
struct libra_image_vk_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_vk_t image,
struct libra_viewport_t viewport,
struct libra_output_image_vk_t out,
const float *mvp,
const struct frame_vk_opt_t *opt);
#endif
@ -1460,7 +1394,7 @@ libra_error_t libra_vk_filter_chain_set_param(libra_vk_filter_chain_t *chain,
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_vk_filter_chain_get_param(const libra_vk_filter_chain_t *chain,
libra_error_t libra_vk_filter_chain_get_param(libra_vk_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1479,7 +1413,7 @@ libra_error_t libra_vk_filter_chain_set_active_pass_count(libra_vk_filter_chain_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
libra_error_t libra_vk_filter_chain_get_active_pass_count(const libra_vk_filter_chain_t *chain,
libra_error_t libra_vk_filter_chain_get_active_pass_count(libra_vk_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1544,26 +1478,9 @@ libra_error_t libra_d3d11_filter_chain_create_deferred(libra_shader_preset_t *pr
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11))
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `device_context` is the ID3D11DeviceContext used to record draw commands to.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
/// the filter chain was created with.
///
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `ID3D11ShaderResourceView` that will serve as the source image for the frame.
/// - `out` is a pointer to a `ID3D11RenderTargetView` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
@ -1582,9 +1499,9 @@ libra_error_t libra_d3d11_filter_chain_create_deferred(libra_shader_preset_t *pr
libra_error_t libra_d3d11_filter_chain_frame(libra_d3d11_filter_chain_t *chain,
ID3D11DeviceContext * device_context,
size_t frame_count,
ID3D11ShaderResourceView * image,
struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport,
ID3D11RenderTargetView * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d11_opt_t *options);
#endif
@ -1608,7 +1525,7 @@ libra_error_t libra_d3d11_filter_chain_set_param(libra_d3d11_filter_chain_t *cha
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_d3d11_filter_chain_get_param(const libra_d3d11_filter_chain_t *chain,
libra_error_t libra_d3d11_filter_chain_get_param(libra_d3d11_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1627,7 +1544,7 @@ libra_error_t libra_d3d11_filter_chain_set_active_pass_count(libra_d3d11_filter_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
libra_error_t libra_d3d11_filter_chain_get_active_pass_count(const libra_d3d11_filter_chain_t *chain,
libra_error_t libra_d3d11_filter_chain_get_active_pass_count(libra_d3d11_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1660,37 +1577,29 @@ libra_error_t libra_d3d9_filter_chain_create(libra_shader_preset_t *preset,
#if (defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9))
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `IDirect3DTexture9` that will serve as the source image for the frame.
/// - `out` is a pointer to a `IDirect3DSurface9` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `Id3d9Device`
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `viewport` may be null, or if it is not null, must be an aligned pointer to an instance of `libra_viewport_t`.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
/// struct.
/// - `out` must not be null.
/// - `image` must not be null.
/// - `image.handle` must not be null.
/// - If `device_context` is null, commands will be recorded onto the immediate context of the `Id3d9Device`
/// this filter chain was created with. The context must otherwise be associated with the `Id3d9Device`
/// the filter chain was created with.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
libra_error_t libra_d3d9_filter_chain_frame(libra_d3d9_filter_chain_t *chain,
size_t frame_count,
IDirect3DTexture9 * image,
struct libra_viewport_t viewport,
IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_d3d9_opt_t *options);
#endif
@ -1714,7 +1623,7 @@ libra_error_t libra_d3d9_filter_chain_set_param(libra_d3d9_filter_chain_t *chain
/// ## 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.
libra_error_t libra_d3d9_filter_chain_get_param(const libra_d3d9_filter_chain_t *chain,
libra_error_t libra_d3d9_filter_chain_get_param(libra_d3d9_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1733,7 +1642,7 @@ libra_error_t libra_d3d9_filter_chain_set_active_pass_count(libra_d3d9_filter_ch
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
libra_error_t libra_d3d9_filter_chain_get_active_pass_count(const libra_d3d9_filter_chain_t *chain,
libra_error_t libra_d3d9_filter_chain_get_active_pass_count(libra_d3d9_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1791,39 +1700,13 @@ libra_error_t libra_d3d12_filter_chain_create_deferred(libra_shader_preset_t *pr
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command list.
///
/// A resource barrier **will not** be created for the final pass. The output image will
/// * The input image must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state.
/// * The output image must be in `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state.
///
/// librashader **will not** create a resource barrier for the final pass. The output image will
/// remain in `D3D12_RESOURCE_STATE_RENDER_TARGET` after all shader passes. The caller must transition
/// the output image to the final resource state.
///
/// The refcount of any COM pointers passed into this frame will not be changed. It is the responsibility
/// of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is
/// submitted.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_list` is a `ID3D12GraphicsCommandList` to record draw commands to.
/// The provided command list must be open and associated with the `ID3D12Device` this filter chain was created with.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_d3d12_t` with `image_type` set to `IMAGE_TYPE_SOURCE_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` either a `ID3D12Resource*` or an `libra_source_image_d3d12_t` containing a CPU descriptor to a shader resource view,
/// and the image resource the view is of, which will serve as the source image for the frame.
/// The input image resource must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state
/// or equivalent barrier layout. The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
/// - `out` is a `libra_image_d3d12_t`, with `image_type` set to `IMAGE_TYPE_OUTPUT_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` being either a `ID3D12Resource*` or an `libra_output_image_d3d12_t`, containing a CPU descriptor handle,
/// format, and size information for the render target of the frame. The output image must be in
/// `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state or equivalent barrier layout.
/// The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -1831,23 +1714,18 @@ libra_error_t libra_d3d12_filter_chain_create_deferred(libra_shader_preset_t *pr
/// 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_d3d12_opt_t`
/// struct.
/// - Any resource pointers contained within a `libra_image_d3d12_t` must be non-null.
/// - The `handle` field of any `libra_image_d3d12_t` must be valid for it's `image_type`.
/// - If `image_type` is `IMAGE_TYPE_RESOURCE`, then `handle` must be `ID3D12Resource *`.
/// - If `image_type` is `IMAGE_TYPE_SOURCE`, then `handle` must be `libra_source_image_d3d12_t`.
/// - If `image_type` is `IMAGE_TYPE_OUTPUT`, then `handle` must be `libra_output_image_d3d12_t`.
/// - `out` must be a descriptor handle to a render target view.
/// - `image.resource` must not be null.
/// - `command_list` must be a non-null pointer to a `ID3D12GraphicsCommandList` that is open,
/// and must be associated with the `ID3D12Device` this filter chain was created with.
/// - All resource pointers contained within a `libra_image_d3d12_t` must remain valid until the `ID3D12GraphicsCommandList *`
/// provided is submitted after the call to this function.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
libra_error_t libra_d3d12_filter_chain_frame(libra_d3d12_filter_chain_t *chain,
ID3D12GraphicsCommandList * command_list,
size_t frame_count,
struct libra_image_d3d12_t image,
struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport,
struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport,
struct libra_output_image_d3d12_t out,
const float *mvp,
const struct frame_d3d12_opt_t *options);
#endif
@ -1871,7 +1749,7 @@ libra_error_t libra_d3d12_filter_chain_set_param(libra_d3d12_filter_chain_t *cha
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
libra_error_t libra_d3d12_filter_chain_get_param(const libra_d3d12_filter_chain_t *chain,
libra_error_t libra_d3d12_filter_chain_get_param(libra_d3d12_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -1890,7 +1768,7 @@ libra_error_t libra_d3d12_filter_chain_set_active_pass_count(libra_d3d12_filter_
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
libra_error_t libra_d3d12_filter_chain_get_active_pass_count(const libra_d3d12_filter_chain_t *chain,
libra_error_t libra_d3d12_filter_chain_get_active_pass_count(libra_d3d12_filter_chain_t *chain,
uint32_t *out);
#endif
@ -1951,22 +1829,6 @@ libra_error_t libra_mtl_filter_chain_create_deferred(libra_shader_preset_t *pres
#if (defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__))
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `MTLCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for encoding and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `id<MTLTexture>` that will serve as the source image for the frame.
/// - `out` is a `id<MTLTexture>` that is the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
@ -1982,8 +1844,8 @@ libra_error_t libra_mtl_filter_chain_frame(libra_mtl_filter_chain_t *chain,
id<MTLCommandBuffer> command_buffer,
size_t frame_count,
id<MTLTexture> image,
struct libra_viewport_t viewport,
id<MTLTexture> output,
const struct libra_viewport_t *viewport,
const float *mvp,
const struct frame_mtl_opt_t *opt);
#endif
@ -2007,7 +1869,7 @@ libra_error_t libra_mtl_filter_chain_set_param(libra_mtl_filter_chain_t *chain,
/// ## 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.
libra_error_t libra_mtl_filter_chain_get_param(const libra_mtl_filter_chain_t *chain,
libra_error_t libra_mtl_filter_chain_get_param(libra_mtl_filter_chain_t *chain,
const char *param_name,
float *out);
#endif
@ -2026,7 +1888,7 @@ libra_error_t libra_mtl_filter_chain_set_active_pass_count(libra_mtl_filter_chai
///
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
libra_error_t libra_mtl_filter_chain_get_active_pass_count(const libra_mtl_filter_chain_t *chain,
libra_error_t libra_mtl_filter_chain_get_active_pass_count(libra_mtl_filter_chain_t *chain,
uint32_t *out);
#endif
@ -2055,7 +1917,7 @@ LIBRASHADER_API_VERSION libra_instance_api_version(void);
///
/// 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.
/// 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.
@ -2109,8 +1971,6 @@ libra_error_t libra_preset_ctx_set_param(libra_preset_ctx_t *context,
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///
@ -2169,7 +2029,7 @@ libra_error_t libra_preset_ctx_set_core_aspect_orientation(libra_preset_ctx_t *c
LIBRA_PRESET_CTX_ORIENTATION value);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __LIBRASHADER_H__ */
#endif /* __LIBRASHADER_H__ */

View file

@ -35,7 +35,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// #if defined(_WIN32)
// #define LIBRA_RUNTIME_D3D11
// #define LIBRA_RUNTIME_D3D12
// #define LIBRA_RUNTIME_D3D9
// #endif
// #if (defined(__APPLE__) && defined(__OBJC__))
@ -179,7 +178,7 @@ libra_error_t __librashader__noop_preset_set_param(
}
libra_error_t __librashader__noop_preset_get_param(
const libra_shader_preset_t *preset, const char *name, float *value) {
libra_shader_preset_t *preset, const char *name, float *value) {
return NULL;
}
@ -187,7 +186,7 @@ libra_error_t __librashader__noop_preset_print(libra_shader_preset_t *preset) {
return NULL;
}
libra_error_t __librashader__noop_preset_get_runtime_params(
const libra_shader_preset_t *preset, struct libra_preset_param_list_t *out) {
libra_shader_preset_t *preset, struct libra_preset_param_list_t *out) {
return NULL;
}
libra_error_t __librashader__noop_preset_free_runtime_params(
@ -195,9 +194,12 @@ libra_error_t __librashader__noop_preset_free_runtime_params(
return NULL;
}
#if defined(LIBRA_RUNTIME_OPENGL)
libra_error_t __librashader__noop_gl_init_context(libra_gl_loader_t loader) {
return NULL;
}
libra_error_t __librashader__noop_gl_filter_chain_create(
libra_shader_preset_t *preset, libra_gl_loader_t loader,
const struct filter_chain_gl_opt_t *options,
libra_shader_preset_t *preset, const struct filter_chain_gl_opt_t *options,
libra_gl_filter_chain_t *out) {
*out = NULL;
return NULL;
@ -205,8 +207,8 @@ libra_error_t __librashader__noop_gl_filter_chain_create(
libra_error_t __librashader__noop_gl_filter_chain_frame(
libra_gl_filter_chain_t *chain, size_t frame_count,
struct libra_image_gl_t image, struct libra_image_gl_t out,
const struct libra_viewport_t *viewport, const float *mvp,
struct libra_source_image_gl_t image, struct libra_viewport_t viewport,
struct libra_output_framebuffer_gl_t out, const float *mvp,
const struct frame_gl_opt_t *opt) {
return NULL;
}
@ -222,7 +224,7 @@ libra_error_t __librashader__noop_gl_filter_chain_set_param(
}
libra_error_t __librashader__noop_gl_filter_chain_get_param(
const libra_gl_filter_chain_t *chain, const char *param_name, float *out) {
libra_gl_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -232,7 +234,7 @@ libra_error_t __librashader__noop_gl_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_gl_filter_chain_get_active_pass_count(
const libra_gl_filter_chain_t *chain, uint32_t *out) {
libra_gl_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -255,9 +257,9 @@ libra_error_t __librashader__noop_vk_filter_chain_create_deferred(
libra_error_t __librashader__noop_vk_filter_chain_frame(
libra_vk_filter_chain_t *chain, VkCommandBuffer command_buffer,
size_t frame_count, struct libra_image_vk_t image, struct libra_image_vk_t out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_vk_opt_t *opt) {
size_t frame_count, struct libra_source_image_vk_t image,
struct libra_viewport_t viewport, struct libra_output_image_vk_t out,
const float *mvp, const struct frame_vk_opt_t *opt) {
return NULL;
}
@ -272,7 +274,7 @@ libra_error_t __librashader__noop_vk_filter_chain_set_param(
}
libra_error_t __librashader__noop_vk_filter_chain_get_param(
const libra_vk_filter_chain_t *chain, const char *param_name, float *out) {
libra_vk_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -282,7 +284,7 @@ libra_error_t __librashader__noop_vk_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_vk_filter_chain_get_active_pass_count(
const libra_vk_filter_chain_t *chain, uint32_t *out) {
libra_vk_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -307,9 +309,9 @@ libra_error_t __librashader__noop_d3d11_filter_chain_create_deferred(
libra_error_t __librashader__noop_d3d11_filter_chain_frame(
libra_d3d11_filter_chain_t *chain, ID3D11DeviceContext *device_context,
size_t frame_count, ID3D11ShaderResourceView *image, ID3D11RenderTargetView *out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_d3d11_opt_t *opt) {
size_t frame_count, struct libra_source_image_d3d11_t image,
struct libra_viewport_t viewport, ID3D11RenderTargetView *out,
const float *mvp, const struct frame_d3d11_opt_t *opt) {
return NULL;
}
@ -324,7 +326,7 @@ libra_error_t __librashader__noop_d3d11_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d11_filter_chain_get_param(
const libra_d3d11_filter_chain_t *chain, const char *param_name, float *out) {
libra_d3d11_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -334,7 +336,7 @@ libra_error_t __librashader__noop_d3d11_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d11_filter_chain_get_active_pass_count(
const libra_d3d11_filter_chain_t *chain, uint32_t *out) {
libra_d3d11_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -359,9 +361,9 @@ libra_error_t __librashader__noop_d3d12_filter_chain_create_deferred(
libra_error_t __librashader__noop_d3d12_filter_chain_frame(
libra_d3d12_filter_chain_t *chain, ID3D12GraphicsCommandList *command_list,
size_t frame_count, struct libra_image_d3d12_t image, struct libra_image_d3d12_t out,
const struct libra_viewport_t *viewport, const float *mvp,
const struct frame_d3d12_opt_t *opt) {
size_t frame_count, struct libra_source_image_d3d12_t image,
struct libra_viewport_t viewport, struct libra_output_image_d3d12_t out,
const float *mvp, const struct frame_d3d12_opt_t *opt) {
return NULL;
}
@ -376,7 +378,7 @@ libra_error_t __librashader__noop_d3d12_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d12_filter_chain_get_param(
const libra_d3d12_filter_chain_t *chain, const char *param_name, float *out) {
libra_d3d12_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -386,7 +388,7 @@ libra_error_t __librashader__noop_d3d12_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d12_filter_chain_get_active_pass_count(
const libra_d3d12_filter_chain_t *chain, uint32_t *out) {
libra_d3d12_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -402,8 +404,8 @@ libra_error_t __librashader__noop_d3d9_filter_chain_create(
libra_error_t __librashader__noop_d3d9_filter_chain_frame(
libra_d3d9_filter_chain_t *chain, size_t frame_count,
IDirect3DTexture9 *image, IDirect3DSurface9 * out,
const struct libra_viewport_t *viewport, const float *mvp,
IDirect3DTexture9 *image, struct libra_viewport_t viewport,
IDirect3DSurface9 * out, const float *mvp,
const struct frame_d3d9_opt_t *opt) {
return NULL;
}
@ -419,7 +421,7 @@ libra_error_t __librashader__noop_d3d9_filter_chain_set_param(
}
libra_error_t __librashader__noop_d3d9_filter_chain_get_param(
const libra_d3d9_filter_chain_t *chain, const char *param_name, float *out) {
libra_d3d9_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -429,7 +431,7 @@ libra_error_t __librashader__noop_d3d9_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_d3d9_filter_chain_get_active_pass_count(
const libra_d3d9_filter_chain_t *chain, uint32_t *out) {
libra_d3d9_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -454,8 +456,8 @@ libra_error_t __librashader__noop_mtl_filter_chain_create_deferred(
libra_error_t __librashader__noop_mtl_filter_chain_frame(
libra_mtl_filter_chain_t *chain, id<MTLCommandBuffer> command_buffer,
size_t frame_count, id<MTLTexture> image, id<MTLTexture> output,
const struct libra_viewport_t *viewport, const float *mvp,
size_t frame_count, id<MTLTexture> image, struct libra_viewport_t viewport,
id<MTLTexture> output, const float *mvp,
const struct frame_mtl_opt_t *opt) {
return NULL;
}
@ -471,7 +473,7 @@ libra_error_t __librashader__noop_mtl_filter_chain_set_param(
}
libra_error_t __librashader__noop_mtl_filter_chain_get_param(
const libra_mtl_filter_chain_t *chain, const char *param_name, float *out) {
libra_mtl_filter_chain_t *chain, const char *param_name, float *out) {
return NULL;
}
@ -481,7 +483,7 @@ libra_error_t __librashader__noop_mtl_filter_chain_set_active_pass_count(
}
libra_error_t __librashader__noop_mtl_filter_chain_get_active_pass_count(
const libra_mtl_filter_chain_t *chain, uint32_t *out) {
libra_mtl_filter_chain_t *chain, uint32_t *out) {
return NULL;
}
#endif
@ -571,8 +573,6 @@ typedef struct libra_instance_t {
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///
@ -799,6 +799,17 @@ typedef struct libra_instance_t {
PFN_libra_error_free_string error_free_string;
#if defined(LIBRA_RUNTIME_OPENGL)
/// Initialize the OpenGL Context for librashader.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the context is not
/// initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately
/// invalidates previous filter chain objects, and drawing with them causes
/// immediate undefined behaviour.
PFN_libra_gl_init_context gl_init_context;
/// Create the filter chain given the shader preset.
///
/// The shader preset is immediately invalidated and must be recreated after
@ -1323,7 +1334,7 @@ typedef struct libra_instance_t {
/// struct.
PFN_libra_mtl_filter_chain_frame mtl_filter_chain_frame;
/// Free a Metal filter chain.
/// Free a D3D11 filter chain.
///
/// The resulting value in `chain` then becomes null.
/// ## Safety
@ -1434,6 +1445,7 @@ libra_instance_t __librashader_make_null_instance(void) {
instance.error_free_string = __librashader__noop_error_free_string;
#if defined(LIBRA_RUNTIME_OPENGL)
instance.gl_init_context = __librashader__noop_gl_init_context;
instance.gl_filter_chain_create =
__librashader__noop_gl_filter_chain_create;
instance.gl_filter_chain_frame = __librashader__noop_gl_filter_chain_frame;
@ -1608,6 +1620,7 @@ libra_instance_t librashader_load_instance(void) {
_LIBRASHADER_ASSIGN(librashader, instance, error_free_string);
#if defined(LIBRA_RUNTIME_OPENGL)
_LIBRASHADER_ASSIGN(librashader, instance, gl_init_context);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_create);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_frame);
_LIBRASHADER_ASSIGN(librashader, instance, gl_filter_chain_free);

View file

@ -6,9 +6,9 @@ publish = false
[dependencies]
cbindgen = "0.27.0"
clap = { workspace = true }
carlog = "0.1.0"
cbindgen = "0.26.0"
clap = { version = "=4.1.0", features = ["derive"] }
[package.metadata.release]
release = false

View file

@ -1,4 +1,3 @@
use carlog::*;
use clap::Parser;
use std::fs::File;
use std::io::{BufWriter, Write};
@ -13,8 +12,6 @@ struct Args {
profile: String,
#[arg(long, global = true)]
target: Option<String>,
#[arg(long, default_value_t = false, global = true)]
stable: bool,
#[arg(last = true)]
cargoflags: Vec<String>,
}
@ -30,11 +27,9 @@ pub fn main() -> ExitCode {
let profile = args.profile;
let crate_dir = Path::new("librashader-capi");
carlog_info!("Building", "librashader C API");
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let mut cmd = Command::new(&cargo);
println!("Building librashader C API...");
let mut cmd = Command::new("cargo");
cmd.arg("build");
cmd.args(["--package", "librashader-capi"]);
cmd.arg(format!(
@ -51,22 +46,11 @@ pub fn main() -> ExitCode {
cmd.arg(format!("--target={}", &target));
}
if args.stable {
carlog_warning!("building librashader with stable Rust compatibility");
carlog_warning!("C headers will not be generated");
cmd.args(["--features", "stable"]);
}
if !args.cargoflags.is_empty() {
cmd.args(args.cargoflags);
}
let Ok(status) = cmd.status().inspect_err(|err| {
carlog_error!("failed to build librashader-capi");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
let status = cmd.status().expect("Failed to build librashader-capi");
if !status.success() {
return ExitCode::from(status.code().unwrap_or(1) as u8);
}
@ -76,76 +60,39 @@ pub fn main() -> ExitCode {
output_dir = PathBuf::from(format!("target/{}/{}", target, profile));
}
let Ok(output_dir) = output_dir.canonicalize() else {
carlog_error!("could not find output directory");
println!("help: are you running the build script from the repository root?");
return ExitCode::FAILURE;
};
let output_dir = output_dir
.canonicalize()
.expect("Could not find output directory.");
if args.stable {
carlog_warning!("generating C headers is not supported when building for stable Rust");
} else {
carlog_info!("Generating", "librashader C API headers");
println!("Generating C headers...");
// Create headers.
let mut buf = BufWriter::new(Vec::new());
let Ok(bindings) = cbindgen::generate(crate_dir).inspect_err(|err| {
carlog_error!("unable to generate C API headers");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
// Create headers.
let mut buf = BufWriter::new(Vec::new());
cbindgen::generate(crate_dir)
.expect("Unable to generate bindings")
.write(&mut buf);
bindings.write(&mut buf);
let bytes = buf.into_inner().expect("Unable to extract bytes");
let string = String::from_utf8(bytes).expect("Unable to create string");
let bytes = buf.into_inner().expect("Unable to extract bytes");
let string = String::from_utf8(bytes).expect("Unable to create string");
File::create(output_dir.join("librashader.h"))
.expect("Unable to open file")
.write_all(string.as_bytes())
.expect("Unable to write bindings.");
let Ok(mut file) = File::create(output_dir.join("librashader.h")).inspect_err(|err| {
carlog_error!("unable to open librashader.h");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
let Ok(_) = file.write_all(string.as_bytes()).inspect_err(|err| {
carlog_error!("unable to write to librashader.h");
carlog_error!(format!("{err}"));
}) else {
return ExitCode::FAILURE;
};
}
carlog_info!("Moving", "built artifacts");
println!("Moving artifacts...");
if cfg!(target_os = "macos") {
let artifacts = &["liblibrashader_capi.dylib", "liblibrashader_capi.a"];
for artifact in artifacts {
let ext = artifact.strip_prefix("lib").unwrap();
let ext = ext.replace("_capi", "");
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
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();
let ext = ext.replace("_capi", "");
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
}
}
@ -159,26 +106,19 @@ pub fn main() -> ExitCode {
];
for artifact in artifacts {
let ext = artifact.replace("_capi", "");
let Ok(_) =
fs::rename(output_dir.join(artifact), output_dir.join(&ext)).inspect_err(|err| {
carlog_error!(format!("Unable to rename {artifact} to {}", &ext));
carlog_error!(format!("{err}"));
})
else {
return ExitCode::FAILURE;
};
carlog_ok!("Renamed", format!("{artifact} to {}", &ext));
println!("Renaming {artifact} to {ext}");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
}
if output_dir.join("librashader_capi.pdb").exists() {
println!("Renaming librashader_capi.pdb to librashader.pdb");
fs::rename(
output_dir.join("librashader_capi.pdb"),
output_dir.join("librashader.pdb"),
)
.unwrap();
carlog_ok!("Renamed", "librashader_capi.pdb to librashader.pdb");
}
}
ExitCode::SUCCESS
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.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,20 +12,16 @@ description = "RetroArch shaders for all."
[dependencies]
serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.5.1", features = ["serde"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
platform-dirs = "0.3.0"
blake3 = { version = "1.5.4" }
blake3 = { version = "1.3.3" }
thiserror = "1.0.38"
bincode = { version = "2.0.0-rc.2", features = ["serde"] }
persy = "1.4.7"
bytemuck = "1.13.0"
[target.x86_64-win7-windows-msvc.dependencies.blake3]
version = "1.5.4"
features = ["pure"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true
features = [

View file

@ -3,7 +3,7 @@ name = "librashader-capi"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -17,7 +17,7 @@ crate-type = [ "cdylib", "staticlib" ]
[features]
default = ["runtime-all" ]
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
runtime-opengl = ["glow", "librashader/runtime-gl"]
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"]
@ -26,8 +26,6 @@ runtime-vulkan = ["ash", "librashader/runtime-vk"]
runtime-metal = ["__cbindgen_internal_objc", "librashader/runtime-metal"]
reflect-unstable = []
stable = ["librashader/stable"]
docsrs = []
__cbindgen_internal = ["runtime-all"]
@ -38,16 +36,15 @@ __cbindgen_internal_objc = ["objc2-metal", "objc2"]
[dependencies]
thiserror = "1.0.37"
paste = "1.0.9"
gl = { version = "0.14.0", optional = true }
rustc-hash = "2.0.0"
ash = { version = "0.38", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
sptr = "0.3.2"
glow = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
[dependencies.librashader]
path = "../librashader"
version = "0.5.1"
version = "0.3.0"
default-features = false
features = ["reflect", "presets", "preprocess"]
@ -67,4 +64,4 @@ targets = [ "x86_64-pc-windows-msvc",
"aarch64-apple-ios",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu", ]
features = ["docsrs", "librashader/docsrs"]
features = ["librashader/docsrs"]

View file

@ -173,7 +173,6 @@ include = [
exclude = [
"Option_ID3D11DeviceContext",
"Option_PFN_vkGetInstanceProcAddr",
"PMTLCommandQueue",
"PMTLCommandBuffer",
"PMTLTexture"
@ -199,7 +198,6 @@ exclude = [
"CommandBuffer" = "VkCommandBuffer"
"Format" = "VkFormat"
"Image" = "VkImage"
"Queue" = "VkQueue"
# hack to get proper pointer indirection for COM pointers
# we don't need one for ID3D11DeviceContext.
@ -216,9 +214,6 @@ exclude = [
# hack to force cbindgen to not generate option type for nullable ID3D11DeviceContext.
"Option_ID3D11DeviceContext" = "ID3D11DeviceContext *"
# hack to force cbindgen to not generate option type for nullable PFN_vkGetInstanceProcAddr.
"Option_PFN_vkGetInstanceProcAddr" = "PFN_vkGetInstanceProcAddr"
# hack to get proper pointer indirection for COM pointers
"ID3D12Device" = "ID3D12Device *"
"ID3D12Resource" = "ID3D12Resource *"

View file

@ -18,9 +18,7 @@ pub type libra_error_t = Option<NonNull<LibrashaderError>>;
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_ORIENTATION {
/// Context parameter for vertical orientation.
Vertical = 0,
/// Context parameter for horizontal orientation.
Horizontal,
}
impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
@ -32,24 +30,16 @@ impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
}
}
/// An enum representing graphics runtimes (video drivers) for use in preset contexts.
// An enum representing graphics runtimes (video drivers) for use in preset contexts.
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum LIBRA_PRESET_CTX_RUNTIME {
/// No runtime.
None = 0,
/// OpenGL 3.3+
GlCore,
/// Vulkan
Vulkan,
/// Direct3D 11
D3D11,
/// Direct3D 12
D3D12,
/// Metal
Metal,
/// Direct3D 9
D3D9_HLSL,
}
impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
@ -61,7 +51,6 @@ impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
LIBRA_PRESET_CTX_RUNTIME::D3D11 => VideoDriver::Direct3D11,
LIBRA_PRESET_CTX_RUNTIME::D3D12 => VideoDriver::Direct3D12,
LIBRA_PRESET_CTX_RUNTIME::Metal => VideoDriver::Metal,
LIBRA_PRESET_CTX_RUNTIME::D3D9_HLSL => VideoDriver::Direct3D9Hlsl,
}
}
}
@ -71,7 +60,7 @@ use librashader::runtime::gl::FilterChain as FilterChainGL;
/// A handle to a OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
#[doc(cfg(feature = "runtime-opengl"))]
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
/// A handle to a Direct3D 11 filter chain.
@ -82,10 +71,7 @@ pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
/// A handle to a Direct3D 11 filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
@ -112,10 +98,7 @@ pub type libra_d3d12_filter_chain_t = Option<NonNull<FilterChainD3D12>>;
use librashader::runtime::d3d9::FilterChain as FilterChainD3D9;
/// A handle to a Direct3D 11 filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
@ -126,17 +109,12 @@ pub type libra_d3d9_filter_chain_t = Option<NonNull<FilterChainD3D9>>;
use librashader::runtime::vk::FilterChain as FilterChainVulkan;
/// A handle to a Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
#[doc(cfg(feature = "runtime-vulkan"))]
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;
/// A handle to a Vulkan filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(
@ -147,18 +125,16 @@ use librashader::runtime::mtl::FilterChain as FilterChainMetal;
))]
pub type libra_mtl_filter_chain_t = Option<NonNull<FilterChainMetal>>;
/// Defines the output origin for a rendered frame.
/// Defines the output viewport for a rendered frame.
#[repr(C)]
pub struct libra_viewport_t {
/// The x offset in the viewport framebuffer to begin rendering from.
pub x: f32,
/// The y offset in the viewport framebuffer to begin rendering from.
pub y: f32,
/// The width extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by x.
/// The width of the viewport framebuffer.
pub width: u32,
/// The height extent of the viewport framebuffer to end rendering, relative to
/// the origin specified by y.
/// The height of the viewport framebuffer.
pub height: u32,
}
@ -170,85 +146,29 @@ where
}
macro_rules! config_set_field {
(@POINTER $options:ident.$field:ident <- $ptr:ident) => {
($options:ident.$field:ident <- $ptr:ident) => {
$options.$field = unsafe { ::std::ptr::addr_of!((*$ptr).$field).read() };
};
(@POINTER @NEGATIVE $options:ident.$field:ident <- $ptr:ident) => {
$options.$field = unsafe { !::std::ptr::addr_of!((*$ptr).$field).read() };
};
(@LITERAL $options:ident.$field:ident <- $value:literal) => {
$options.$field = $value;
};
}
macro_rules! config_version_set {
// "optimized" version for normal behaviour
(@ROOT $realver:ident $version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
($version:literal => [$($field:ident),+ $(,)?] ($options:ident <- $ptr:ident)) => {
let version = unsafe { ::std::ptr::addr_of!((*$ptr).version).read() };
#[allow(unused_comparisons)]
if $realver >= $version {
$($crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);)+
if version >= $version {
$($crate::ctypes::config_set_field!($options.$field <- $ptr);)+
}
};
// Repeater
(@ROOT $realver:ident $version:literal => [$($field:tt),+ $(,)?] ($options:ident <- $ptr:ident)) => {
$(crate::ctypes::config_version_set!(@SINGLE $realver $version => [$field] ($options <- $ptr));)+
};
// Allow overriding default value with a literal for older versions
(@SINGLE $realver:ident $version:literal => [($field:ident: $value:literal)] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@LITERAL $options.$field <- $value);
}
};
// Allow negation of prior variables that is version dependent.
(@SINGLE $realver:ident $version:literal => [(!$field:ident)] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@POINTER @NEGATIVE $options.$field <- $ptr);
}
};
(@SINGLE $realver:ident $version:literal => [$field:ident] ($options:ident <- $ptr:ident)) => {
#[allow(unused_comparisons)]
if $realver >= $version {
$crate::ctypes::config_set_field!(@POINTER $options.$field <- $ptr);
}
};
}
}
/// Macro to declare a configuration struct, with options to change behaviour based on
/// API version.
///
/// For example following declaration does the following
///
/// * Declare `frames_in_flight`, `use_dynamic_rendering` for API version 0, with the following forward compatibility statements
/// * Inverts the behaviour of `use_dynamic_rendering` compared to API version 1.
/// * `disable_cache` is defaulted to `true` for API version 0, regardless of `Default::default`
/// but is not declared for API 0.
/// * Declare `use_dynamic_rendering` with normal behaviour, and `disable_cache` for API version 1.
/// * All fields that are undeclared inherit `Default::default`
///
/// ```rust
/// config_struct! {
/// impl FilterChainOptions => filter_chain_vk_opt_t {
/// 0 => [frames_in_flight, (!use_dynamic_rendering), (disable_cache: true)];
/// 1 => [use_dynamic_rendering, disable_cache];
/// }
/// }
/// ```
macro_rules! config_struct {
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:tt),+]);+ $(;)?}) => {
(impl $rust:ty => $capi:ty {$($version:literal => [$($field:ident),+ $(,)?]);+ $(;)?}) => {
impl $crate::ctypes::FromUninit<$rust> for $capi {
fn from_uninit(value: ::std::mem::MaybeUninit<Self>) -> $rust {
let ptr = value.as_ptr();
let version = unsafe { ::std::ptr::addr_of!((*ptr).version).read() };
let mut options = <$rust>::default();
$(
$crate::ctypes::config_version_set!(@ROOT version $version => [$($field),+] (options <- ptr));
$crate::ctypes::config_version_set!($version => [$($field),+] (options <- ptr));
)+
options
}

View file

@ -9,116 +9,58 @@ use thiserror::Error;
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum LibrashaderError {
/// An unknown error or panic occurred.
#[error("There was an unknown error.")]
UnknownError(Box<dyn Any + Send + 'static>),
/// An invalid parameter (likely null), was passed.
#[error("The parameter was null or invalid.")]
InvalidParameter(&'static str),
/// The string provided was not valid UTF-8.
#[error("The provided string was not valid UTF8.")]
InvalidString(#[from] std::str::Utf8Error),
/// An error occurred in the preset parser.
#[error("There was an error parsing the preset.")]
PresetError(#[from] librashader::presets::ParsePresetError),
/// An error occurred in the shader preprocessor.
#[error("There was an error preprocessing the shader source.")]
PreprocessError(#[from] librashader::preprocess::PreprocessError),
/// An error occurred in the shader compiler.
#[error("There was an error compiling the shader source.")]
ShaderCompileError(#[from] librashader::reflect::ShaderCompileError),
/// An error occrred when validating and reflecting the shader.
#[error("There was an error reflecting the shader source.")]
ShaderReflectError(#[from] librashader::reflect::ShaderReflectError),
/// An invalid shader parameter name was provided.
#[error("The provided parameter name was invalid.")]
UnknownShaderParameter(*const c_char),
/// An error occurred with the OpenGL filter chain.
#[cfg(feature = "runtime-opengl")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
#[doc(cfg(feature = "runtime-opengl"))]
#[error("There was an error in the OpenGL filter chain.")]
OpenGlFilterError(#[from] librashader::runtime::gl::error::FilterChainError),
/// An error occurred with the Direct3D 11 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[error("There was an error in the D3D11 filter chain.")]
D3D11FilterError(#[from] librashader::runtime::d3d11::error::FilterChainError),
/// An error occurred with the Direct3D 12 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[error("There was an error in the D3D12 filter chain.")]
D3D12FilterError(#[from] librashader::runtime::d3d12::error::FilterChainError),
/// An error occurred with the Direct3D 9 filter chain.
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[error("There was an error in the D3D9 filter chain.")]
D3D9FilterError(#[from] librashader::runtime::d3d9::error::FilterChainError),
/// An error occurred with the Vulkan filter chain.
#[cfg(feature = "runtime-vulkan")]
#[cfg_attr(feature = "docsrs", doc(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),
/// An error occurred with the Metal filter chain.
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
#[error("There was an error in the Metal filter chain.")]
#[error("There was an error in the D3D12 filter chain.")]
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
/// This error is unreachable.
#[error("This error is not reachable")]
Infallible(#[from] std::convert::Infallible),
}
/// Error codes for librashader error types.
#[repr(i32)]
pub enum LIBRA_ERRNO {
/// Error code for an unknown error.
UNKNOWN_ERROR = 0,
/// Error code for an invalid parameter.
INVALID_PARAMETER = 1,
/// Error code for an invalid (non-UTF8) string.
INVALID_STRING = 2,
/// Error code for a preset parser error.
PRESET_ERROR = 3,
/// Error code for a preprocessor error.
PREPROCESS_ERROR = 4,
/// Error code for a shader parameter error.
SHADER_PARAMETER_ERROR = 5,
/// Error code for a reflection error.
REFLECT_ERROR = 6,
/// Error code for a runtime error.
RUNTIME_ERROR = 7,
}
@ -257,7 +199,6 @@ impl LibrashaderError {
LibrashaderError::VulkanFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
LibrashaderError::MetalFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
LibrashaderError::Infallible(_) => LIBRA_ERRNO::UNKNOWN_ERROR,
}
}
pub(crate) const fn ok() -> libra_error_t {
@ -270,12 +211,12 @@ impl LibrashaderError {
}
macro_rules! assert_non_null {
(@EXPORT $value:ident) => {
($value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
};
($value:ident) => {
(noexport $value:ident) => {
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
@ -287,18 +228,14 @@ macro_rules! assert_non_null {
macro_rules! assert_some_ptr {
($value:ident) => {
if $value.is_none() {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
let $value = unsafe { $value.as_ref().unwrap_unchecked().as_ref() };
};
(mut $value:ident) => {
if $value.is_none() {
return Err($crate::error::LibrashaderError::InvalidParameter(
stringify!($value),
));
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
}
let $value = unsafe { $value.as_mut().unwrap_unchecked().as_mut() };

View file

@ -1,15 +1,9 @@
macro_rules! wrap_ok {
($e:expr) => {
::core::iter::empty().try_fold($e, |_, __x: ::core::convert::Infallible| match __x {})
};
}
macro_rules! ffi_body {
(nopanic $body:block) => {
{
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
}))();
};
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -28,13 +22,13 @@ macro_rules! ffi_body {
};
(nopanic |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$($crate::error::assert_non_null!($ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$($crate::error::assert_non_null!($mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
}))();
};
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -53,11 +47,11 @@ macro_rules! ffi_body {
};
(nopanic mut |$($mut_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $mut_capture);)*
$($crate::error::assert_non_null!($mut_capture);)*
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
}))();
};
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -76,11 +70,11 @@ macro_rules! ffi_body {
};
(nopanic |$($ref_capture:ident),*| $body:block) => {
{
$($crate::error::assert_non_null!(@EXPORT $ref_capture);)*
$($crate::error::assert_non_null!($ref_capture);)*
$(let $ref_capture = unsafe { &*$ref_capture };)*
let result: Result<(), $crate::error::LibrashaderError> = (|| $crate::ffi::wrap_ok!({
let result: Result<(), $crate::error::LibrashaderError> = try {
$body
}))();
};
let Err(e) = result else {
return $crate::error::LibrashaderError::ok()
@ -237,16 +231,16 @@ 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");
}
sptr::Strict::addr(ptr) & (align - 1) == 0
ptr.addr() & (align - 1) == 0
}
pub(crate) use extern_fn;
pub(crate) use ffi_body;
pub(crate) use wrap_ok;
use std::mem::ManuallyDrop;

View file

@ -1,4 +1,4 @@
#![forbid(missing_docs)]
#![feature(doc_cfg)]
//! The C API for [librashader](https://docs.rs/librashader/).
//!
//! The librashader C API is designed to be loaded dynamically via `librashader_ld.h`, but static usage is also
@ -60,13 +60,11 @@
//! to the same context as the drawing thread, and in Direct3D 11, where filter chain creation is unsafe
//! if the `ID3D11Device` was created with `D3D11_CREATE_DEVICE_SINGLETHREADED`. Metal is entirely thread unsafe.
//!
//! Setting and retrieving filter parameters from any thread, regardless of the lack of other thread safety-guarantees
//! of the runtime, is always thread safe.
//!
//! You must ensure that only thread has access to a created filter pass **before** you call `*_frame`. `*_frame` may only be
//! called from one thread at a time.
#![cfg_attr(feature = "docsrs", feature(doc_cfg))]
#![allow(non_camel_case_types)]
#![feature(try_blocks)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(deprecated)]

View file

@ -14,10 +14,11 @@ const _: () = crate::assert_thread_safe::<ShaderPreset>();
pub struct libra_preset_param_list_t {
/// A pointer to the parameter
pub parameters: *const libra_preset_param_t,
/// The number of parameters in the list. This field
/// is readonly, and changing it will lead to undefined
/// behaviour on free.
/// The number of parameters in the list.
pub length: u64,
/// For internal use only.
/// Changing this causes immediate undefined behaviour on freeing this parameter list.
pub _internal_alloc: u64,
}
/// A preset parameter.
@ -157,7 +158,7 @@ extern_fn! {
/// - `name` must be null or a valid and aligned pointer to a string.
/// - `value` may be a pointer to a uninitialized `float`.
fn libra_preset_get_param(
preset: *const libra_shader_preset_t,
preset: *mut libra_shader_preset_t,
name: *const c_char,
value: *mut MaybeUninit<f32>
) |name, preset| {
@ -195,7 +196,7 @@ extern_fn! {
/// - It is safe to call `libra_preset_get_runtime_params` multiple times, however
/// the output struct must only be freed once per call.
fn libra_preset_get_runtime_params(
preset: *const libra_shader_preset_t,
preset: *mut libra_shader_preset_t,
out: *mut MaybeUninit<libra_preset_param_list_t>
) |preset| {
assert_some_ptr!(preset);
@ -204,7 +205,7 @@ extern_fn! {
let iter = librashader::presets::get_parameter_meta(preset)?;
let mut values = Vec::new();
for param in iter {
let name = CString::new(param.id.to_string())
let name = CString::new(param.id)
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
let description = CString::new(param.description)
.map_err(|err| LibrashaderError::UnknownError(Box::new(err)))?;
@ -225,6 +226,7 @@ extern_fn! {
out.write(MaybeUninit::new(libra_preset_param_list_t {
parameters: parts,
length: len as u64,
_internal_alloc: 0,
}));
}
}

View file

@ -1,6 +1,6 @@
use crate::error;
use librashader::presets::{PassConfig, ShaderPreset};
use librashader::presets::{ShaderPassConfig, ShaderPreset};
use librashader::reflect::semantics::ShaderSemantics;
use librashader::reflect::targets::SPIRV;
use librashader::reflect::{CompileShader, ReflectShader, ShaderCompilerOutput, ShaderReflection};
@ -21,7 +21,7 @@ pub(crate) struct LookupTexture {
pub(crate) struct PassReflection {
reflection: ShaderReflection,
config: PassConfig,
config: ShaderPassConfig,
spirv: ShaderCompilerOutput<Vec<u32>>,
}
pub(crate) struct FilterReflection {
@ -35,7 +35,7 @@ impl FilterReflection {
preset: ShaderPreset,
direction: UVDirection,
) -> Result<FilterReflection, error::LibrashaderError> {
let (passes, textures) = (preset.passes, preset.textures);
let (passes, textures) = (preset.shaders, preset.textures);
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
Glslang,

View file

@ -15,8 +15,18 @@ use windows::Win32::Graphics::Direct3D11::{
};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::d3d11::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
use librashader::runtime::{FilterChainParameters, Viewport};
/// Direct3D 11 parameters for the source image.
#[repr(C)]
pub struct libra_source_image_d3d11_t {
/// A shader resource view into the source image
pub handle: ManuallyDrop<ID3D11ShaderResourceView>,
/// This is currently ignored.
pub width: u32,
/// This is currently ignored.
pub height: u32,
}
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
@ -49,7 +59,7 @@ pub struct frame_d3d11_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -179,26 +189,10 @@ const _: () = assert!(
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `device_context` is the ID3D11DeviceContext used to record draw commands to.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
/// the filter chain was created with.
///
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `ID3D11ShaderResourceView` that will serve as the source image for the frame.
/// - `out` is a pointer to a `ID3D11RenderTargetView` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
/// If `device_context` is null, then commands are recorded onto the immediate context. Otherwise,
/// it will record commands onto the provided context. If the context is deferred, librashader
/// will not finalize command lists. The context must otherwise be associated with the `ID3D11Device`
// the filter chain was created with.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
@ -220,9 +214,9 @@ extern_fn! {
// so ManuallyDrop<Option<ID3D11DeviceContext>> doesn't generate correct bindings.
device_context: Option<ManuallyDrop<ID3D11DeviceContext>>,
frame_count: usize,
image: ManuallyDrop<ID3D11ShaderResourceView>,
image: libra_source_image_d3d11_t,
viewport: libra_viewport_t,
out: ManuallyDrop<ID3D11RenderTargetView>,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d11_opt_t>
) mut |chain| {
@ -240,27 +234,17 @@ extern_fn! {
Some(unsafe { options.read() })
};
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(out.deref(), mvp)
.map_err(|e| LibrashaderError::D3D11FilterError(FilterChainError::Direct3DError(e)))?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: out.deref(),
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
let 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(device_context.as_deref(), image.deref(), &viewport, frame_count, options.as_ref())?;
chain.frame(device_context.as_deref(), image.handle.deref(), &viewport, frame_count, options.as_ref())?;
}
}
}
@ -276,15 +260,15 @@ extern_fn! {
chain: *mut libra_d3d11_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -298,18 +282,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d11_filter_chain_get_param(
chain: *const libra_d3d11_filter_chain_t,
chain: *mut libra_d3d11_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -325,9 +309,9 @@ extern_fn! {
fn libra_d3d11_filter_chain_set_active_pass_count(
chain: *mut libra_d3d11_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -337,12 +321,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
fn libra_d3d11_filter_chain_get_active_pass_count(
chain: *const libra_d3d11_filter_chain_t,
chain: *mut libra_d3d11_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let value = chain.parameters().passes_enabled();
let value = chain.get_enabled_pass_count();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -19,47 +19,19 @@ use librashader::runtime::d3d12::{
};
use librashader::runtime::{FilterChainParameters, Size, Viewport};
/// Tagged union for a Direct3D 12 image
#[repr(C)]
pub struct libra_image_d3d12_t {
/// The type of the image.
pub image_type: LIBRA_D3D12_IMAGE_TYPE,
/// The handle to the image.
pub handle: libra_image_d3d12_handle_t,
}
/// A handle to a Direct3D 12 image.
///
/// This must be either a pointer to a `ID3D12Resource`,
/// or a valid source or output image type.
#[repr(C)]
pub union libra_image_d3d12_handle_t {
/// A pointer to an `ID3D12Resource`, with descriptors managed by the filter chain.
pub resource: ManuallyDrop<ID3D12Resource>,
/// A source image with externally managed descriptors.
pub source: ManuallyDrop<libra_source_image_d3d12_t>,
/// An output image with externally managed descriptors.
pub output: ManuallyDrop<libra_output_image_d3d12_t>,
}
/// The type of image passed to the image.
#[repr(i32)]
pub enum LIBRA_D3D12_IMAGE_TYPE {
/// The image handle is a pointer to a `ID3D12Resource`.
RESOURCE = 0,
/// The image handle is a `libra_source_image_d3d12_t`
SOURCE_IMAGE = 1,
/// The image handle is a `libra_output_image_d3d12_t`
OUTPUT_IMAGE = 2,
}
/// Direct3D 12 parameters for the source image.
#[repr(C)]
pub struct libra_source_image_d3d12_t {
/// A CPU descriptor handle to a shader resource view of the image.
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
/// The resource containing the image.
pub resource: ManuallyDrop<ID3D12Resource>,
/// A CPU descriptor handle to a shader resource view of the image.
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
/// This is currently ignored.
pub format: DXGI_FORMAT,
/// This is currently ignored.
pub width: u32,
/// This is currently ignored.
pub height: u32,
}
/// Direct3D 12 parameters for the output image.
@ -69,10 +41,6 @@ pub struct libra_output_image_d3d12_t {
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
/// The format of the image.
pub format: DXGI_FORMAT,
/// The width of the output image.
pub width: u32,
/// The height of the output image.
pub height: u32,
}
/// Options for each Direct3D 12 shader frame.
@ -86,7 +54,7 @@ pub struct frame_d3d12_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -127,6 +95,19 @@ config_struct! {
}
}
impl TryFrom<libra_source_image_d3d12_t> for D3D12InputImage {
type Error = LibrashaderError;
fn try_from(value: libra_source_image_d3d12_t) -> Result<Self, Self::Error> {
let resource = value.resource.clone();
Ok(D3D12InputImage {
resource: ManuallyDrop::into_inner(resource),
descriptor: value.descriptor,
})
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
@ -229,39 +210,13 @@ extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command list.
///
/// A resource barrier **will not** be created for the final pass. The output image will
/// * The input image must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state.
/// * The output image must be in `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state.
///
/// librashader **will not** create a resource barrier for the final pass. The output image will
/// remain in `D3D12_RESOURCE_STATE_RENDER_TARGET` after all shader passes. The caller must transition
/// the output image to the final resource state.
///
/// The refcount of any COM pointers passed into this frame will not be changed. It is the responsibility
/// of the caller to ensure any resources remain alive until the `ID3D12GraphicsCommandList` provided is
/// submitted.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_list` is a `ID3D12GraphicsCommandList` to record draw commands to.
/// The provided command list must be open and associated with the `ID3D12Device` this filter chain was created with.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_d3d12_t` with `image_type` set to `IMAGE_TYPE_SOURCE_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` either a `ID3D12Resource*` or an `libra_source_image_d3d12_t` containing a CPU descriptor to a shader resource view,
/// and the image resource the view is of, which will serve as the source image for the frame.
/// The input image resource must be in the `D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE` resource state
/// or equivalent barrier layout. The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
/// - `out` is a `libra_image_d3d12_t`, with `image_type` set to `IMAGE_TYPE_OUTPUT_IMAGE` or `IMAGE_TYPE_RESOURCE`,
/// with `image_handle` being either a `ID3D12Resource*` or an `libra_output_image_d3d12_t`, containing a CPU descriptor handle,
/// format, and size information for the render target of the frame. The output image must be in
/// `D3D12_RESOURCE_STATE_RENDER_TARGET` resource state or equivalent barrier layout.
/// The image resource must have dimension `D3D12_RESOURCE_DIMENSION_TEXTURE2D`.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -269,24 +224,19 @@ extern_fn! {
/// 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_d3d12_opt_t`
/// struct.
/// - Any resource pointers contained within a `libra_image_d3d12_t` must be non-null.
/// - The `handle` field of any `libra_image_d3d12_t` must be valid for it's `image_type`.
/// - If `image_type` is `IMAGE_TYPE_RESOURCE`, then `handle` must be `ID3D12Resource *`.
/// - If `image_type` is `IMAGE_TYPE_SOURCE`, then `handle` must be `libra_source_image_d3d12_t`.
/// - If `image_type` is `IMAGE_TYPE_OUTPUT`, then `handle` must be `libra_output_image_d3d12_t`.
/// - `out` must be a descriptor handle to a render target view.
/// - `image.resource` must not be null.
/// - `command_list` must be a non-null pointer to a `ID3D12GraphicsCommandList` that is open,
/// and must be associated with the `ID3D12Device` this filter chain was created with.
/// - All resource pointers contained within a `libra_image_d3d12_t` must remain valid until the `ID3D12GraphicsCommandList *`
/// provided is submitted after the call to this function.
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
/// thread at a time may call this function.
nopanic fn libra_d3d12_filter_chain_frame(
chain: *mut libra_d3d12_filter_chain_t,
command_list: ManuallyDrop<ID3D12GraphicsCommandList>,
frame_count: usize,
image: libra_image_d3d12_t,
out: libra_image_d3d12_t,
viewport: *const libra_viewport_t,
image: libra_source_image_d3d12_t,
viewport: libra_viewport_t,
out: libra_output_image_d3d12_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d12_opt_t>
) mut |chain| {
@ -305,65 +255,14 @@ extern_fn! {
};
let options = options.map(FromUninit::from_uninit);
let output = unsafe {
match out.image_type {
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
let out = out.handle.resource;
D3D12OutputView::new_from_resource(
out,
chain,
)?
}
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
let out = out.handle.output;
D3D12OutputView::new_from_raw(
out.descriptor,
Size::new(out.width, out.height),
out.format,
)
}
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
return Err(LibrashaderError::InvalidParameter("out"))
}
}
};
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
};
let image = unsafe {
match image.image_type {
LIBRA_D3D12_IMAGE_TYPE::RESOURCE => {
let image = image.handle.resource;
D3D12InputImage::Managed(image)
}
LIBRA_D3D12_IMAGE_TYPE::SOURCE_IMAGE => {
let image = ManuallyDrop::into_inner(image.handle.source);
D3D12InputImage::External {
resource: image.resource,
descriptor: image.descriptor,
}
}
LIBRA_D3D12_IMAGE_TYPE::OUTPUT_IMAGE => {
return Err(LibrashaderError::InvalidParameter("image"))
}
}
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: unsafe { D3D12OutputView::new_from_raw(out.descriptor, Size::new(viewport.width, viewport.height), out.format) },
mvp,
};
let image = image.try_into()?;
unsafe {
chain.frame(&command_list, image, &viewport, frame_count, options.as_ref())?;
}
@ -381,15 +280,15 @@ extern_fn! {
chain: *mut libra_d3d12_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -403,18 +302,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d12_filter_chain_get_param(
chain: *const libra_d3d12_filter_chain_t,
chain: *mut libra_d3d12_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -430,9 +329,9 @@ extern_fn! {
fn libra_d3d12_filter_chain_set_active_pass_count(
chain: *mut libra_d3d12_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -442,12 +341,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
fn libra_d3d12_filter_chain_get_active_pass_count(
chain: *const libra_d3d12_filter_chain_t,
chain: *mut libra_d3d12_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let value = chain.parameters().passes_enabled();
let value = chain.get_enabled_pass_count();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -7,14 +7,12 @@ use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
use std::ffi::c_char;
use std::ffi::CStr;
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ops::Deref;
use std::ptr::NonNull;
use std::slice;
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::d3d9::error::FilterChainError;
use librashader::runtime::{FilterChainParameters, Size, Viewport};
use librashader::runtime::{FilterChainParameters, Viewport};
/// Options for Direct3D 11 filter chain creation.
#[repr(C)]
@ -47,7 +45,7 @@ pub struct frame_d3d9_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -110,24 +108,10 @@ extern_fn! {
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a pointer to a `IDirect3DTexture9` that will serve as the source image for the frame.
/// - `out` is a pointer to a `IDirect3DSurface9` that will serve as the render target for the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
/// - `viewport` may be null, or if it is not null, must be an aligned pointer to an instance of `libra_viewport_t`.
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
/// values for the model view projection matrix.
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
@ -140,8 +124,8 @@ extern_fn! {
chain: *mut libra_d3d9_filter_chain_t,
frame_count: usize,
image: ManuallyDrop<IDirect3DTexture9>,
viewport: libra_viewport_t,
out: ManuallyDrop<IDirect3DSurface9>,
viewport: *const libra_viewport_t,
mvp: *const f32,
options: *const MaybeUninit<frame_d3d9_opt_t>
) mut |chain| {
@ -159,28 +143,18 @@ extern_fn! {
Some(unsafe { options.read() })
};
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(out.deref(), mvp)
.map_err(|e| LibrashaderError::D3D9FilterError(FilterChainError::Direct3DError(e)))?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: out.deref(),
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
let 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(image.deref(), &viewport, frame_count, options.as_ref())?;
chain.frame(ManuallyDrop::into_inner(image.clone()), &viewport, frame_count, options.as_ref())?;
}
}
}
@ -196,15 +170,15 @@ extern_fn! {
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -218,18 +192,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_d3d9_filter_chain_get_param(
chain: *const libra_d3d9_filter_chain_t,
chain: *mut libra_d3d9_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -245,9 +219,9 @@ extern_fn! {
fn libra_d3d9_filter_chain_set_active_pass_count(
chain: *mut libra_d3d9_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -257,12 +231,12 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
fn libra_d3d9_filter_chain_get_active_pass_count(
chain: *const libra_d3d9_filter_chain_t,
chain: *mut libra_d3d9_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let value = chain.parameters().passes_enabled();
let value = chain.get_enabled_pass_count();
out.write(MaybeUninit::new(value as u32))
}
}

View file

@ -3,42 +3,50 @@ use crate::ctypes::{
};
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
use crate::ffi::extern_fn;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use librashader::runtime::gl::{
FilterChain, FilterChainOptions, FrameOptions, GLFramebuffer, GLImage,
};
use std::ffi::CStr;
use std::ffi::{c_char, c_void};
use std::ffi::{c_char, c_void, CString};
use std::mem::MaybeUninit;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::slice;
use std::sync::Arc;
use crate::LIBRASHADER_API_VERSION;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
/// A GL function loader that librashader needs to be initialized with.
pub type libra_gl_loader_t = unsafe extern "system" fn(*const c_char) -> *const c_void;
/// OpenGL parameters for an image.
/// OpenGL parameters for the source image.
#[repr(C)]
pub struct libra_image_gl_t {
/// A texture GLuint to the texture.
pub struct libra_source_image_gl_t {
/// A texture GLuint to the source image.
pub handle: u32,
/// The format of the texture.
/// The format of the source image.
pub format: u32,
/// The width of the texture.
/// The width of the source image.
pub width: u32,
/// The height of the texture.
/// The height of the source image.
pub height: u32,
}
impl From<libra_image_gl_t> for GLImage {
fn from(value: libra_image_gl_t) -> Self {
let handle = NonZeroU32::try_from(value.handle)
.ok()
.map(glow::NativeTexture);
/// OpenGL parameters for the output framebuffer.
#[repr(C)]
pub struct libra_output_framebuffer_gl_t {
/// A framebuffer GLuint to the output framebuffer.
pub fbo: u32,
/// A texture GLuint to the logical buffer of the output framebuffer.
pub texture: u32,
/// The format of the output framebuffer.
pub format: u32,
}
impl From<libra_source_image_gl_t> for GLImage {
fn from(value: libra_source_image_gl_t) -> Self {
GLImage {
handle,
handle: value.handle,
format: value.format,
size: Size::new(value.width, value.height),
}
@ -56,7 +64,7 @@ pub struct frame_gl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -96,6 +104,27 @@ config_struct! {
}
}
extern_fn! {
/// Initialize the OpenGL Context for librashader.
///
/// This only has to be done once throughout the lifetime of the application,
/// unless for whatever reason you switch OpenGL loaders mid-flight.
///
/// ## Safety
/// Attempting to create a filter chain will fail if the GL context is not initialized.
///
/// Reinitializing the OpenGL context with a different loader immediately invalidates previous filter
/// chain objects, and drawing with them causes immediate undefined behaviour.
raw fn libra_gl_init_context(loader: libra_gl_loader_t) {
gl::load_with(|s| unsafe {
let proc_name = CString::new(s).unwrap_unchecked();
loader(proc_name.as_ptr())
});
LibrashaderError::ok()
}
}
extern_fn! {
/// Create the filter chain given the shader preset.
///
@ -108,7 +137,6 @@ extern_fn! {
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
fn libra_gl_filter_chain_create(
preset: *mut libra_shader_preset_t,
loader: libra_gl_loader_t,
options: *const MaybeUninit<filter_chain_gl_opt_t>,
out: *mut MaybeUninit<libra_gl_filter_chain_t>
) {
@ -128,11 +156,7 @@ extern_fn! {
let options = options.map(FromUninit::from_uninit);
unsafe {
let context = glow::Context::from_loader_function_cstr(
|proc_name| loader(proc_name.as_ptr()));
let chain = FilterChain::load_from_preset(*preset,
Arc::new(context), options.as_ref())?;
let chain = FilterChain::load_from_preset(*preset, options.as_ref())?;
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
chain,
@ -144,23 +168,6 @@ extern_fn! {
extern_fn! {
/// Draw a frame with the given parameters for the given filter chain.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_gl_t`, containing the name of a Texture, format, and size information to
/// to an image that will serve as the source image for the frame.
/// - `out` is a `libra_output_framebuffer_gl_t`, containing the name of a Framebuffer, the name of a Texture, format,
/// and size information for the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
/// function will return an error.
@ -175,16 +182,14 @@ extern_fn! {
nopanic fn libra_gl_filter_chain_frame(
chain: *mut libra_gl_filter_chain_t,
frame_count: usize,
image: libra_image_gl_t,
out: libra_image_gl_t,
viewport: *const libra_viewport_t,
image: libra_source_image_gl_t,
viewport: libra_viewport_t,
out: libra_output_framebuffer_gl_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_gl_opt_t>,
) mut |chain| {
assert_some_ptr!(mut chain);
let image: GLImage = image.into();
let out: GLImage = out.into();
let mvp = if mvp.is_null() {
None
} else {
@ -197,21 +202,12 @@ extern_fn! {
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(&out, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output: &out,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
let framebuffer = GLFramebuffer::new_from_raw(out.texture, out.fbo, out.format, Size::new(viewport.width, viewport.height), 1);
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output: &framebuffer,
mvp,
};
unsafe {
@ -231,15 +227,15 @@ extern_fn! {
chain: *mut libra_gl_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -253,18 +249,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_gl_filter_chain_get_param(
chain: *const libra_gl_filter_chain_t,
chain: *mut libra_gl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -280,9 +276,9 @@ extern_fn! {
fn libra_gl_filter_chain_set_active_pass_count(
chain: *mut libra_gl_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -292,11 +288,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
fn libra_gl_filter_chain_get_active_pass_count(
chain: *const libra_gl_filter_chain_t,
chain: *mut libra_gl_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
unsafe {
out.write(MaybeUninit::new(value as u32))
}
@ -309,7 +305,6 @@ extern_fn! {
/// The resulting value in `chain` then becomes null.
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
/// - The context that the filter chain was initialized with **must be current** before freeing the filter chain.
fn libra_gl_filter_chain_free(
chain: *mut libra_gl_filter_chain_t
) {

View file

@ -1,46 +1,34 @@
//! librashader runtime C APIs.
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-opengl")))]
#[doc(cfg(feature = "runtime-opengl"))]
#[cfg(feature = "runtime-opengl")]
pub mod gl;
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "runtime-vulkan")))]
#[doc(cfg(feature = "runtime-vulkan"))]
#[cfg(feature = "runtime-vulkan")]
pub mod vk;
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d11")
))]
pub mod d3d11;
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d9")
))]
pub mod d3d9;
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))
)]
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(target_os = "windows", feature = "runtime-d3d12")
))]
pub mod d3d12;
#[cfg_attr(
feature = "docsrs",
doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))
)]
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
#[cfg(any(
feature = "__cbindgen_internal",
all(

View file

@ -38,7 +38,7 @@ pub struct frame_mtl_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -173,22 +173,6 @@ extern_fn! {
extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `MTLCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for encoding and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `id<MTLTexture>` that will serve as the source image for the frame.
/// - `out` is a `id<MTLTexture>` that is the render target of the frame.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
@ -205,8 +189,8 @@ extern_fn! {
command_buffer: PMTLCommandBuffer,
frame_count: usize,
image: PMTLTexture,
viewport: libra_viewport_t,
output: PMTLTexture,
viewport: *const libra_viewport_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_mtl_opt_t>
) |command_buffer, image, output|; mut |chain| {
@ -223,21 +207,11 @@ extern_fn! {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output,
mvp,
};
chain.frame(&image, &viewport, command_buffer, frame_count, opt.as_ref())?;
@ -255,14 +229,14 @@ extern_fn! {
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
if chain.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -276,18 +250,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_mtl_filter_chain_get_param(
chain: *const libra_mtl_filter_chain_t,
chain: *mut libra_mtl_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) mut |chain| {
assert_some_ptr!(mut chain);
unsafe {
let name = CStr::from_ptr(param_name);
let name = name.to_str()?;
let Some(value) = chain.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -303,9 +277,9 @@ extern_fn! {
fn libra_mtl_filter_chain_set_active_pass_count(
chain: *mut libra_mtl_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -315,11 +289,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
fn libra_mtl_filter_chain_get_active_pass_count(
chain: *const libra_mtl_filter_chain_t,
chain: *mut libra_mtl_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
unsafe {
out.write(MaybeUninit::new(value as u32))
}

View file

@ -6,8 +6,8 @@ use crate::ffi::extern_fn;
use librashader::runtime::vk::{
FilterChain, FilterChainOptions, FrameOptions, VulkanImage, VulkanInstance,
};
use std::ffi::c_char;
use std::ffi::CStr;
use std::ffi::{c_char, c_void};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
@ -15,26 +15,37 @@ use std::slice;
use librashader::runtime::FilterChainParameters;
use librashader::runtime::{Size, Viewport};
use crate::LIBRASHADER_API_VERSION;
use ash::vk;
use ash::vk::Handle;
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
use crate::LIBRASHADER_API_VERSION;
pub use ash::vk::PFN_vkGetInstanceProcAddr;
/// Vulkan parameters for an image.
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
pub type libra_PFN_vkGetInstanceProcAddr =
unsafe extern "system" fn(instance: *mut c_void, p_name: *const c_char);
/// Vulkan parameters for the source image.
#[repr(C)]
pub struct libra_image_vk_t {
/// A raw `VkImage` handle.
pub struct libra_source_image_vk_t {
/// A raw `VkImage` handle to the source image.
pub handle: vk::Image,
/// The `VkFormat` of the `VkImage`.
/// The `VkFormat` of the source image.
pub format: vk::Format,
/// The width of the `VkImage`.
/// The width of the source image.
pub width: u32,
/// The height of the `VkImage`.
/// The height of the source image.
pub height: u32,
}
/// Vulkan parameters for the output image.
#[repr(C)]
pub struct libra_output_image_vk_t {
/// A raw `VkImage` handle to the output image.
pub handle: vk::Image,
/// The `VkFormat` of the output image.
pub format: vk::Format,
}
/// Handles required to instantiate vulkan
#[repr(C)]
pub struct libra_device_vk_t {
@ -47,15 +58,12 @@ pub struct libra_device_vk_t {
/// A raw `VkDevice` handle
/// for the device attached to the instance that will perform rendering.
pub device: vk::Device,
/// The queue to use, if this is `NULL`, then
/// a suitable queue will be chosen. This must be a graphics queue.
pub queue: vk::Queue,
/// The entry loader for the Vulkan library.
pub entry: Option<vk::PFN_vkGetInstanceProcAddr>,
pub entry: vk::PFN_vkGetInstanceProcAddr,
}
impl From<libra_image_vk_t> for VulkanImage {
fn from(value: libra_image_vk_t) -> Self {
impl From<libra_source_image_vk_t> for VulkanImage {
fn from(value: libra_source_image_vk_t) -> Self {
VulkanImage {
size: Size::new(value.width, value.height),
image: value.handle,
@ -66,18 +74,11 @@ impl From<libra_image_vk_t> for VulkanImage {
impl From<libra_device_vk_t> for VulkanInstance {
fn from(value: libra_device_vk_t) -> Self {
let queue = if value.queue.is_null() {
None
} else {
Some(value.queue)
};
VulkanInstance {
device: value.device,
instance: value.instance,
physical_device: value.physical_device,
get_instance_proc_addr: value.entry,
queue,
}
}
}
@ -93,7 +94,7 @@ pub struct frame_vk_opt_t {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
/// 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,
@ -235,31 +236,13 @@ extern_fn! {
/// Records rendering commands for a frame with the given parameters for the given filter chain
/// to the input command buffer.
///
/// A pipeline barrier **will not** be created for the final pass. The output image must be
/// in `VK_COLOR_ATTACHMENT_OPTIMAL`, and will remain so after all shader passes. The caller must transition
/// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
///
/// librashader **will not** create a pipeline barrier for the final pass. The output image will
/// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition
/// the output image to the final layout.
///
/// ## Parameters
///
/// - `chain` is a handle to the filter chain.
/// - `command_buffer` is a `VkCommandBuffer` handle to record draw commands to.
/// The provided command buffer must be ready for recording and contain no prior commands
/// - `frame_count` is the number of frames passed to the shader
/// - `image` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// to an image that will serve as the source image for the frame. The input image must be in
/// the `VK_SHADER_READ_ONLY_OPTIMAL` layout.
/// - `out` is a `libra_image_vk_t`, containing a `VkImage` handle, it's format and size information,
/// for the render target of the frame. The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL` layout.
/// The output image will remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes.
///
/// - `viewport` is a pointer to a `libra_viewport_t` that specifies the area onto which scissor and viewport
/// will be applied to the render target. It may be null, in which case a default viewport spanning the
/// entire render target will be used.
/// - `mvp` is a pointer to an array of 16 `float` values to specify the model view projection matrix to
/// be passed to the shader.
/// - `options` is a pointer to options for the frame. Valid options are dependent on the `LIBRASHADER_API_VERSION`
/// passed in. It may be null, in which case default options for the filter chain are used.
///
/// ## Safety
/// - `libra_vk_filter_chain_frame` **must not be called within a RenderPass**.
/// - `command_buffer` must be a valid handle to a `VkCommandBuffer` that is ready for recording.
@ -275,9 +258,9 @@ extern_fn! {
chain: *mut libra_vk_filter_chain_t,
command_buffer: vk::CommandBuffer,
frame_count: usize,
image: libra_image_vk_t,
out: libra_image_vk_t,
viewport: *const libra_viewport_t,
image: libra_source_image_vk_t,
viewport: libra_viewport_t,
out: libra_output_image_vk_t,
mvp: *const f32,
opt: *const MaybeUninit<frame_vk_opt_t>
) mut |chain| {
@ -285,7 +268,7 @@ extern_fn! {
let image: VulkanImage = image.into();
let output = VulkanImage {
image: out.handle,
size: Size::new(out.width, out.height),
size: Size::new(viewport.width, viewport.height),
format: out.format
};
let mvp = if mvp.is_null() {
@ -299,21 +282,11 @@ extern_fn! {
Some(unsafe { opt.read() })
};
let opt = opt.map(FromUninit::from_uninit);
let viewport = if viewport.is_null() {
Viewport::new_render_target_sized_origin(output, mvp)?
} else {
let viewport = unsafe { viewport.read() };
Viewport {
x: viewport.x,
y: viewport.y,
output,
size: Size {
height: viewport.height,
width: viewport.width
},
mvp,
}
let viewport = Viewport {
x: viewport.x,
y: viewport.y,
output,
mvp,
};
unsafe {
@ -333,15 +306,15 @@ extern_fn! {
chain: *mut libra_vk_filter_chain_t,
param_name: *const c_char,
value: f32
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().set_parameter_value(name, value).is_none() {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
if chain.set_parameter(name, value).is_none() {
return LibrashaderError::UnknownShaderParameter(param_name).export()
}
}
}
@ -355,18 +328,18 @@ extern_fn! {
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
/// - `param_name` must be either null or a null terminated string.
fn libra_vk_filter_chain_get_param(
chain: *const libra_vk_filter_chain_t,
chain: *mut libra_vk_filter_chain_t,
param_name: *const c_char,
out: *mut MaybeUninit<f32>
) |chain| {
assert_some_ptr!(chain);
) 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.parameters().parameter_value(name) else {
return Err(LibrashaderError::UnknownShaderParameter(param_name))
let Some(value) = chain.get_parameter(name) else {
return LibrashaderError::UnknownShaderParameter(param_name).export()
};
out.write(MaybeUninit::new(value));
@ -382,9 +355,9 @@ extern_fn! {
fn libra_vk_filter_chain_set_active_pass_count(
chain: *mut libra_vk_filter_chain_t,
value: u32
) |chain| {
assert_some_ptr!(chain);
chain.parameters().set_passes_enabled(value as usize);
) mut |chain| {
assert_some_ptr!(mut chain);
chain.set_enabled_pass_count(value as usize);
}
}
@ -394,11 +367,11 @@ extern_fn! {
/// ## Safety
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
fn libra_vk_filter_chain_get_active_pass_count(
chain: *const libra_vk_filter_chain_t,
chain: *mut libra_vk_filter_chain_t,
out: *mut MaybeUninit<u32>
) |chain| {
assert_some_ptr!(chain);
let value = chain.parameters().passes_enabled();
) mut |chain| {
assert_some_ptr!(mut chain);
let value = chain.get_enabled_pass_count();
unsafe {
out.write(MaybeUninit::new(value as u32))
}

View file

@ -2,7 +2,6 @@
/// API version type alias.
pub type LIBRASHADER_API_VERSION = usize;
/// ABI version type alias.
pub type LIBRASHADER_ABI_VERSION = usize;
/// The current version of the librashader API.
@ -28,17 +27,10 @@ pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 1;
/// ABI versions are not backwards compatible. It is not
/// valid to load a librashader C API instance for any ABI
/// version not equal to LIBRASHADER_CURRENT_ABI.
///
/// ## ABI Versions
/// - ABI version 0: null instance (unloaded)
/// - ABI version 1: 0.1.0
/// - ABI version 2: 0.5.0
/// - Reduced texture size information needed for some runtimes.
/// - Removed wrapper structs for Direct3D 11 SRV and RTV handles.
/// - Removed `gl_context_init`.
/// - Make viewport handling consistent across runtimes, which are now
/// span the output render target if omitted.
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 2;
pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 1;
/// Function pointer definition for libra_abi_version
pub type PFN_libra_instance_abi_version = extern "C" fn() -> LIBRASHADER_ABI_VERSION;

View file

@ -25,7 +25,7 @@ extern_fn! {
///
/// 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.
/// 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.
@ -144,8 +144,6 @@ extern_fn! {
/// - GLCore
/// - Direct3D11
/// - Direct3D12
/// - Metal
/// - Direct3D9 (HLSL)
///
/// This will also set the appropriate video driver extensions.
///

View file

@ -1,91 +0,0 @@
[package]
name = "librashader-cli"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
categories = ["emulators", "compilers", "graphics"]
keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[lib]
name = "librashader_test"
path = "src/lib.rs"
[[bin]]
name = "librashader-cli"
path = "src/cli/main.rs"
[dependencies]
librashader = { version = "0.5.1", path = "../librashader", features = [
"presets",
"preprocess",
"serde",
], default-features = false }
librashader-runtime = { version = "0.5.1", path = "../librashader-runtime" }
librashader-common = { version = "0.5.1", path = "../librashader-common" }
wgpu = { version = "22", default-features = false, optional = true }
wgpu-types = { version = "22", optional = true }
anyhow = "1.0.86"
image = { workspace = true }
gfx-maths = "0.2.8"
pollster = "0.3.0"
parking_lot = "0.12.3"
image-compare = "0.4.1"
gpu-allocator = "0.27.0"
bitvec = "1.0.1"
d3d12-descriptor-heap = { version = "0.2", optional = true }
glow = { workspace = true, optional = true }
glfw = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
clap = { workspace = true }
serde = "1.0"
serde_json = "1.0"
spq-spvasm = "0.1.4"
rmp-serde = "1.3.0"
[features]
default = ["full"]
full = ["vulkan", "opengl", "wgpu", "d3d9", "d3d11", "d3d12", "metal"]
vulkan = ["librashader/runtime-vk", "dep:ash"]
opengl = ["librashader/runtime-gl", "dep:glow", "dep:glfw"]
wgpu = ["librashader/runtime-wgpu", "dep:wgpu", "dep:wgpu-types"]
d3d11 = ["librashader/runtime-d3d11", "dep:windows"]
d3d12 = [
"librashader/runtime-d3d12",
"dep:windows",
"dep:d3d12-descriptor-heap",
]
d3d9 = ["librashader/runtime-d3d9", "dep:windows"]
metal = ["librashader/runtime-metal", "dep:objc2", "dep:objc2-metal"]
vulkan-debug = ["vulkan"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true
optional = true
features = [
"Win32_Foundation",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_UI",
]
[target.'cfg(target_vendor="apple")'.dependencies]
objc2-metal = { version = "0.2.0", features = ["all"], optional = true }
objc2 = { version = "0.5.0", features = ["apple"], optional = true }

View file

@ -1,804 +0,0 @@
use anyhow::anyhow;
use clap::{Parser, Subcommand};
use image::codecs::png::PngEncoder;
use librashader::presets::context::ContextItem;
use librashader::presets::{ShaderPreset, ShaderPresetPack, WildcardContext};
use librashader::reflect::cross::{GlslVersion, HlslShaderModel, MslVersion, SpirvCross};
use librashader::reflect::naga::{Naga, NagaLoweringOptions};
use librashader::reflect::semantics::ShaderSemantics;
use librashader::reflect::{CompileShader, FromCompilation, ReflectShader, SpirvCompilation};
use librashader::runtime::Size;
use librashader::{FastHashMap, ShortString};
use librashader_common::StorageType;
use librashader_runtime::parameters::RuntimeParameters;
use librashader_test::render::{CommonFrameOptions, RenderTest};
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
/// Helpers and utilities to reflect and debug 'slang' shaders and presets.
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Args, Debug)]
struct PresetArgs {
/// The path to the shader preset to load.
#[arg(short, long)]
preset: PathBuf,
/// Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR
/// wildcards are always added to the preset parsing context.
///
/// For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
#[arg(short, long, value_delimiter = ',', num_args = 1..)]
wildcards: Option<Vec<String>>,
}
#[derive(clap::Args, Debug)]
struct RenderArgs {
/// The frame to render.
///
/// The renderer will run up to the number of frames specified here
/// to ensure feedback and history.
#[arg(short, long, default_value_t = 0)]
frame: usize,
/// The dimensions of the image.
///
/// This is given in either explicit dimensions `WIDTHxHEIGHT`, or a
/// percentage of the input image in `SCALE%`.
#[arg(short, long)]
dimensions: Option<String>,
/// Parameters to pass to the shader preset, comma separated with equals signs.
///
/// For example, crt_gamma=2.5,halation_weight=0.001
#[arg(long, value_delimiter = ',', num_args = 1..)]
params: Option<Vec<String>>,
/// Set the number of passes enabled for the preset.
#[arg(long)]
passes_enabled: Option<usize>,
/// The path to the input image.
#[arg(short, long)]
image: PathBuf,
#[clap(flatten)]
options: Option<FrameOptionsArgs>,
}
impl From<FrameOptionsArgs> for CommonFrameOptions {
fn from(value: FrameOptionsArgs) -> Self {
Self {
clear_history: false,
frame_direction: value.frame_direction,
rotation: value.rotation,
total_subframes: value.total_subframes,
current_subframe: value.current_subframe,
}
}
}
#[derive(clap::Args, Debug)]
struct FrameOptionsArgs {
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
#[arg(long, default_value_t = 1, allow_hyphen_values = true)]
pub frame_direction: i32,
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
#[arg(long, default_value_t = 0)]
pub rotation: u32,
/// The total number of subframes ran. Default is 1.
#[arg(long, default_value_t = 1)]
pub total_subframes: u32,
/// The current sub frame. Default is 1.
#[arg(long, default_value_t = 1)]
pub current_subframe: u32,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Render a shader preset against an image
Render {
#[clap(flatten)]
preset: PresetArgs,
#[clap(flatten)]
render: RenderArgs,
/// The path to the output image
///
/// If `-`, writes the image in PNG format to stdout.
#[arg(short, long)]
out: PathBuf,
/// The runtime to use to render the shader preset.
#[arg(value_enum, short, long)]
runtime: Runtime,
},
/// Compare two runtimes and get a similarity score between the two
/// runtimes rendering the same frame
Compare {
#[clap(flatten)]
preset: PresetArgs,
#[clap(flatten)]
render: RenderArgs,
/// The runtime to compare against
#[arg(value_enum, short, long)]
left: Runtime,
/// The runtime to compare to
#[arg(value_enum, short, long)]
right: Runtime,
/// The path to write the similarity image.
///
/// If `-`, writes the image to stdout.
#[arg(short, long)]
out: Option<PathBuf>,
},
/// Parse a preset and get a JSON representation of the data.
Parse {
#[clap(flatten)]
preset: PresetArgs,
},
/// Create a serialized preset pack from a shader preset.
Pack {
#[clap(flatten)]
preset: PresetArgs,
/// The path to write the output
///
/// If `-`, writes the output to stdout
#[arg(short, long)]
out: PathBuf,
/// The file format to output.
#[arg(value_enum, short, long)]
format: PackFormat,
},
/// Get the raw GLSL output of a preprocessed shader.
Preprocess {
/// The path to the slang shader.
#[arg(short, long)]
shader: PathBuf,
/// The item to output.
///
/// `json` will print a JSON representation of the preprocessed shader.
#[arg(value_enum, short, long)]
output: PreprocessOutput,
},
/// Transpile a shader to the given format.
Transpile {
/// The path to the slang shader.
#[arg(short, long)]
shader: PathBuf,
/// The shader stage to output
#[arg(value_enum, short = 'o', long)]
stage: TranspileStage,
/// The output format.
#[arg(value_enum, short, long)]
format: TranspileFormat,
/// The version of the output format to parse as, if applicable
///
/// For GLSL, this should be an string corresponding to a GLSL version (e.g. '330', or '300es', or '300 es').
///
/// For HLSL, this is a shader model version as an integer (50), or a version in the format MAJ_MIN (5_0), or MAJ.MIN (5.0).
///
/// For MSL, this is the shader language version as an integer in format
/// <MMmmpp>(30100), or a version in the format MAJ_MIN (3_1), or MAJ.MIN (3.1).
///
/// For SPIR-V, if this is the string "raw-id", then shows raw ID values instead of friendly names.
#[arg(short, long)]
version: Option<String>,
},
/// Reflect the shader relative to a preset, giving information about semantics used in a slang shader.
Reflect {
#[clap(flatten)]
preset: PresetArgs,
/// The pass index to use.
#[arg(short, long)]
index: usize,
#[arg(value_enum, short, long, default_value = "cross")]
backend: ReflectionBackend,
},
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum PreprocessOutput {
#[clap(name = "fragment")]
Fragment,
#[clap(name = "vertex")]
Vertex,
#[clap(name = "params")]
Params,
#[clap(name = "passformat")]
Format,
#[clap(name = "json")]
Json,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum TranspileStage {
#[clap(name = "fragment")]
Fragment,
#[clap(name = "vertex")]
Vertex,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum TranspileFormat {
#[clap(name = "glsl")]
GLSL,
#[clap(name = "hlsl")]
HLSL,
#[clap(name = "wgsl")]
WGSL,
#[clap(name = "msl")]
MSL,
#[clap(name = "spirv")]
SPIRV,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum PackFormat {
#[clap(name = "json")]
JSON,
#[clap(name = "msgpack")]
MsgPack,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum Runtime {
#[cfg(feature = "opengl")]
#[clap(name = "opengl3")]
OpenGL3,
#[cfg(feature = "opengl")]
#[clap(name = "opengl4")]
OpenGL4,
#[cfg(feature = "vulkan")]
#[clap(name = "vulkan")]
Vulkan,
#[cfg(feature = "wgpu")]
#[clap(name = "wgpu")]
Wgpu,
#[cfg(all(windows, feature = "d3d9"))]
#[clap(name = "d3d9")]
Direct3D9,
#[cfg(all(windows, feature = "d3d11"))]
#[clap(name = "d3d11")]
Direct3D11,
#[cfg(all(windows, feature = "d3d12"))]
#[clap(name = "d3d12")]
Direct3D12,
#[cfg(all(target_vendor = "apple", feature = "metal"))]
#[clap(name = "metal")]
Metal,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum ReflectionBackend {
#[clap(name = "cross")]
SpirvCross,
#[clap(name = "naga")]
Naga,
}
macro_rules! get_runtime {
($rt:ident, $image:ident) => {
match $rt {
#[cfg(feature = "opengl")]
Runtime::OpenGL3 => &mut librashader_test::render::gl::OpenGl3::new($image.as_path())?,
#[cfg(feature = "opengl")]
Runtime::OpenGL4 => &mut librashader_test::render::gl::OpenGl4::new($image.as_path())?,
#[cfg(feature = "vulkan")]
Runtime::Vulkan => &mut librashader_test::render::vk::Vulkan::new($image.as_path())?,
#[cfg(feature = "wgpu")]
Runtime::Wgpu => &mut librashader_test::render::wgpu::Wgpu::new($image.as_path())?,
#[cfg(all(windows, feature = "d3d9"))]
Runtime::Direct3D9 => {
&mut librashader_test::render::d3d9::Direct3D9::new($image.as_path())?
}
#[cfg(all(windows, feature = "d3d11"))]
Runtime::Direct3D11 => {
&mut librashader_test::render::d3d11::Direct3D11::new($image.as_path())?
}
#[cfg(all(windows, feature = "d3d12"))]
Runtime::Direct3D12 => {
&mut librashader_test::render::d3d12::Direct3D12::new($image.as_path())?
}
#[cfg(all(target_vendor = "apple", feature = "metal"))]
Runtime::Metal => &mut librashader_test::render::mtl::Metal::new($image.as_path())?,
}
};
}
pub fn main() -> Result<(), anyhow::Error> {
let args = Args::parse();
match args.command {
Commands::Render {
preset,
render,
out,
runtime,
} => {
let PresetArgs { preset, wildcards } = preset;
let RenderArgs {
frame,
dimensions,
params,
passes_enabled,
image,
options,
} = render;
let test: &mut dyn RenderTest = get_runtime!(runtime, image);
let dimensions = parse_dimension(dimensions, test.image_size())?;
let preset = get_shader_preset(preset, wildcards)?;
let params = parse_params(params)?;
let image = test.render_with_preset_and_params(
preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
options.map(CommonFrameOptions::from),
)?;
if out.as_path() == Path::new("-") {
let out = std::io::stdout();
image.write_with_encoder(PngEncoder::new(out))?;
} else {
image.save(out)?;
}
}
Commands::Compare {
preset,
render,
left,
right,
out,
} => {
let PresetArgs { preset, wildcards } = preset;
let RenderArgs {
frame,
dimensions,
params,
passes_enabled,
image,
options,
} = render;
let left: &mut dyn RenderTest = get_runtime!(left, image);
let right: &mut dyn RenderTest = get_runtime!(right, image);
let dimensions = parse_dimension(dimensions, left.image_size())?;
let params = parse_params(params)?;
let left_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let left_image = left.render_with_preset_and_params(
left_preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
None,
)?;
let right_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let right_image = right.render_with_preset_and_params(
right_preset,
frame,
Some(dimensions),
Some(&|rp| set_params(rp, &params, passes_enabled)),
options.map(CommonFrameOptions::from),
)?;
let similarity = image_compare::rgba_hybrid_compare(&left_image, &right_image)?;
print!("{}", similarity.score);
if let Some(out) = out {
let image = similarity.image.to_color_map();
if out.as_path() == Path::new("-") {
let out = std::io::stdout();
image.write_with_encoder(PngEncoder::new(out))?;
} else {
image.save(out)?;
}
}
}
Commands::Parse { preset } => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let out = serde_json::to_string_pretty(&preset)?;
print!("{out:}");
}
Commands::Preprocess { shader, output } => {
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
match output {
PreprocessOutput::Fragment => print!("{}", source.fragment),
PreprocessOutput::Vertex => print!("{}", source.vertex),
PreprocessOutput::Params => {
print!("{}", serde_json::to_string_pretty(&source.parameters)?)
}
PreprocessOutput::Format => print!("{:?}", source.format),
PreprocessOutput::Json => print!("{}", serde_json::to_string_pretty(&source)?),
}
}
Commands::Transpile {
shader,
stage,
format,
version,
} => {
let source = librashader::preprocess::ShaderSource::load(&StorageType::Path(shader))?;
let compilation = SpirvCompilation::try_from(&source)?;
let output = match format {
TranspileFormat::GLSL => {
let mut compilation =
librashader::reflect::targets::GLSL::from_compilation(compilation)?;
compilation.validate()?;
let version = version
.map(|s| parse_glsl_version(&s))
.unwrap_or(Ok(GlslVersion::Glsl330))?;
let output = compilation.compile(version)?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::HLSL => {
let mut compilation =
librashader::reflect::targets::HLSL::from_compilation(compilation)?;
compilation.validate()?;
let shader_model = version
.map(|s| parse_hlsl_version(&s))
.unwrap_or(Ok(HlslShaderModel::ShaderModel5_0))?;
let output = compilation.compile(Some(shader_model))?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::WGSL => {
let mut compilation =
librashader::reflect::targets::WGSL::from_compilation(compilation)?;
compilation.validate()?;
let output = compilation.compile(NagaLoweringOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::MSL => {
let mut compilation =
<librashader::reflect::targets::MSL as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.validate()?;
let version = version
.map(|s| parse_msl_version(&s))
.unwrap_or(Ok(MslVersion::new(1, 2, 0)))?;
let output = compilation.compile(Some(version))?;
TranspileOutput {
vertex: output.vertex,
fragment: output.fragment,
}
}
TranspileFormat::SPIRV => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.validate()?;
let output = compilation.compile(None)?;
let raw = version.is_some_and(|s| s == "raw-id");
TranspileOutput {
vertex: spirv_to_dis(output.vertex, raw)?,
fragment: spirv_to_dis(output.fragment, raw)?,
}
}
};
let print = match stage {
TranspileStage::Fragment => output.fragment,
TranspileStage::Vertex => output.vertex,
};
print!("{print}")
}
Commands::Reflect {
preset,
index,
backend,
} => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let Some(shader) = preset.passes.get(index) else {
return Err(anyhow!("Invalid pass index for the preset"));
};
let source = librashader::preprocess::ShaderSource::load(&shader.storage)?;
let compilation = SpirvCompilation::try_from(&source)?;
let semantics =
ShaderSemantics::create_pass_semantics::<anyhow::Error>(&preset, index)?;
let reflection = match backend {
ReflectionBackend::SpirvCross => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
SpirvCross,
>>::from_compilation(compilation)?;
compilation.reflect(index, &semantics)?
}
ReflectionBackend::Naga => {
let mut compilation =
<librashader::reflect::targets::SPIRV as FromCompilation<
SpirvCompilation,
Naga,
>>::from_compilation(compilation)?;
compilation.reflect(index, &semantics)?
}
};
print!("{}", serde_json::to_string_pretty(&reflection)?);
}
Commands::Pack {
preset,
out,
format,
} => {
let PresetArgs { preset, wildcards } = preset;
let preset = get_shader_preset(preset, wildcards)?;
let preset = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset)?;
let output_bytes = match format {
PackFormat::JSON => serde_json::to_vec_pretty(&preset)?,
PackFormat::MsgPack => rmp_serde::to_vec(&preset)?,
};
if out.as_path() == Path::new("-") {
let mut out = std::io::stdout();
out.write_all(output_bytes.as_slice())?;
} else {
let mut file = File::create(out.as_path())?;
file.write_all(output_bytes.as_slice())?;
}
}
}
Ok(())
}
struct TranspileOutput {
vertex: String,
fragment: String,
}
fn get_shader_preset(
preset: PathBuf,
wildcards: Option<Vec<String>>,
) -> anyhow::Result<ShaderPreset> {
let mut context = WildcardContext::new();
context.add_path_defaults(preset.as_path());
if let Some(wildcards) = wildcards {
for string in wildcards {
let Some((left, right)) = string.split_once("=") else {
return Err(anyhow!("Encountered invalid context string {string}"));
};
context.append_item(ContextItem::ExternContext(
left.to_string(),
right.to_string(),
))
}
}
let preset = ShaderPreset::try_parse_with_context(preset, context)?;
Ok(preset)
}
fn parse_params(
assignments: Option<Vec<String>>,
) -> anyhow::Result<Option<FastHashMap<ShortString, f32>>> {
let Some(assignments) = assignments else {
return Ok(None);
};
let mut map = FastHashMap::default();
for string in assignments {
let Some((left, right)) = string.split_once("=") else {
return Err(anyhow!("Encountered invalid parameter string {string}"));
};
let value = right
.parse::<f32>()
.map_err(|_| anyhow!("Encountered invalid parameter value: {right}"))?;
map.insert(ShortString::from(left), value);
}
Ok(Some(map))
}
fn set_params(
params: &RuntimeParameters,
assignments: &Option<FastHashMap<ShortString, f32>>,
passes_enabled: Option<usize>,
) {
if let Some(passes_enabled) = passes_enabled {
params.set_passes_enabled(passes_enabled)
};
let Some(assignments) = assignments else {
return;
};
params.update_parameters(|params| {
for (key, param) in assignments {
params.insert(key.clone(), *param);
}
});
}
fn spirv_to_dis(spirv: Vec<u32>, raw: bool) -> anyhow::Result<String> {
let binary = spq_spvasm::SpirvBinary::from(spirv);
spq_spvasm::Disassembler::new()
.print_header(true)
.name_ids(!raw)
.name_type_ids(!raw)
.name_const_ids(!raw)
.indent(true)
.disassemble(&binary)
}
fn parse_glsl_version(version_str: &str) -> anyhow::Result<GlslVersion> {
if version_str.contains("es") {
let Some(version) = version_str.strip_suffix("es").map(|s| s.trim()) else {
return Err(anyhow!("Unknown GLSL version"));
};
Ok(match version {
"100" => GlslVersion::Glsl100Es,
"300" => GlslVersion::Glsl300Es,
"310" => GlslVersion::Glsl310Es,
"320" => GlslVersion::Glsl320Es,
_ => return Err(anyhow!("Unknown GLSL version")),
})
} else {
Ok(match version_str {
"100" => GlslVersion::Glsl100Es,
"110" => GlslVersion::Glsl110,
"120" => GlslVersion::Glsl120,
"130" => GlslVersion::Glsl130,
"140" => GlslVersion::Glsl140,
"150" => GlslVersion::Glsl150,
"300" => GlslVersion::Glsl300Es,
"330" => GlslVersion::Glsl330,
"310" => GlslVersion::Glsl310Es,
"320" => GlslVersion::Glsl320Es,
"400" => GlslVersion::Glsl400,
"410" => GlslVersion::Glsl410,
"420" => GlslVersion::Glsl420,
"430" => GlslVersion::Glsl430,
"440" => GlslVersion::Glsl440,
"450" => GlslVersion::Glsl450,
"460" => GlslVersion::Glsl460,
_ => return Err(anyhow!("Unknown GLSL version")),
})
}
}
fn version_to_usize(version_str: &str) -> anyhow::Result<usize> {
let version: &str = if version_str.contains("_") {
&version_str.replace("_", "")
} else if version_str.contains(".") {
&version_str.replace(".", "")
} else {
version_str
};
let version = version
.parse::<usize>()
.map_err(|_| anyhow!("Invalid version string"))?;
Ok(version)
}
fn parse_hlsl_version(version_str: &str) -> anyhow::Result<HlslShaderModel> {
let version = version_to_usize(version_str)?;
Ok(match version {
30 => HlslShaderModel::ShaderModel3_0,
40 => HlslShaderModel::ShaderModel4_0,
50 => HlslShaderModel::ShaderModel5_0,
51 => HlslShaderModel::ShaderModel5_1,
60 => HlslShaderModel::ShaderModel6_0,
61 => HlslShaderModel::ShaderModel6_1,
62 => HlslShaderModel::ShaderModel6_2,
63 => HlslShaderModel::ShaderModel6_3,
64 => HlslShaderModel::ShaderModel6_4,
65 => HlslShaderModel::ShaderModel6_5,
66 => HlslShaderModel::ShaderModel6_6,
67 => HlslShaderModel::ShaderModel6_7,
68 => HlslShaderModel::ShaderModel6_8,
_ => return Err(anyhow!("Unknown Shader Model")),
})
}
fn parse_msl_version(version_str: &str) -> anyhow::Result<MslVersion> {
let version = version_to_usize(version_str)?;
Ok(match version {
10 => MslVersion::new(1, 0, 0),
11 => MslVersion::new(1, 1, 0),
12 => MslVersion::new(1, 2, 0),
20 => MslVersion::new(2, 0, 0),
21 => MslVersion::new(2, 1, 0),
22 => MslVersion::new(2, 2, 0),
23 => MslVersion::new(2, 3, 0),
24 => MslVersion::new(2, 4, 0),
30 => MslVersion::new(3, 0, 0),
31 => MslVersion::new(3, 1, 0),
32 => MslVersion::new(3, 2, 0),
n if n >= 10000 => {
let major = n / 10000;
let minor = (n - (major * 10000)) / 100;
let patch = n - ((major * 10000) + (minor * 100));
MslVersion::new(major as u32, minor as u32, patch as u32)
}
_ => return Err(anyhow!("Unknown MSL version")),
})
}
fn parse_dimension(dimstr: Option<String>, image_dim: Size<u32>) -> anyhow::Result<Size<u32>> {
let Some(dimstr) = dimstr else {
return Ok(image_dim);
};
if dimstr.contains("x") {
if let Some((Ok(width), Ok(height))) = dimstr
.split_once("x")
.map(|(width, height)| (width.parse::<u32>(), height.parse::<u32>()))
{
if width < 1 || height < 1 {
return Err(anyhow!("Dimensions must be larger than 1x1"));
}
if width > 16384 || height > 16384 {
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
}
return Ok(Size::new(width, height));
}
}
if dimstr.ends_with("%") && dimstr.len() > 1 {
if let Ok(percent) = dimstr.trim_end_matches("%").parse::<u32>() {
let percent = percent as f32 / 100f32;
let width = (image_dim.width as f32 * percent) as u32;
let height = (image_dim.height as f32 * percent) as u32;
if width < 1 || height < 1 {
return Err(anyhow!("Dimensions must be larger than 1x1"));
}
if width > 16384 || height > 16384 {
return Err(anyhow!("Dimensions must not be larger than 16384x16384"));
}
return Ok(Size { width, height });
}
}
Err(anyhow!(
"Invalid dimension syntax, must either in form WIDTHxHEIGHT or SCALE%"
))
}

View file

@ -1,2 +0,0 @@
/// Render tests
pub mod render;

View file

@ -1,258 +0,0 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::runtime::d3d11::*;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use std::io::{Cursor, Write};
use std::ops::DerefMut;
use std::path::Path;
impl RenderTest for Direct3D11 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D11::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let output_size = output_size.unwrap_or(self.image_bytes.size);
let (renderbuffer, rtv) = self.create_renderbuffer(output_size)?;
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let viewport = Viewport::new_render_target_sized_origin(&rtv, None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
filter_chain.frame(None, &self.image_srv, &viewport, frame, options.as_ref())?;
}
let mut renderbuffer_desc = Default::default();
self.immediate_context.Flush();
renderbuffer.GetDesc(&mut renderbuffer_desc);
eprintln!("{:?}", renderbuffer_desc);
let mut staging = None;
self.device.CreateTexture2D(
&D3D11_TEXTURE2D_DESC {
MipLevels: 1,
BindFlags: 0,
MiscFlags: 0,
Usage: D3D11_USAGE_STAGING,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
..renderbuffer_desc
},
None,
Some(&mut staging),
)?;
let staging = staging.ok_or(anyhow!("Unable to create staging texture"))?;
self.immediate_context.CopyResource(&staging, &renderbuffer);
let mut pixels: Vec<u8> = Vec::new();
let mut map_info = Default::default();
self.immediate_context
.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut map_info))?;
let slice = std::slice::from_raw_parts(
map_info.pData as *const u8,
(renderbuffer_desc.Height * map_info.RowPitch) as usize,
);
pixels.resize(
(renderbuffer_desc.Height * renderbuffer_desc.Width * 4) as usize,
0,
);
let mut cursor = Cursor::new(pixels.deref_mut());
for chunk in slice.chunks(map_info.RowPitch as usize) {
cursor.write_all(&chunk[..(renderbuffer_desc.Width * 4) as usize])?
}
let image = RgbaImage::from_raw(output_size.width, output_size.height, pixels)
.ok_or(anyhow!("Unable to create image from data"))?;
self.immediate_context.Unmap(&staging, 0);
Ok(image)
}
}
}
use librashader::presets::ShaderPreset;
use librashader_runtime::image::{Image, UVDirection};
use windows::{
Win32::Foundation::*, Win32::Graphics::Direct3D::*, Win32::Graphics::Direct3D11::*,
Win32::Graphics::Dxgi::Common::*, Win32::Graphics::Dxgi::*,
};
pub struct Direct3D11 {
device: ID3D11Device,
immediate_context: ID3D11DeviceContext,
_image_tex: ID3D11Texture2D,
image_srv: ID3D11ShaderResourceView,
image_bytes: Image,
}
impl Direct3D11 {
fn create_device() -> anyhow::Result<(IDXGIFactory4, ID3D11Device, ID3D11DeviceContext)> {
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }?;
let feature_levels = vec![D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1];
let mut out_device = None;
let mut out_context = None;
let mut _out_feature_level = D3D_FEATURE_LEVEL_11_0;
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
D3D11_CREATE_DEVICE_DEBUG,
Some(&feature_levels),
D3D11_SDK_VERSION,
Some(&mut out_device),
Some(&mut _out_feature_level),
Some(&mut out_context),
)
}?;
Ok((dxgi_factory, out_device.unwrap(), out_context.unwrap()))
}
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let (_factory, device, imm_context) = Self::create_device()?;
let (image, image_tex, srv) = Self::load_image(&device, image_path)?;
Ok(Self {
device,
immediate_context: imm_context,
image_bytes: image,
_image_tex: image_tex,
image_srv: srv,
})
}
fn load_image(
device: &ID3D11Device,
image_path: &Path,
) -> anyhow::Result<(Image, ID3D11Texture2D, ID3D11ShaderResourceView)> {
let image = Image::load(image_path, UVDirection::TopLeft)?;
let desc = D3D11_TEXTURE2D_DESC {
Width: image.size.width,
Height: image.size.height,
// todo: set this to 0
MipLevels: 1,
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: 0,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
..Default::default()
};
unsafe {
let mut resource = None;
device.CreateTexture2D(
&desc,
Some(&D3D11_SUBRESOURCE_DATA {
pSysMem: image.bytes.as_ptr().cast(),
SysMemPitch: image.pitch as u32,
SysMemSlicePitch: 0,
}),
Some(&mut resource),
)?;
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
let mut srv = None;
device.CreateShaderResourceView(
&resource,
Some(&D3D11_SHADER_RESOURCE_VIEW_DESC {
Format: desc.Format,
ViewDimension: D3D_SRV_DIMENSION_TEXTURE2D,
Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D11_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: u32::MAX,
},
},
}),
Some(&mut srv),
)?;
let srv = srv.ok_or(anyhow!("Failed to create SRV"))?;
Ok((image, resource, srv))
}
}
fn create_renderbuffer(
&self,
size: Size<u32>,
) -> anyhow::Result<(ID3D11Texture2D, ID3D11RenderTargetView)> {
let desc = D3D11_TEXTURE2D_DESC {
Width: size.width,
Height: size.height,
// todo: set this to 0
MipLevels: 1,
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
CPUAccessFlags: 0,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
..Default::default()
};
unsafe {
let mut resource = None;
self.device
.CreateTexture2D(&desc, None, Some(&mut resource))?;
let resource = resource.ok_or(anyhow!("Failed to create texture"))?;
let mut rtv = None;
self.device
.CreateRenderTargetView(&resource, None, Some(&mut rtv))?;
let rtv = rtv.ok_or(anyhow!("Failed to create RTV"))?;
Ok((resource, rtv))
}
}
}

View file

@ -1,35 +0,0 @@
use d3d12_descriptor_heap::D3D12DescriptorHeapType;
use windows::Win32::Graphics::Direct3D12::{
D3D12_DESCRIPTOR_HEAP_DESC, D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
};
#[derive(Clone)]
pub struct CpuStagingHeap;
#[derive(Clone)]
pub struct RenderTargetHeap;
impl D3D12DescriptorHeapType for RenderTargetHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: size as u32,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
}
}
}
impl D3D12DescriptorHeapType for CpuStagingHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
NumDescriptors: size as u32,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
}
}
}

View file

@ -1,426 +0,0 @@
mod descriptor_heap;
mod util;
use crate::render::d3d12::descriptor_heap::{CpuStagingHeap, RenderTargetHeap};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use d3d12_descriptor_heap::{D3D12DescriptorHeap, D3D12DescriptorHeapSlot};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d12::{D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::core::Interface;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Graphics::Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_12_1};
use windows::Win32::Graphics::Direct3D12::{
D3D12CreateDevice, ID3D12CommandAllocator, ID3D12CommandQueue, ID3D12Device, ID3D12Fence,
ID3D12GraphicsCommandList, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT,
D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
D3D12_FENCE_FLAG_NONE, D3D12_HEAP_FLAG_NONE, D3D12_HEAP_PROPERTIES, D3D12_HEAP_TYPE_CUSTOM,
D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD, D3D12_MEMORY_POOL_L0,
D3D12_MEMORY_POOL_UNKNOWN, D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_DESC,
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_DIMENSION_TEXTURE2D,
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_SHADER_RESOURCE_VIEW_DESC, D3D12_SHADER_RESOURCE_VIEW_DESC_0,
D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA, D3D12_TEX2D_SRV,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::{
CreateDXGIFactory2, IDXGIAdapter1, IDXGIFactory4, DXGI_ADAPTER_FLAG_NONE,
DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_DEBUG,
};
use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject, INFINITE};
pub struct Direct3D12 {
device: ID3D12Device,
_cpu_heap: D3D12DescriptorHeap<CpuStagingHeap>,
rtv_heap: D3D12DescriptorHeap<RenderTargetHeap>,
texture: ID3D12Resource,
_heap_slot: D3D12DescriptorHeapSlot<CpuStagingHeap>,
command_pool: ID3D12CommandAllocator,
queue: ID3D12CommandQueue,
image: Image<BGRA8>,
}
impl RenderTest for Direct3D12 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D12::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let descriptor = self.rtv_heap.allocate_descriptor()?;
let cmd: ID3D12GraphicsCommandList = self.device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&self.command_pool,
None,
)?;
let fence_event = CreateEventA(None, false, false, None)?;
let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_hlsl_pipeline: false,
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let output_size = output_size.unwrap_or(self.image.size);
let mut output_texture = None;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: output_size.width as u64,
Height: output_size.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: Default::default(),
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
};
// We only need to draw one frame, so don't worry about the performance impact
// of this heap.
self.device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_CUSTOM,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_COMMON | D3D12_RESOURCE_STATE_RENDER_TARGET,
None,
&mut output_texture,
)?;
let output_texture: ID3D12Resource =
output_texture.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
self.device
.CreateRenderTargetView(&output_texture, None, *descriptor.as_ref());
let viewport = Viewport::new_render_target_sized_origin(
D3D12OutputView::new_from_raw(
*descriptor.as_ref(),
output_size,
DXGI_FORMAT_B8G8R8A8_UNORM,
),
None,
)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let image = self.texture.to_ref();
for frame in 0..=frame_count {
filter_chain.frame(&cmd, image.into(), &viewport, frame, options.as_ref())?;
}
cmd.Close()?;
self.queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
self.queue.Signal(&fence, 1)?;
if fence.GetCompletedValue() < 1 {
fence.SetEventOnCompletion(1, fence_event)?;
WaitForSingleObject(fence_event, INFINITE);
CloseHandle(fence_event)?;
};
let mut buffer = vec![0u8; (output_size.height * output_size.width) as usize * 4];
output_texture.ReadFromSubresource(
buffer.as_mut_ptr().cast(),
4 * output_size.width,
0,
0,
None,
)?;
BGRA8::convert(&mut buffer);
let image =
RgbaImage::from_raw(output_size.width, output_size.height, Vec::from(buffer))
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Direct3D12 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let device = Self::create_device()?;
let mut heap = unsafe { D3D12DescriptorHeap::new(&device, 8)? };
let rtv_heap = unsafe { D3D12DescriptorHeap::new(&device, 16)? };
unsafe {
let command_pool: ID3D12CommandAllocator =
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?;
let queue: ID3D12CommandQueue =
device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
Priority: 0,
Flags: D3D12_COMMAND_QUEUE_FLAG_NONE,
NodeMask: 0,
})?;
let (image, texture, heap_slot) =
Self::load_image(&device, &command_pool, &queue, &mut heap, image_path)?;
Ok(Self {
device,
_cpu_heap: heap,
rtv_heap,
texture,
_heap_slot: heap_slot,
command_pool,
image,
queue,
})
}
}
fn create_device() -> anyhow::Result<ID3D12Device> {
let dxgi_factory_flags = DXGI_CREATE_FACTORY_DEBUG;
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(dxgi_factory_flags) }?;
let adapter = Self::get_hardware_adapter(&dxgi_factory)?;
let mut device: Option<ID3D12Device> = None;
unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_12_1, &mut device) }?;
let device = device.ok_or(anyhow!("Failed to initialize D3D12 device"))?;
Ok(device)
}
fn load_image(
device: &ID3D12Device,
command_pool: &ID3D12CommandAllocator,
queue: &ID3D12CommandQueue,
heap: &mut D3D12DescriptorHeap<CpuStagingHeap>,
path: &Path,
) -> anyhow::Result<(
Image<BGRA8>,
ID3D12Resource,
D3D12DescriptorHeapSlot<CpuStagingHeap>,
)> {
// 1 time queue infrastructure for lut uploads
let image: Image<BGRA8> = Image::load(path, UVDirection::TopLeft)?;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: image.size.width as u64,
Height: image.size.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: Default::default(),
Flags: Default::default(),
};
let descriptor = heap.allocate_descriptor()?;
// create handles on GPU
let mut resource: Option<ID3D12Resource> = None;
unsafe {
let cmd: ID3D12GraphicsCommandList = device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&command_pool.clone(),
None,
)?;
let fence_event = CreateEventA(None, false, false, None)?;
let fence: ID3D12Fence = device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
None,
&mut resource,
)?;
let resource = resource.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: desc.Format,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MipLevels: u32::MAX,
..Default::default()
},
},
};
device.CreateShaderResourceView(&resource, Some(&srv_desc), *descriptor.as_ref());
let mut buffer_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
..Default::default()
};
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut total = 0;
// texture upload
device.GetCopyableFootprints(
&desc,
0,
1,
0,
Some(&mut layout),
None,
None,
Some(&mut total),
);
buffer_desc.Width = total;
buffer_desc.Height = 1;
buffer_desc.DepthOrArraySize = 1;
buffer_desc.MipLevels = 1;
buffer_desc.SampleDesc.Count = 1;
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
let mut upload: Option<ID3D12Resource> = None;
device.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 1,
VisibleNodeMask: 1,
},
D3D12_HEAP_FLAG_NONE,
&buffer_desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
None,
&mut upload,
)?;
let upload = upload.ok_or_else(|| anyhow!("Failed to allocate copy texture"))?;
let subresource = [D3D12_SUBRESOURCE_DATA {
pData: image.bytes.as_ptr().cast(),
RowPitch: 4 * image.size.width as isize,
SlicePitch: (4 * image.size.width * image.size.height) as isize,
}];
util::d3d12_resource_transition(
&cmd,
&resource,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
);
util::d3d12_update_subresources(&cmd, &resource, &upload, 0, 0, 1, &subresource)?;
util::d3d12_resource_transition(
&cmd,
&resource,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
cmd.Close()?;
queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
queue.Signal(&fence, 1)?;
if fence.GetCompletedValue() < 1 {
fence.SetEventOnCompletion(1, fence_event)?;
WaitForSingleObject(fence_event, INFINITE);
CloseHandle(fence_event)?;
}
Ok((image, resource, descriptor))
}
}
fn get_hardware_adapter(factory: &IDXGIFactory4) -> windows::core::Result<IDXGIAdapter1> {
for i in 0.. {
let adapter = unsafe { factory.EnumAdapters1(i)? };
let desc = unsafe { adapter.GetDesc1()? };
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != DXGI_ADAPTER_FLAG_NONE.0 as u32
{
// Don't select the Basic Render Driver adapter. If you want a
// software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see whether the adapter supports Direct3D 12, but don't
// create the actual device yet.
if unsafe {
D3D12CreateDevice(
&adapter,
D3D_FEATURE_LEVEL_11_0,
std::ptr::null_mut::<Option<ID3D12Device>>(),
)
}
.is_ok()
{
return Ok(adapter);
}
}
// Fallback to warp
unsafe { factory.EnumWarpAdapter() }
}
}

View file

@ -1,213 +0,0 @@
use anyhow::anyhow;
use std::mem::ManuallyDrop;
use windows::Win32::Graphics::Direct3D12::{
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_MEMCPY_DEST,
D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0,
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_BARRIER_FLAG_NONE,
D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_STATES,
D3D12_RESOURCE_TRANSITION_BARRIER, D3D12_SUBRESOURCE_DATA, D3D12_TEXTURE_COPY_LOCATION,
D3D12_TEXTURE_COPY_LOCATION_0, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
};
#[inline(always)]
pub fn d3d12_resource_transition(
cmd: &ID3D12GraphicsCommandList,
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
) {
d3d12_resource_transition_subresource(
cmd,
resource,
before,
after,
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
);
}
pub fn d3d12_get_resource_transition_subresource(
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
subresource: u32,
) -> D3D12_RESOURCE_BARRIER {
D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER {
pResource: ManuallyDrop::new(Some(resource.clone())),
Subresource: subresource,
StateBefore: before,
StateAfter: after,
}),
},
}
}
#[inline(always)]
pub fn d3d12_resource_transition_subresource(
cmd: &ID3D12GraphicsCommandList,
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
subresource: u32,
) {
let barrier = [d3d12_get_resource_transition_subresource(
resource,
before,
after,
subresource,
)];
unsafe { cmd.ResourceBarrier(&barrier) }
}
pub(crate) fn d3d12_update_subresources(
cmd: &ID3D12GraphicsCommandList,
destination_resource: &ID3D12Resource,
intermediate_resource: &ID3D12Resource,
intermediate_offset: u64,
first_subresouce: u32,
num_subresources: u32,
source: &[D3D12_SUBRESOURCE_DATA],
) -> anyhow::Result<u64> {
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
// + std::mem::size_of::<u32>()
// + std::mem::size_of::<u64>() * num_subresources;
unsafe {
let destination_desc = destination_resource.GetDesc();
let mut device: Option<ID3D12Device> = None;
destination_resource.GetDevice(&mut device)?;
let device = device.ok_or_else(|| anyhow!("Unable to get device"))?;
let mut layouts =
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
let mut num_rows = vec![0; num_subresources as usize];
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
let mut required_size = 0;
// texture upload
device.GetCopyableFootprints(
&destination_desc,
first_subresouce,
num_subresources,
intermediate_offset,
Some(layouts.as_mut_ptr()),
Some(num_rows.as_mut_ptr()),
Some(row_sizes_in_bytes.as_mut_ptr()),
Some(&mut required_size),
);
update_subresources(
cmd,
destination_resource,
intermediate_resource,
first_subresouce,
num_subresources,
required_size,
&layouts,
&num_rows,
&row_sizes_in_bytes,
source,
)
}
}
#[allow(clippy::too_many_arguments)]
fn update_subresources(
cmd: &ID3D12GraphicsCommandList,
destination_resource: &ID3D12Resource,
intermediate_resource: &ID3D12Resource,
first_subresouce: u32,
num_subresources: u32,
required_size: u64,
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
num_rows: &[u32],
row_sizes_in_bytes: &[u64],
source_data: &[D3D12_SUBRESOURCE_DATA],
) -> anyhow::Result<u64> {
// ToDo: implement validation as in the original function
unsafe {
let mut data = std::ptr::null_mut();
intermediate_resource.Map(0, None, Some(&mut data))?;
for i in 0..num_subresources as usize {
let dest_data = D3D12_MEMCPY_DEST {
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
RowPitch: layouts[i].Footprint.RowPitch as usize,
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
};
memcpy_subresource(
&dest_data,
&source_data[i],
row_sizes_in_bytes[i],
num_rows[i],
layouts[i].Footprint.Depth,
);
}
intermediate_resource.Unmap(0, None);
}
unsafe {
let destination_desc = destination_resource.GetDesc();
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
cmd.CopyBufferRegion(
destination_resource,
0,
intermediate_resource,
layouts[0].Offset,
layouts[0].Footprint.Width as u64,
);
} else {
for i in 0..num_subresources as usize {
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: i as u32 + first_subresouce,
},
};
let source_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layouts[i],
},
};
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
}
}
Ok(required_size)
}
}
// this function should not leak to the public API, so
// there is no point in using struct wrappers
unsafe fn memcpy_subresource(
dest: &D3D12_MEMCPY_DEST,
src: &D3D12_SUBRESOURCE_DATA,
row_sizes_in_bytes: u64,
num_rows: u32,
num_slices: u32,
) {
unsafe {
for z in 0..num_slices as usize {
let dest_slice = dest.pData.add(dest.SlicePitch * z);
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
for y in 0..num_rows as usize {
std::ptr::copy_nonoverlapping(
src_slice.offset(src.RowPitch * y as isize),
dest_slice.add(dest.RowPitch * y),
row_sizes_in_bytes as usize,
);
}
}
}
}

View file

@ -1,193 +0,0 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::Win32::Foundation::{HWND, TRUE};
use windows::Win32::Graphics::Direct3D9::{
Direct3DCreate9, IDirect3D9, IDirect3DDevice9, IDirect3DTexture9, D3DADAPTER_DEFAULT,
D3DCREATE_HARDWARE_VERTEXPROCESSING, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DLOCKED_RECT,
D3DPOOL_DEFAULT, D3DPOOL_MANAGED, D3DPOOL_SYSTEMMEM, D3DPRESENT_INTERVAL_IMMEDIATE,
D3DPRESENT_PARAMETERS, D3DSURFACE_DESC, D3DSWAPEFFECT_DISCARD, D3DUSAGE_RENDERTARGET,
D3D_SDK_VERSION,
};
pub struct Direct3D9 {
pub texture: IDirect3DTexture9,
pub image: Image<BGRA8>,
pub direct3d: IDirect3D9,
pub device: IDirect3DDevice9,
}
impl RenderTest for Direct3D9 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Direct3D9::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let mut render_texture = None;
let output_size = output_size.unwrap_or(self.image.size);
self.device.CreateTexture(
output_size.width,
output_size.height,
1,
D3DUSAGE_RENDERTARGET as u32,
D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT,
&mut render_texture,
std::ptr::null_mut(),
)?;
let render_texture = render_texture
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 render texture"))?;
let mut copy_texture = None;
self.device.CreateOffscreenPlainSurface(
output_size.width,
output_size.height,
D3DFMT_A8R8G8B8,
D3DPOOL_SYSTEMMEM,
&mut copy_texture,
std::ptr::null_mut(),
)?;
let copy_texture =
copy_texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 copy texture"))?;
let surface = render_texture.GetSurfaceLevel(0)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let viewport = Viewport::new_render_target_sized_origin(&surface, None)?;
for frame in 0..=frame_count {
filter_chain.frame(&self.texture, &viewport, frame, options.as_ref())?;
}
self.device.GetRenderTargetData(&surface, &copy_texture)?;
let mut desc = D3DSURFACE_DESC::default();
surface.GetDesc(&mut desc)?;
let mut lock = D3DLOCKED_RECT::default();
copy_texture.LockRect(&mut lock, std::ptr::null_mut(), 0)?;
let mut buffer = vec![0u8; desc.Height as usize * lock.Pitch as usize];
std::ptr::copy_nonoverlapping(lock.pBits.cast(), buffer.as_mut_ptr(), buffer.len());
copy_texture.UnlockRect()?;
BGRA8::convert(&mut buffer);
let image = RgbaImage::from_raw(output_size.width, output_size.height, buffer)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Direct3D9 {
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
let direct3d = unsafe {
Direct3DCreate9(D3D_SDK_VERSION)
.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
};
let image = Image::<BGRA8>::load(image_path, UVDirection::TopLeft)?;
let mut present_params: D3DPRESENT_PARAMETERS = Default::default();
present_params.BackBufferWidth = image.size.width;
present_params.BackBufferHeight = image.size.height;
present_params.Windowed = TRUE;
present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
present_params.BackBufferFormat = D3DFMT_A8R8G8B8;
present_params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE as u32;
let device = unsafe {
let mut device = None;
direct3d.CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
HWND(std::ptr::null_mut()),
D3DCREATE_HARDWARE_VERTEXPROCESSING as u32,
&mut present_params,
&mut device,
)?;
device.ok_or_else(|| anyhow!("Unable to create Direct3D 9 device"))?
};
let texture = unsafe {
let mut texture = None;
device.CreateTexture(
image.size.width,
image.size.height,
1,
0,
D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED,
&mut texture,
std::ptr::null_mut(),
)?;
texture.ok_or_else(|| anyhow!("Unable to create Direct3D 9 texture"))?
};
unsafe {
let mut lock = D3DLOCKED_RECT::default();
texture.LockRect(0, &mut lock, std::ptr::null_mut(), 0)?;
std::ptr::copy_nonoverlapping(
image.bytes.as_ptr(),
lock.pBits.cast(),
image.bytes.len(),
);
texture.UnlockRect(0)?;
}
Ok(Self {
texture,
image,
direct3d,
device,
})
}
}

View file

@ -1,67 +0,0 @@
// Copyright (c) 2023 Christian Vallentin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use anyhow::anyhow;
use glfw::{fail_on_errors, Context, Glfw, OpenGlProfileHint, PWindow, WindowHint, WindowMode};
use std::sync::Arc;
pub struct GlfwContext {
_wnd: PWindow,
_glfw: Glfw,
pub gl: Arc<glow::Context>,
}
impl GlfwContext {
pub fn new(version: GLVersion, width: u32, height: u32) -> Result<Self, anyhow::Error> {
let mut glfw = glfw::init(fail_on_errors!())?;
let GLVersion(major, minor) = version;
glfw.window_hint(WindowHint::ContextVersion(major, minor));
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(WindowHint::Visible(false));
glfw.window_hint(WindowHint::OpenGlDebugContext(true));
let (mut wnd, _events) = glfw
.create_window(width, height, env!("CARGO_PKG_NAME"), WindowMode::Windowed)
.ok_or_else(|| anyhow!("No window"))?;
wnd.make_current();
let gl =
unsafe { glow::Context::from_loader_function(|proc| wnd.get_proc_address(proc) as _) };
// unsafe {
// gl.enable(glow::DEBUG_OUTPUT);
// gl.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
// gl.debug_message_callback(debug_callback);
// gl.debug_message_control(glow::DONT_CARE, glow::DONT_CARE, glow::DONT_CARE, &[], true);
// }
Ok(Self {
_wnd: wnd,
_glfw: glfw,
gl: Arc::new(gl),
})
}
}
#[derive(Debug)]
pub struct GLVersion(pub u32, pub u32);

View file

@ -1,259 +0,0 @@
mod context;
use crate::render::gl::context::{GLVersion, GlfwContext};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use glow::{HasContext, PixelPackData, PixelUnpackData};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::gl::{FilterChain, FilterChainOptions, FrameOptions, GLImage};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection, RGBA8};
use std::path::Path;
use std::sync::Arc;
struct OpenGl {
context: GlfwContext,
texture: GLImage,
image_bytes: Image<RGBA8>,
}
pub struct OpenGl3(OpenGl);
pub struct OpenGl4(OpenGl);
impl RenderTest for OpenGl3 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
OpenGl3::new(path)
}
fn image_size(&self) -> Size<u32> {
self.0.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 330,
use_dsa: false,
force_no_mipmaps: false,
disable_cache: false,
}),
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(
&mut filter_chain,
frame_count,
output_size,
frame_options
.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
})
.as_ref(),
)?)
}
}
impl RenderTest for OpenGl4 {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
OpenGl4::new(path)
}
fn image_size(&self) -> Size<u32> {
self.0.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 460,
use_dsa: true,
force_no_mipmaps: false,
disable_cache: true,
}),
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(
&mut filter_chain,
frame_count,
output_size,
frame_options
.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
})
.as_ref(),
)?)
}
}
impl OpenGl3 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
Ok(Self(OpenGl::new(image_path, false)?))
}
}
impl OpenGl4 {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
Ok(Self(OpenGl::new(image_path, true)?))
}
}
impl OpenGl {
pub fn new(image_path: &Path, use_dsa: bool) -> anyhow::Result<Self> {
let image: Image<RGBA8> = Image::load(image_path, UVDirection::TopLeft)?;
let height = image.size.height;
let width = image.size.width;
let version = if use_dsa {
GLVersion(4, 6)
} else {
GLVersion(3, 3)
};
let context = GlfwContext::new(version, width, height)?;
let texture = unsafe {
let tex = context.gl.create_texture().map_err(|s| anyhow!("{}", s))?;
context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
context.gl.tex_storage_2d(
glow::TEXTURE_2D,
1,
glow::RGBA8,
image.size.width as i32,
image.size.height as i32,
);
context.gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
context.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 4);
context.gl.bind_buffer(glow::PIXEL_UNPACK_BUFFER, None);
context.gl.tex_sub_image_2d(
glow::TEXTURE_2D,
0,
0,
0,
image.size.width as i32,
image.size.height as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
PixelUnpackData::Slice(&image.bytes),
);
context.gl.bind_texture(glow::TEXTURE_2D, None);
tex
};
Ok(Self {
context,
texture: GLImage {
handle: Some(texture),
format: glow::RGBA8,
size: image.size,
},
image_bytes: image,
})
}
pub fn render(
&self,
chain: &mut FilterChain,
frame_count: usize,
output_size: Option<Size<u32>>,
options: Option<&FrameOptions>,
) -> Result<RgbaImage, anyhow::Error> {
let output_size = output_size.unwrap_or(self.image_bytes.size);
let render_texture = unsafe {
let tex = self
.context
.gl
.create_texture()
.map_err(|s| anyhow!("{}", s))?;
self.context.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
self.context.gl.tex_storage_2d(
glow::TEXTURE_2D,
1,
glow::RGBA8,
output_size.width as i32,
output_size.height as i32,
);
self.context.gl.bind_texture(glow::TEXTURE_2D, None);
tex
};
let output = GLImage {
handle: Some(render_texture),
format: glow::RGBA8,
size: output_size,
};
let viewport = Viewport::new_render_target_sized_origin(&output, None)?;
for frame in 0..=frame_count {
unsafe {
chain.frame(&self.texture, &viewport, frame, options)?;
}
}
let mut data = vec![0u8; output_size.width as usize * output_size.height as usize * 4];
unsafe {
self.context
.gl
.bind_texture(glow::TEXTURE_2D, output.handle);
self.context.gl.get_tex_image(
glow::TEXTURE_2D,
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
PixelPackData::Slice(&mut data),
)
}
Ok(
RgbaImage::from_raw(output_size.width, output_size.height, data)
.ok_or(anyhow!("failed to create image from slice"))?,
)
}
}

View file

@ -1,174 +0,0 @@
#[cfg(all(windows, feature = "d3d11"))]
pub mod d3d11;
#[cfg(all(windows, feature = "d3d12"))]
pub mod d3d12;
#[cfg(all(windows, feature = "d3d9"))]
pub mod d3d9;
#[cfg(feature = "opengl")]
pub mod gl;
#[cfg(feature = "vulkan")]
pub mod vk;
#[cfg(feature = "wgpu")]
pub mod wgpu;
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub mod mtl;
use librashader::presets::ShaderPreset;
use librashader::runtime::Size;
use librashader_runtime::impl_default_frame_options;
use librashader_runtime::parameters::RuntimeParameters;
use std::path::Path;
/// Test harness to set up a device, render a triangle, and apply a shader
pub trait RenderTest {
/// Create a new instance of the test harness.
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized;
/// Get the size of the image loaded.
fn image_size(&self) -> Size<u32>;
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render(
&mut self,
path: &Path,
frame_count: usize,
output_size: Option<Size<u32>>,
) -> anyhow::Result<image::RgbaImage> {
let preset = ShaderPreset::try_parse(path)?;
self.render_with_preset(preset, frame_count, output_size)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
) -> anyhow::Result<image::RgbaImage> {
self.render_with_preset_and_params(preset, frame_count, output_size, None, None)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage>;
}
impl_default_frame_options!(CommonFrameOptions);
#[cfg(test)]
mod test {
use crate::render::RenderTest;
use image::codecs::png::PngEncoder;
use std::fs::File;
const IMAGE_PATH: &str = "../triangle.png";
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
// const FILTER_PATH: &str =
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
fn do_test<T: RenderTest>() -> anyhow::Result<()> {
let mut test = T::new(IMAGE_PATH.as_ref())?;
let image = test.render(FILTER_PATH.as_ref(), 100)?;
let out = File::create("out.png")?;
image.write_with_encoder(PngEncoder::new(out))?;
Ok(())
}
#[test]
#[cfg(all(windows, feature = "d3d11"))]
pub fn test_d3d11() -> anyhow::Result<()> {
do_test::<crate::render::d3d11::Direct3D11>()
}
#[test]
#[cfg(feature = "wgpu")]
pub fn test_wgpu() -> anyhow::Result<()> {
do_test::<crate::render::wgpu::Wgpu>()
}
#[test]
#[cfg(feature = "vulkan")]
pub fn test_vk() -> anyhow::Result<()> {
do_test::<crate::render::vk::Vulkan>()
}
#[test]
#[cfg(feature = "opengl")]
pub fn test_gl3() -> anyhow::Result<()> {
do_test::<crate::render::gl::OpenGl3>()
}
#[test]
#[cfg(feature = "opengl")]
pub fn test_gl4() -> anyhow::Result<()> {
do_test::<crate::render::gl::OpenGl4>()
}
#[test]
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub fn test_metal() -> anyhow::Result<()> {
do_test::<crate::render::mtl::Metal>()
}
#[test]
#[cfg(all(windows, feature = "d3d9"))]
pub fn test_d3d9() -> anyhow::Result<()> {
do_test::<crate::render::d3d9::Direct3D9>()
}
#[test]
#[cfg(all(windows, feature = "d3d12"))]
pub fn test_d3d12() -> anyhow::Result<()> {
do_test::<crate::render::d3d12::Direct3D12>()
}
pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
let mut a = A::new(IMAGE_PATH.as_ref())?;
let mut b = B::new(IMAGE_PATH.as_ref())?;
let a_image = a.render(FILTER_PATH.as_ref(), 100)?;
let b_image = b.render(FILTER_PATH.as_ref(), 100)?;
let similarity = image_compare::rgba_hybrid_compare(&a_image, &b_image)?;
assert!(similarity.score > 0.95);
Ok(())
}
}

View file

@ -1,208 +0,0 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use objc2::ffi::NSUInteger;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLCommandBuffer, MTLCommandQueue, MTLCreateSystemDefaultDevice, MTLDevice, MTLOrigin,
MTLPixelFormat, MTLRegion, MTLSize, MTLStorageMode, MTLTexture, MTLTextureDescriptor,
MTLTextureUsage,
};
use std::path::Path;
use std::ptr::NonNull;
pub struct Metal {
device: Retained<ProtocolObject<dyn MTLDevice>>,
texture: Retained<ProtocolObject<dyn MTLTexture>>,
image_bytes: Image<BGRA8>,
}
impl RenderTest for Metal {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Metal::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let queue = self
.device
.newCommandQueue()
.ok_or_else(|| anyhow!("Unable to create command queue"))?;
let cmd = queue
.commandBuffer()
.ok_or_else(|| anyhow!("Unable to create command buffer"))?;
let mut filter_chain = FilterChain::load_from_preset(
preset,
&queue,
Some(&FilterChainOptions {
force_no_mipmaps: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let output_size = output_size.unwrap_or(self.image_bytes.size);
let render_texture = unsafe {
let texture_descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
MTLPixelFormat::BGRA8Unorm,
output_size.width as NSUInteger,
output_size.height as NSUInteger,
false,
);
texture_descriptor.setSampleCount(1);
texture_descriptor.setStorageMode(
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
MTLStorageMode::Shared
} else {
MTLStorageMode::Managed
},
);
texture_descriptor.setUsage(MTLTextureUsage::ShaderWrite);
let texture = self
.device
.newTextureWithDescriptor(&texture_descriptor)
.ok_or_else(|| anyhow!("Failed to create texture"))?;
texture
};
let viewport = Viewport::new_render_target_sized_origin(render_texture.as_ref(), None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
filter_chain.frame(
&self.texture,
&viewport,
cmd.as_ref(),
frame,
options.as_ref(),
)?;
}
cmd.commit();
unsafe {
cmd.waitUntilCompleted();
}
let region = MTLRegion {
origin: MTLOrigin { x: 0, y: 0, z: 0 },
size: MTLSize {
width: output_size.width as usize,
height: output_size.height as usize,
depth: 1,
},
};
unsafe {
// should be the same size
let mut buffer =
vec![0u8; output_size.width as usize * output_size.height as usize * 4];
render_texture.getBytes_bytesPerRow_fromRegion_mipmapLevel(
NonNull::new(buffer.as_mut_ptr().cast()).unwrap(),
4 * output_size.width as usize,
region,
0,
);
// swap the BGRA back to RGBA.
BGRA8::convert(&mut buffer);
let image = RgbaImage::from_raw(
render_texture.width() as u32,
render_texture.height() as u32,
Vec::from(buffer),
)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
}
impl Metal {
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
unsafe {
let device = Retained::from_raw(MTLCreateSystemDefaultDevice())
.ok_or_else(|| anyhow!("Unable to create default Metal device"))?;
let texture_descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
MTLPixelFormat::BGRA8Unorm,
image.size.width as NSUInteger,
image.size.height as NSUInteger,
false,
);
texture_descriptor.setSampleCount(1);
texture_descriptor.setStorageMode(
if cfg!(all(target_arch = "aarch64", target_vendor = "apple")) {
MTLStorageMode::Shared
} else {
MTLStorageMode::Managed
},
);
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
let texture = device
.newTextureWithDescriptor(&texture_descriptor)
.ok_or_else(|| anyhow!("Failed to create texture"))?;
let region = MTLRegion {
origin: MTLOrigin { x: 0, y: 0, z: 0 },
size: MTLSize {
width: image.size.width as usize,
height: image.size.height as usize,
depth: 1,
},
};
texture.replaceRegion_mipmapLevel_withBytes_bytesPerRow(
region,
0,
// SAFETY: replaceRegion withBytes is const.
NonNull::new_unchecked(image.bytes.as_slice().as_ptr() as *mut _),
4 * image.size.width as usize,
);
Ok(Self {
device,
texture,
image_bytes: image,
})
}
}
}

View file

@ -1,153 +0,0 @@
use ash::vk;
use gpu_allocator::vulkan::Allocator;
use librashader::runtime::vk::VulkanObjects;
use parking_lot::Mutex;
use std::ffi::CStr;
use std::sync::Arc;
pub struct VulkanBase {
device: Arc<ash::Device>,
graphics_queue: vk::Queue,
allocator: Arc<Mutex<Allocator>>,
cmd_buffer: vk::CommandBuffer,
pool: vk::CommandPool,
}
impl From<&VulkanBase> for VulkanObjects {
fn from(value: &VulkanBase) -> Self {
VulkanObjects {
device: Arc::clone(&value.device),
alloc: Arc::clone(&value.allocator),
queue: value.graphics_queue.clone(),
}
}
}
const KHRONOS_VALIDATION: &[u8] = b"VK_LAYER_KHRONOS_validation\0";
const LIBRASHADER_VULKAN: &[u8] = b"librashader Vulkan\0";
impl VulkanBase {
pub fn new() -> anyhow::Result<Self> {
let entry = unsafe { ash::Entry::load() }?;
let layers = [KHRONOS_VALIDATION.as_ptr().cast()];
let app_info = vk::ApplicationInfo::default()
.application_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
.engine_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
.engine_version(0)
.application_version(0)
.api_version(vk::make_api_version(0, 1, 3, 0));
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_layer_names(&layers)
.enabled_extension_names(&[]);
let instance = unsafe { entry.create_instance(&create_info, None) }?;
let physical_device = super::physical_device::pick_physical_device(&instance)?;
let (device, queue, cmd_pool) = Self::create_device(&instance, &physical_device)?;
let alloc = super::memory::create_allocator(
device.clone(),
instance.clone(),
physical_device.clone(),
);
let buffer_info = vk::CommandBufferAllocateInfo::default()
.command_pool(cmd_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let buffers = unsafe { device.allocate_command_buffers(&buffer_info) }?
.into_iter()
.next()
.unwrap();
Ok(Self {
device: Arc::new(device),
graphics_queue: queue,
// debug,
allocator: alloc,
pool: cmd_pool,
cmd_buffer: buffers,
})
}
pub(crate) fn device(&self) -> &Arc<ash::Device> {
&self.device
}
pub(crate) fn allocator(&self) -> &Arc<Mutex<Allocator>> {
&self.allocator
}
fn create_device(
instance: &ash::Instance,
physical_device: &vk::PhysicalDevice,
) -> anyhow::Result<(ash::Device, vk::Queue, vk::CommandPool)> {
let _debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }];
let indices = super::physical_device::find_queue_family(&instance, *physical_device);
let queue_info = [vk::DeviceQueueCreateInfo::default()
.queue_family_index(indices.graphics_family()?)
.queue_priorities(&[1.0f32])];
let mut physical_device_features =
vk::PhysicalDeviceVulkan13Features::default().dynamic_rendering(true);
let extensions = [ash::khr::dynamic_rendering::NAME.as_ptr()];
let device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_info)
.enabled_extension_names(&extensions)
.push_next(&mut physical_device_features);
let device =
unsafe { instance.create_device(*physical_device, &device_create_info, None)? };
let queue = unsafe { device.get_device_queue(indices.graphics_family()?, 0) };
let create_info = vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(indices.graphics_family()?);
let pool = unsafe { device.create_command_pool(&create_info, None) }?;
Ok((device, queue, pool))
}
/// Simple helper function to synchronously queue work on the graphics queue
pub fn queue_work<T>(&self, f: impl FnOnce(vk::CommandBuffer) -> T) -> anyhow::Result<T> {
unsafe {
self.device.begin_command_buffer(
self.cmd_buffer,
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
)?
}
let result = f(self.cmd_buffer);
unsafe {
self.device.end_command_buffer(self.cmd_buffer)?;
self.device.queue_submit(
self.graphics_queue,
&[vk::SubmitInfo::default().command_buffers(&[self.cmd_buffer])],
vk::Fence::null(),
)?;
self.device.queue_wait_idle(self.graphics_queue)?;
self.device
.reset_command_buffer(self.cmd_buffer, vk::CommandBufferResetFlags::empty())?;
}
Ok(result)
}
}
impl Drop for VulkanBase {
fn drop(&mut self) {
unsafe {
self.device.destroy_command_pool(self.pool, None);
}
}
}

View file

@ -1,134 +0,0 @@
use ash::vk;
use gpu_allocator::vulkan::{
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
};
use gpu_allocator::{AllocationSizes, MemoryLocation};
use parking_lot::Mutex;
use anyhow::anyhow;
use std::mem::ManuallyDrop;
use std::sync::Arc;
pub struct VulkanImageMemory {
pub(crate) allocation: ManuallyDrop<Allocation>,
allocator: Arc<Mutex<Allocator>>,
}
impl VulkanImageMemory {
pub fn new(
device: &Arc<ash::Device>,
allocator: &Arc<Mutex<Allocator>>,
requirements: vk::MemoryRequirements,
image: &vk::Image,
location: MemoryLocation,
) -> anyhow::Result<VulkanImageMemory> {
let allocation = allocator.lock().allocate(&AllocationCreateDesc {
name: "imagemem",
requirements,
location,
linear: false,
allocation_scheme: AllocationScheme::DedicatedImage(*image),
})?;
unsafe {
device.bind_image_memory(*image, allocation.memory(), 0)?;
Ok(VulkanImageMemory {
allocation: ManuallyDrop::new(allocation),
allocator: Arc::clone(allocator),
})
}
}
}
impl Drop for VulkanImageMemory {
fn drop(&mut self) {
let allocation = unsafe { ManuallyDrop::take(&mut self.allocation) };
if let Err(e) = self.allocator.lock().free(allocation) {
println!("librashader-runtime-vk: [warn] failed to deallocate image buffer {e}")
}
}
}
pub struct VulkanBuffer {
pub handle: vk::Buffer,
device: Arc<ash::Device>,
allocation: ManuallyDrop<Allocation>,
allocator: Arc<Mutex<Allocator>>,
}
impl VulkanBuffer {
pub fn new(
device: &Arc<ash::Device>,
allocator: &Arc<Mutex<Allocator>>,
usage: vk::BufferUsageFlags,
size: usize,
) -> anyhow::Result<VulkanBuffer> {
unsafe {
let buffer_info = vk::BufferCreateInfo::default()
.size(size as vk::DeviceSize)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = device.create_buffer(&buffer_info, None)?;
let memory_reqs = device.get_buffer_memory_requirements(buffer);
let alloc = allocator.lock().allocate(&AllocationCreateDesc {
name: "buffer",
requirements: memory_reqs,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::DedicatedBuffer(buffer),
})?;
// let alloc = device.allocate_memory(&alloc_info, None)?;
device.bind_buffer_memory(buffer, alloc.memory(), 0)?;
Ok(VulkanBuffer {
handle: buffer,
allocation: ManuallyDrop::new(alloc),
allocator: Arc::clone(allocator),
device: device.clone(),
})
}
}
pub fn as_mut_slice(&mut self) -> anyhow::Result<&mut [u8]> {
let Some(allocation) = self.allocation.mapped_slice_mut() else {
return Err(anyhow!("Allocation is not host visible"));
};
Ok(allocation)
}
}
impl Drop for VulkanBuffer {
fn drop(&mut self) {
unsafe {
// SAFETY: things can not be double dropped.
let allocation = ManuallyDrop::take(&mut self.allocation);
if let Err(e) = self.allocator.lock().free(allocation) {
println!("librashader-test-vk: [warn] failed to deallocate buffer memory {e}")
}
if self.handle != vk::Buffer::null() {
self.device.destroy_buffer(self.handle, None);
}
}
}
}
pub fn create_allocator(
device: ash::Device,
instance: ash::Instance,
physical_device: vk::PhysicalDevice,
) -> Arc<Mutex<Allocator>> {
let alloc = Allocator::new(&AllocatorCreateDesc {
instance,
device,
physical_device,
debug_settings: Default::default(),
buffer_device_address: false,
allocation_sizes: AllocationSizes::default(),
})
.unwrap();
Arc::new(Mutex::new(alloc))
}

View file

@ -1,391 +0,0 @@
use crate::render::vk::base::VulkanBase;
use crate::render::vk::memory::{VulkanBuffer, VulkanImageMemory};
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use ash::vk;
use gpu_allocator::MemoryLocation;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::vk::{FilterChain, FilterChainOptions, FrameOptions, VulkanImage};
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection, BGRA8};
use std::path::Path;
mod base;
mod memory;
mod physical_device;
mod util;
pub struct Vulkan {
vk: VulkanBase,
image_bytes: Image<BGRA8>,
image: vk::Image,
_image_alloc: VulkanImageMemory,
}
impl RenderTest for Vulkan {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Vulkan::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image_bytes.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.vk,
Some(&FilterChainOptions {
frames_in_flight: 3,
force_no_mipmaps: false,
use_dynamic_rendering: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.extent(output_size.map_or(self.image_bytes.size.into(), |size| size.into()))
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
.initial_layout(vk::ImageLayout::UNDEFINED);
let render_texture = { self.vk.device().create_image(&image_info, None)? };
// This just needs to stay alive until the read.
let _memory = {
let mem_reqs = self
.vk
.device()
.get_image_memory_requirements(render_texture);
VulkanImageMemory::new(
self.vk.device(),
&self.vk.allocator(),
mem_reqs,
&render_texture,
MemoryLocation::GpuOnly,
)?
};
let transfer_texture = self.vk.device().create_image(
&vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.extent(self.image_bytes.size.into())
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::LINEAR)
.usage(vk::ImageUsageFlags::TRANSFER_DST)
.initial_layout(vk::ImageLayout::UNDEFINED),
None,
)?;
let mut transfer_memory = {
let mem_reqs = self
.vk
.device()
.get_image_memory_requirements(transfer_texture);
VulkanImageMemory::new(
self.vk.device(),
&self.vk.allocator(),
mem_reqs,
&transfer_texture,
MemoryLocation::GpuToCpu,
)?
};
self.vk.queue_work(|cmd| {
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
render_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
transfer_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::GENERAL,
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
let viewport = Viewport::new_render_target_sized_origin(
VulkanImage {
image: render_texture,
size: self.image_bytes.size.into(),
format: vk::Format::B8G8R8A8_UNORM,
},
None,
)?;
for frame in 0..=frame_count {
filter_chain.frame(
&VulkanImage {
image: self.image,
size: self.image_bytes.size,
format: vk::Format::B8G8R8A8_UNORM,
},
&viewport,
cmd,
frame,
options.as_ref(),
)?;
}
{
util::vulkan_image_layout_transition_levels(
&self.vk.device(),
cmd,
render_texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::AccessFlags::TRANSFER_READ,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
)
}
let offsets = [
vk::Offset3D { x: 0, y: 0, z: 0 },
vk::Offset3D {
x: self.image_bytes.size.width as i32,
y: self.image_bytes.size.height as i32,
z: 1,
},
];
let subresource = vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_array_layer(0)
.layer_count(1);
let image_blit = vk::ImageBlit::default()
.src_subresource(subresource.clone())
.src_offsets(offsets.clone())
.dst_subresource(subresource)
.dst_offsets(offsets);
self.vk.device().cmd_blit_image(
cmd,
render_texture,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
transfer_texture,
vk::ImageLayout::GENERAL,
&[image_blit],
vk::Filter::NEAREST,
);
Ok::<_, anyhow::Error>(())
})??;
// should have read now.
let mut memory = transfer_memory
.allocation
.mapped_slice_mut()
.ok_or(anyhow!("readback buffer was not mapped"))?;
let layout = self.vk.device().get_image_subresource_layout(
transfer_texture,
vk::ImageSubresource::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.array_layer(0),
);
memory = &mut memory[layout.offset as usize..];
// let mut pixels = Vec::with_capacity(layout.size as usize);
let image = RgbaImage::from_raw(
self.image_bytes.size.width,
self.image_bytes.size.height,
Vec::from(memory),
)
.ok_or(anyhow!("failed to create image from slice"));
self.vk.device().destroy_image(transfer_texture, None);
self.vk.device().destroy_image(render_texture, None);
Ok(image?)
}
}
}
impl Vulkan {
pub fn new(image_path: &Path) -> anyhow::Result<Self> {
let vk = VulkanBase::new()?;
let (image_bytes, image_alloc, image, _view) = Self::load_image(&vk, image_path)?;
Ok(Self {
vk,
image,
image_bytes,
_image_alloc: image_alloc,
})
}
pub fn load_image(
vk: &VulkanBase,
image_path: &Path,
) -> anyhow::Result<(Image<BGRA8>, VulkanImageMemory, vk::Image, vk::ImageView)> {
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.extent(image.size.into())
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(
vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC
| vk::ImageUsageFlags::TRANSFER_DST,
)
.initial_layout(vk::ImageLayout::UNDEFINED);
let texture = unsafe { vk.device().create_image(&image_info, None)? };
let memory = unsafe {
let mem_reqs = vk.device().get_image_memory_requirements(texture);
VulkanImageMemory::new(
vk.device(),
&vk.allocator(),
mem_reqs,
&texture,
MemoryLocation::GpuOnly,
)?
};
let image_subresource = vk::ImageSubresourceRange::default()
.level_count(image_info.mip_levels)
.layer_count(1)
.aspect_mask(vk::ImageAspectFlags::COLOR);
let swizzle_components = vk::ComponentMapping::default()
.r(vk::ComponentSwizzle::R)
.g(vk::ComponentSwizzle::G)
.b(vk::ComponentSwizzle::B)
.a(vk::ComponentSwizzle::A);
let view_info = vk::ImageViewCreateInfo::default()
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.image(texture)
.subresource_range(image_subresource)
.components(swizzle_components);
let texture_view = unsafe { vk.device().create_image_view(&view_info, None)? };
let mut staging = VulkanBuffer::new(
&vk.device(),
&vk.allocator(),
vk::BufferUsageFlags::TRANSFER_SRC,
image.bytes.len(),
)?;
staging.as_mut_slice()?.copy_from_slice(&image.bytes);
vk.queue_work(|cmd| unsafe {
util::vulkan_image_layout_transition_levels(
&vk.device(),
cmd,
texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::empty(),
vk::AccessFlags::TRANSFER_WRITE,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
let builder = vk::BufferImageCopy::default()
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(0)
.base_array_layer(0)
.layer_count(1),
)
.image_extent(image.size.into());
vk.device().cmd_copy_buffer_to_image(
cmd,
staging.handle,
texture,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[builder],
);
util::vulkan_image_layout_transition_levels(
&vk.device(),
cmd,
texture,
vk::REMAINING_MIP_LEVELS,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::ALL_GRAPHICS,
vk::QUEUE_FAMILY_IGNORED,
vk::QUEUE_FAMILY_IGNORED,
);
})?;
Ok((image, memory, texture, texture_view))
}
}

View file

@ -1,144 +0,0 @@
use anyhow::anyhow;
use ash::vk;
use std::ffi::CStr;
pub struct QueueFamilyIndices {
graphics_family: Option<u32>,
}
impl QueueFamilyIndices {
pub fn is_complete(&self) -> bool {
self.graphics_family.is_some()
}
pub fn graphics_family(&self) -> anyhow::Result<u32> {
self.graphics_family
.ok_or(anyhow!("The queue family is not complete"))
}
}
pub(crate) fn pick_physical_device(instance: &ash::Instance) -> anyhow::Result<vk::PhysicalDevice> {
let physical_devices = unsafe { instance.enumerate_physical_devices()? };
let mut result = None;
for &physical_device in physical_devices.iter() {
if is_physical_device_suitable(instance, physical_device) && result.is_none() {
result = Some(physical_device)
}
}
result.ok_or(anyhow!("Failed to find a suitable GPU"))
}
fn is_physical_device_suitable(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> bool {
let device_properties = unsafe { instance.get_physical_device_properties(physical_device) };
let device_features = unsafe { instance.get_physical_device_features(physical_device) };
let device_queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
if cfg!(feature = "vulkan-debug") {
let device_type = match device_properties.device_type {
vk::PhysicalDeviceType::CPU => "Cpu",
vk::PhysicalDeviceType::INTEGRATED_GPU => "Integrated GPU",
vk::PhysicalDeviceType::DISCRETE_GPU => "Discrete GPU",
vk::PhysicalDeviceType::VIRTUAL_GPU => "Virtual GPU",
vk::PhysicalDeviceType::OTHER => "Unknown",
_ => panic!(),
};
let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) };
let device_name = device_name.to_string_lossy();
println!(
"\tDevice Name: {}, id: {}, type: {}",
device_name, device_properties.device_id, device_type
);
println!("\tAPI Version: {}", device_properties.api_version);
println!("\tSupport Queue Family: {}", device_queue_families.len());
println!("\t\tQueue Count | Graphics, Compute, Transfer, Sparse Binding");
for queue_family in device_queue_families.iter() {
let is_graphics_support = if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
"support"
} else {
"unsupport"
};
let is_compute_support = if queue_family.queue_flags.contains(vk::QueueFlags::COMPUTE) {
"support"
} else {
"unsupport"
};
let is_transfer_support = if queue_family.queue_flags.contains(vk::QueueFlags::TRANSFER)
{
"support"
} else {
"unsupport"
};
let is_sparse_support = if queue_family
.queue_flags
.contains(vk::QueueFlags::SPARSE_BINDING)
{
"support"
} else {
"unsupport"
};
println!(
"\t\t{}\t | {}, {}, {}, {}",
queue_family.queue_count,
is_graphics_support,
is_compute_support,
is_transfer_support,
is_sparse_support
);
}
// there are plenty of features
println!(
"\tGeometry Shader support: {}",
if device_features.geometry_shader == 1 {
"support"
} else {
"unsupported"
}
);
}
let indices = find_queue_family(instance, physical_device);
indices.is_complete()
}
pub fn find_queue_family(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> QueueFamilyIndices {
let queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
let mut queue_family_indices = QueueFamilyIndices {
graphics_family: None,
};
let mut index = 0;
for queue_family in queue_families.iter() {
if queue_family.queue_count > 0
&& queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
queue_family_indices.graphics_family = Some(index);
}
if queue_family_indices.is_complete() {
break;
}
index += 1;
}
queue_family_indices
}

View file

@ -1,42 +0,0 @@
use ash::vk;
#[inline(always)]
pub unsafe fn vulkan_image_layout_transition_levels(
device: &ash::Device,
cmd: vk::CommandBuffer,
image: vk::Image,
levels: u32,
old_layout: vk::ImageLayout,
new_layout: vk::ImageLayout,
src_access: vk::AccessFlags,
dst_access: vk::AccessFlags,
src_stage: vk::PipelineStageFlags,
dst_stage: vk::PipelineStageFlags,
src_queue_family_index: u32,
dst_queue_family_index: u32,
) {
let mut barrier = vk::ImageMemoryBarrier::default();
barrier.s_type = vk::StructureType::IMAGE_MEMORY_BARRIER;
barrier.p_next = std::ptr::null();
barrier.src_access_mask = src_access;
barrier.dst_access_mask = dst_access;
barrier.old_layout = old_layout;
barrier.new_layout = new_layout;
barrier.src_queue_family_index = src_queue_family_index;
barrier.dst_queue_family_index = dst_queue_family_index;
barrier.image = image;
barrier.subresource_range.aspect_mask = vk::ImageAspectFlags::COLOR;
barrier.subresource_range.base_array_layer = 0;
barrier.subresource_range.level_count = levels;
barrier.subresource_range.layer_count = vk::REMAINING_ARRAY_LAYERS;
device.cmd_pipeline_barrier(
cmd,
src_stage,
dst_stage,
vk::DependencyFlags::empty(),
&[],
&[],
&[barrier],
)
}

View file

@ -1,268 +0,0 @@
use crate::render::{CommonFrameOptions, RenderTest};
use anyhow::anyhow;
use image::RgbaImage;
use librashader::runtime::wgpu::*;
use librashader::runtime::{Size, Viewport};
use librashader_runtime::image::{Image, UVDirection};
use std::io::{Cursor, Write};
use std::ops::DerefMut;
use std::path::Path;
use std::sync::Arc;
use wgpu::{Adapter, Device, Instance, Queue, Texture};
use wgpu_types::{
BufferAddress, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ImageCopyBuffer,
ImageDataLayout, Maintain, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};
use librashader::presets::ShaderPreset;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use parking_lot::Mutex;
pub struct Wgpu {
_instance: Instance,
_adapter: Adapter,
device: Arc<Device>,
queue: Arc<Queue>,
image: Image,
texture: Arc<Texture>,
}
struct BufferDimensions {
height: usize,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}
impl BufferDimensions {
fn new(width: usize, height: usize) -> Self {
let bytes_per_pixel = std::mem::size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
height,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
}
}
impl RenderTest for Wgpu {
fn new(path: &Path) -> anyhow::Result<Self>
where
Self: Sized,
{
Wgpu::new(path)
}
fn image_size(&self) -> Size<u32> {
self.image.size
}
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
output_size: Option<Size<u32>>,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
frame_options: Option<CommonFrameOptions>,
) -> anyhow::Result<image::RgbaImage> {
let mut chain = FilterChain::load_from_preset(
preset,
Arc::clone(&self.device),
Arc::clone(&self.queue),
Some(&FilterChainOptions {
force_no_mipmaps: false,
enable_cache: true,
adapter_info: None,
}),
)?;
if let Some(setter) = param_setter {
setter(&chain.parameters());
}
let mut cmd = self
.device
.create_command_encoder(&CommandEncoderDescriptor { label: None });
let output_tex = self.device.create_texture(&TextureDescriptor {
label: None,
size: output_size.map_or(self.texture.size(), |size| size.into()),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
});
let buffer_dimensions =
BufferDimensions::new(output_tex.width() as usize, output_tex.height() as usize);
let output_buf = Arc::new(self.device.create_buffer(&BufferDescriptor {
label: None,
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height)
as BufferAddress, // 4bpp
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
mapped_at_creation: false,
}));
let view = output_tex.create_view(&wgpu::TextureViewDescriptor::default());
let output = WgpuOutputView::new_from_raw(
&view,
output_tex.size().into(),
TextureFormat::Rgba8Unorm,
);
let viewport = Viewport::new_render_target_sized_origin(output, None)?;
let options = frame_options.map(|options| FrameOptions {
clear_history: options.clear_history,
frame_direction: options.frame_direction,
rotation: options.rotation,
total_subframes: options.total_subframes,
current_subframe: options.current_subframe,
});
for frame in 0..=frame_count {
chain.frame(
Arc::clone(&self.texture),
&viewport,
&mut cmd,
frame,
options.as_ref(),
)?;
}
cmd.copy_texture_to_buffer(
output_tex.as_image_copy(),
ImageCopyBuffer {
buffer: &output_buf,
layout: ImageDataLayout {
offset: 0,
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
rows_per_image: None,
},
},
output_tex.size(),
);
let si = self.queue.submit([cmd.finish()]);
self.device.poll(Maintain::WaitForSubmissionIndex(si));
let capturable = Arc::clone(&output_buf);
let pixels = Arc::new(Mutex::new(Vec::new()));
let pixels_async = Arc::clone(&pixels);
output_buf
.slice(..)
.map_async(wgpu::MapMode::Read, move |r| {
if r.is_ok() {
let buffer = capturable.slice(..).get_mapped_range();
let mut pixels = pixels_async.lock();
pixels.resize(buffer.len(), 0);
let mut cursor = Cursor::new(pixels.deref_mut());
for chunk in buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
cursor
.write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row])
.unwrap()
}
cursor.into_inner();
}
capturable.unmap();
});
self.device.poll(Maintain::Wait);
if pixels.lock().len() == 0 {
return Err(anyhow!("failed to copy pixels from buffer"));
}
let image = RgbaImage::from_raw(
output_tex.width(),
output_tex.height(),
pixels.lock().to_vec(),
)
.ok_or(anyhow!("Unable to create image from data"))?;
Ok(image)
}
}
impl Wgpu {
pub fn new(image: &Path) -> anyhow::Result<Self> {
pollster::block_on(async {
let instance = wgpu::Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok_or(anyhow!("Couldn't request WGPU adapter"))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
| wgpu::Features::PIPELINE_CACHE
| wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::FLOAT32_FILTERABLE,
required_limits: wgpu::Limits::default(),
label: None,
memory_hints: Default::default(),
},
None,
)
.await?;
let (image, texture) = Self::load_image(&device, &queue, image)?;
Ok(Self {
_instance: instance,
_adapter: adapter,
device: Arc::new(device),
queue: Arc::new(queue),
image: image,
texture: Arc::new(texture),
})
})
}
fn load_image(device: &Device, queue: &Queue, path: &Path) -> anyhow::Result<(Image, Texture)> {
let image = Image::load(path, UVDirection::TopLeft)?;
let texture = device.create_texture(&TextureDescriptor {
size: image.size.into(),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
format: wgpu::TextureFormat::Rgba8Unorm,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
label: None,
});
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&image.bytes,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * image.size.width),
rows_per_image: None,
},
image.size.into(),
);
let si = queue.submit([]);
device.poll(Maintain::WaitForSubmissionIndex(si));
Ok((image, texture))
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-common"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -13,26 +13,22 @@ description = "RetroArch shaders for all."
[features]
default = []
opengl = ["glow"]
opengl = ["gl"]
d3d9 = ["windows"]
d3d11 = ["windows", "dxgi"]
d3d12 = ["windows", "dxgi"]
dxgi = ["windows"]
vulkan = ["ash"]
wgpu = ["wgpu-types"]
metal = ["objc2", "objc2-metal"]
serde = ["dep:serde", "serde/derive", "smartstring/serde", "halfbrown/serde"]
metal = ["objc2-metal"]
[dependencies]
gl = { version = "0.14.0", optional = true }
ash = { version = "0.38", optional = true }
wgpu-types = { version = "22", optional = true }
num-traits = "0.2.15"
rustc-hash = "2.0.0"
halfbrown = "0.2.4"
smartstring = "1.0"
glow = { workspace = true, optional = true }
ash = { workspace = true, optional = true }
wgpu-types = { workspace = true, optional = true }
serde = { version = "1.0", optional = true }
[target.'cfg(windows)'.dependencies.windows]
optional = true
@ -46,14 +42,7 @@ features = [
"Win32_Graphics_Direct3D12",
]
[target.'cfg(target_vendor="apple")'.dependencies.objc2]
optional = true
workspace = true
features = ["apple"]
[target.'cfg(target_vendor="apple")'.dependencies.objc2-metal]
optional = true
workspace = true
version = "0.2.0"
features = ["MTLPixelFormat", "MTLRenderCommandEncoder", "MTLSampler"]

View file

@ -1,7 +1,5 @@
use crate::{FilterMode, GetSize, Size, WrapMode};
use windows::Win32::Foundation::E_NOINTERFACE;
use crate::{FilterMode, WrapMode};
use windows::Win32::Graphics::Direct3D11;
use windows::Win32::Graphics::Direct3D11::{ID3D11Texture2D, D3D11_RESOURCE_DIMENSION_TEXTURE2D};
impl From<WrapMode> for Direct3D11::D3D11_TEXTURE_ADDRESS_MODE {
fn from(value: WrapMode) -> Self {
@ -22,82 +20,3 @@ impl From<FilterMode> for Direct3D11::D3D11_FILTER {
}
}
}
impl GetSize<u32> for &Direct3D11::ID3D11RenderTargetView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let parent: ID3D11Texture2D = unsafe {
let resource = self.GetResource()?;
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
return Err(windows::core::Error::new(
E_NOINTERFACE,
"expected ID3D11Texture2D as the resource for the view.",
));
}
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
// inheritance chain.
//
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
std::mem::transmute(resource)
};
let mut desc = Default::default();
unsafe {
parent.GetDesc(&mut desc);
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D11::ID3D11RenderTargetView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
impl GetSize<u32> for &Direct3D11::ID3D11ShaderResourceView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let parent: ID3D11Texture2D = unsafe {
let resource = self.GetResource()?;
if resource.GetType() != D3D11_RESOURCE_DIMENSION_TEXTURE2D {
return Err(windows::core::Error::new(
E_NOINTERFACE,
"expected ID3D11Texture2D as the resource for the view.",
));
}
// SAFETY: We know tha the resource is an `ID3D11Texture2D`.
// This downcast is safe because ID3D11Texture2D has ID3D11Resource in its
// inheritance chain.
//
// This check + transmute is cheaper than doing `.cast` (i.e. `QueryInterface`).
std::mem::transmute(resource)
};
let mut desc = Default::default();
unsafe {
parent.GetDesc(&mut desc);
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D11::ID3D11ShaderResourceView {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}

View file

@ -1,4 +1,4 @@
use crate::{FilterMode, GetSize, Size, WrapMode};
use crate::{FilterMode, WrapMode};
use windows::Win32::Graphics::Direct3D12;
impl From<WrapMode> for Direct3D12::D3D12_TEXTURE_ADDRESS_MODE {
@ -20,12 +20,3 @@ impl From<FilterMode> for Direct3D12::D3D12_FILTER {
}
}
}
impl GetSize<u32> for Direct3D12::ID3D12Resource {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let desc = unsafe { self.GetDesc() };
Ok(Size::new(desc.Width as u32, desc.Height))
}
}

View file

@ -1,4 +1,4 @@
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use crate::{FilterMode, ImageFormat, WrapMode};
use windows::Win32::Graphics::Direct3D9;
//
impl From<ImageFormat> for Direct3D9::D3DFORMAT {
@ -64,52 +64,6 @@ impl From<FilterMode> for Direct3D9::D3DTEXTUREFILTER {
}
}
impl GetSize<u32> for &Direct3D9::IDirect3DSurface9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let mut desc = Default::default();
unsafe {
self.GetDesc(&mut desc)?;
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D9::IDirect3DSurface9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
impl GetSize<u32> for &Direct3D9::IDirect3DTexture9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let mut desc = Default::default();
unsafe {
self.GetLevelDesc(0, &mut desc)?;
}
Ok(Size {
height: desc.Height,
width: desc.Width,
})
}
}
impl GetSize<u32> for Direct3D9::IDirect3DTexture9 {
type Error = windows::core::Error;
fn size(&self) -> Result<Size<u32>, Self::Error> {
<&Self as GetSize<u32>>::size(&self)
}
}
// impl FilterMode {
// /// Get the mipmap filtering mode for the given combination.
// pub fn d3d9_mip(&self, mip: FilterMode) -> Direct3D9::D3DTEXTUREFILTER {

View file

@ -1,71 +1,71 @@
use crate::{FilterMode, ImageFormat, WrapMode};
impl From<ImageFormat> for u32 {
impl From<ImageFormat> for gl::types::GLenum {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => 0,
ImageFormat::R8Unorm => glow::R8,
ImageFormat::R8Uint => glow::R8UI,
ImageFormat::R8Sint => glow::R8I,
ImageFormat::R8G8Unorm => glow::RG8,
ImageFormat::R8G8Uint => glow::RG8UI,
ImageFormat::R8G8Sint => glow::RG8I,
ImageFormat::R8G8B8A8Unorm => glow::RGBA8,
ImageFormat::R8G8B8A8Uint => glow::RGBA8UI,
ImageFormat::R8G8B8A8Sint => glow::RGBA8I,
ImageFormat::R8G8B8A8Srgb => glow::SRGB8_ALPHA8,
ImageFormat::A2B10G10R10UnormPack32 => glow::RGB10_A2,
ImageFormat::A2B10G10R10UintPack32 => glow::RGB10_A2UI,
ImageFormat::R16Uint => glow::R16UI,
ImageFormat::R16Sint => glow::R16I,
ImageFormat::R16Sfloat => glow::R16F,
ImageFormat::R16G16Uint => glow::RG16UI,
ImageFormat::R16G16Sint => glow::RG16I,
ImageFormat::R16G16Sfloat => glow::RG16F,
ImageFormat::R16G16B16A16Uint => glow::RGBA16UI,
ImageFormat::R16G16B16A16Sint => glow::RGBA16I,
ImageFormat::R16G16B16A16Sfloat => glow::RGBA16F,
ImageFormat::R32Uint => glow::R32UI,
ImageFormat::R32Sint => glow::R32I,
ImageFormat::R32Sfloat => glow::R32F,
ImageFormat::R32G32Uint => glow::RG32UI,
ImageFormat::R32G32Sint => glow::RG32I,
ImageFormat::R32G32Sfloat => glow::RG32F,
ImageFormat::R32G32B32A32Uint => glow::RGBA32UI,
ImageFormat::R32G32B32A32Sint => glow::RGBA32I,
ImageFormat::R32G32B32A32Sfloat => glow::RGBA32F,
ImageFormat::Unknown => 0 as gl::types::GLenum,
ImageFormat::R8Unorm => gl::R8,
ImageFormat::R8Uint => gl::R8UI,
ImageFormat::R8Sint => gl::R8I,
ImageFormat::R8G8Unorm => gl::RG8,
ImageFormat::R8G8Uint => gl::RG8UI,
ImageFormat::R8G8Sint => gl::RG8I,
ImageFormat::R8G8B8A8Unorm => gl::RGBA8,
ImageFormat::R8G8B8A8Uint => gl::RGBA8UI,
ImageFormat::R8G8B8A8Sint => gl::RGBA8I,
ImageFormat::R8G8B8A8Srgb => gl::SRGB8_ALPHA8,
ImageFormat::A2B10G10R10UnormPack32 => gl::RGB10_A2,
ImageFormat::A2B10G10R10UintPack32 => gl::RGB10_A2UI,
ImageFormat::R16Uint => gl::R16UI,
ImageFormat::R16Sint => gl::R16I,
ImageFormat::R16Sfloat => gl::R16F,
ImageFormat::R16G16Uint => gl::RG16UI,
ImageFormat::R16G16Sint => gl::RG16I,
ImageFormat::R16G16Sfloat => gl::RG16F,
ImageFormat::R16G16B16A16Uint => gl::RGBA16UI,
ImageFormat::R16G16B16A16Sint => gl::RGBA16I,
ImageFormat::R16G16B16A16Sfloat => gl::RGBA16F,
ImageFormat::R32Uint => gl::R32UI,
ImageFormat::R32Sint => gl::R32I,
ImageFormat::R32Sfloat => gl::R32F,
ImageFormat::R32G32Uint => gl::RG32UI,
ImageFormat::R32G32Sint => gl::RG32I,
ImageFormat::R32G32Sfloat => gl::RG32F,
ImageFormat::R32G32B32A32Uint => gl::RGBA32UI,
ImageFormat::R32G32B32A32Sint => gl::RGBA32I,
ImageFormat::R32G32B32A32Sfloat => gl::RGBA32F,
}
}
}
impl From<WrapMode> for i32 {
impl From<WrapMode> for gl::types::GLenum {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => glow::CLAMP_TO_BORDER as i32,
WrapMode::ClampToEdge => glow::CLAMP_TO_EDGE as i32,
WrapMode::Repeat => glow::REPEAT as i32,
WrapMode::MirroredRepeat => glow::MIRRORED_REPEAT as i32,
WrapMode::ClampToBorder => gl::CLAMP_TO_BORDER,
WrapMode::ClampToEdge => gl::CLAMP_TO_EDGE,
WrapMode::Repeat => gl::REPEAT,
WrapMode::MirroredRepeat => gl::MIRRORED_REPEAT,
}
}
}
impl From<FilterMode> for i32 {
impl From<FilterMode> for gl::types::GLenum {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => glow::LINEAR as i32,
_ => glow::NEAREST as i32,
FilterMode::Linear => gl::LINEAR,
_ => gl::NEAREST,
}
}
}
impl FilterMode {
/// Get the mipmap filtering mode for the given combination.
pub fn gl_mip(&self, mip: FilterMode) -> u32 {
pub fn gl_mip(&self, mip: FilterMode) -> gl::types::GLenum {
match (self, mip) {
(FilterMode::Linear, FilterMode::Linear) => glow::LINEAR_MIPMAP_LINEAR,
(FilterMode::Linear, FilterMode::Nearest) => glow::LINEAR_MIPMAP_NEAREST,
(FilterMode::Nearest, FilterMode::Linear) => glow::NEAREST_MIPMAP_LINEAR,
_ => glow::NEAREST_MIPMAP_NEAREST,
(FilterMode::Linear, FilterMode::Linear) => gl::LINEAR_MIPMAP_LINEAR,
(FilterMode::Linear, FilterMode::Nearest) => gl::LINEAR_MIPMAP_NEAREST,
(FilterMode::Nearest, FilterMode::Linear) => gl::NEAREST_MIPMAP_LINEAR,
_ => gl::NEAREST_MIPMAP_NEAREST,
}
}
}

View file

@ -38,22 +38,18 @@ pub mod map;
pub use viewport::Viewport;
use num_traits::{AsPrimitive, Num};
use num_traits::AsPrimitive;
use std::convert::Infallible;
use std::ops::{Add, Sub};
use std::str::FromStr;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum StorageType {
/// The fully qualified path to the resource, often a shader source file or a texture.
pub enum ShaderStorage {
Path(std::path::PathBuf),
String(String),
}
#[repr(u32)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Supported image formats for textures.
pub enum ImageFormat {
#[default]
@ -100,7 +96,6 @@ pub enum ImageFormat {
#[repr(i32)]
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The filtering mode for a texture sampler.
pub enum FilterMode {
/// Linear filtering.
@ -139,7 +134,6 @@ impl FromStr for FilterMode {
#[repr(i32)]
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The wrapping (address) mode for a texture sampler.
pub enum WrapMode {
#[default]
@ -200,7 +194,6 @@ impl FromStr for ImageFormat {
/// A size with a width and height.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Size<T> {
pub width: T,
pub height: T,
@ -213,50 +206,6 @@ impl<T> Size<T> {
}
}
impl<T: Sub<Output = T>> Sub for Size<T> {
type Output = Size<T>;
fn sub(self, rhs: Self) -> Self::Output {
Self {
width: self.width - rhs.width,
height: self.height - rhs.height,
}
}
}
impl<T: Sub<T, Output = T> + Copy> Sub<T> for Size<T> {
type Output = Size<T>;
fn sub(self, rhs: T) -> Self::Output {
Self {
width: self.width - rhs,
height: self.height - rhs,
}
}
}
impl<T: Add<Output = T>> Add for Size<T> {
type Output = Size<T>;
fn add(self, rhs: Self) -> Self::Output {
Self {
width: self.width + rhs.width,
height: self.height + rhs.height,
}
}
}
impl<T: Add<T, Output = T> + Copy> Add<T> for Size<T> {
type Output = Size<T>;
fn add(self, rhs: T) -> Self::Output {
Self {
width: self.width + rhs,
height: self.height + rhs,
}
}
}
impl<T> From<Size<T>> for [f32; 4]
where
T: Copy + AsPrimitive<f32>,
@ -271,22 +220,3 @@ where
]
}
}
/// Trait for surface or texture objects that can fetch size.
pub trait GetSize<C: Num> {
type Error;
/// Fetch the size of the object
fn size(&self) -> Result<Size<C>, Self::Error>;
}
impl<T: GetSize<u32>> GetSize<f32> for T {
type Error = T::Error;
fn size(&self) -> Result<Size<f32>, Self::Error> {
let size = <T as GetSize<u32>>::size(self)?;
Ok(Size {
width: size.width as f32,
height: size.height as f32,
})
}
}

View file

@ -1,8 +1,5 @@
/// A hashmap optimized for small sets of size less than 32 with a fast hash implementation.
///
/// Used widely for shader reflection.
/// Fast optimized hash map type for small sets.
pub type FastHashMap<K, V> =
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
/// A string with small string optimizations up to 23 bytes.
pub type ShortString = smartstring::SmartString<smartstring::LazyCompact>;
pub use halfbrown;

View file

@ -1,8 +1,6 @@
use crate::{FilterMode, GetSize, ImageFormat, Size, WrapMode};
use objc2::runtime::ProtocolObject;
use crate::{FilterMode, ImageFormat, Size, WrapMode};
use objc2_metal::{
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLTexture,
MTLViewport,
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLViewport,
};
impl From<ImageFormat> for MTLPixelFormat {
@ -94,29 +92,3 @@ impl FilterMode {
}
}
}
impl GetSize<u32> for ProtocolObject<dyn MTLTexture> {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let height = self.height();
let width = self.width();
Ok(Size {
height: height as u32,
width: width as u32,
})
}
}
impl GetSize<u32> for &ProtocolObject<dyn MTLTexture> {
type Error = std::convert::Infallible;
fn size(&self) -> Result<Size<u32>, Self::Error> {
let height = self.height();
let width = self.width();
Ok(Size {
height: height as u32,
width: width as u32,
})
}
}

View file

@ -1,16 +1,6 @@
use crate::{GetSize, Size};
/// The rendering output of a filter chain.
///
/// Viewport coordinates are relative to the coordinate system of the
/// target runtime. For correct results, `x` and `y` should almost always be
/// 0, and `size` should be the same as the size of the output texture.
///
/// Size uniforms will always be passed the full size of the output texture,
/// regardless of the user-specified viewport size.
pub struct Viewport<'a, T> {
/// The x offset to start rendering from. For correct results, this should almost
/// always be 0 to indicate the origin.
/// The x offset to start rendering from.
pub x: f32,
/// The y offset to begin rendering from.
pub y: f32,
@ -19,28 +9,4 @@ pub struct Viewport<'a, T> {
pub mvp: Option<&'a [f32; 16]>,
/// The output handle to render the final image to.
pub output: T,
/// The extent of the viewport size starting from the origin defined
/// by x and y.
pub size: Size<u32>,
}
impl<'a, T: GetSize<u32>> Viewport<'a, T> {
/// Create a new viewport from an output that can be sized.
///
/// This will create a viewport that spans the entire output texture,
/// which will give correct results in the general case.
#[inline(always)]
pub fn new_render_target_sized_origin(
output: T,
mvp: Option<&'a [f32; 16]>,
) -> Result<Viewport<'a, T>, T::Error> {
let size = output.size()?;
Ok(Self {
x: 0.0,
y: 0.0,
mvp,
output,
size,
})
}
}

View file

@ -1,41 +0,0 @@
[package]
name = "librashader-pack"
version = "0.5.1"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
categories = ["emulators", "compilers", "graphics"]
keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-presets = { path = "../librashader-presets", version = "0.5.1", features = [
"serde",
] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1", features = [
"serde",
] }
librashader-common = { path = "../librashader-common", version = "0.5.1", features = [
"serde",
] }
thiserror = "1.0.64"
serde = { version = "1.0", features = ["derive"], optional = true }
rayon = { workspace = true }
image = { workspace = true }
base64 = { version = "0.22.1", optional = true }
serde_bytes = { version = "0.11.15", optional = true }
[features]
parse_legacy_glsl = ["librashader-presets/parse_legacy_glsl"]
serde = ["dep:serde", "dep:base64", "dep:serde_bytes"]
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
rayon = { workspace = true }
[dev-dependencies]
anyhow = "1.0.89"
serde_json = "1.0.128"
rmp-serde = "1.3.0"

View file

@ -1,254 +0,0 @@
//! Shader preset resource handling for librashader.
//!
//! This crate contains facilities to load shader preset resources from a [`ShaderPreset`].
//!
//! Also defines abstractly the `.slangpack` shader format implemented via serde derives on [`ShaderPresetPack`].
//!
use image::{ImageError, RgbaImage};
use librashader_common::StorageType;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ParameterMeta, PassMeta, ShaderPreset, TextureMeta};
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;
/// A buffer holding RGBA image bytes.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureBuffer {
#[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))]
image: Vec<u8>,
width: u32,
height: u32,
}
impl From<TextureBuffer> for Option<RgbaImage> {
fn from(value: TextureBuffer) -> Self {
RgbaImage::from_raw(value.width, value.height, value.image)
}
}
impl AsRef<[u8]> for TextureBuffer {
fn as_ref(&self) -> &[u8] {
self.image.as_ref()
}
}
impl From<RgbaImage> for TextureBuffer {
fn from(value: RgbaImage) -> Self {
let width = value.width();
let height = value.height();
TextureBuffer {
image: value.into_raw(),
width,
height,
}
}
}
/// A resource for a shader preset, fully loaded into memory.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LoadedResource<M: LoadableResource> {
/// The fully qualified path to the texture.
pub data: M::ResourceType,
/// Meta information about the texture.
pub meta: M,
}
/// Trait for a resource that is loadable from disk.
pub trait LoadableResource {
/// The type of the resource.
type ResourceType;
/// The error type when loading the resource.
type Error;
/// Load the resource from the path.
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error>;
}
impl LoadableResource for PassMeta {
type ResourceType = ShaderSource;
type Error = PreprocessError;
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
ShaderSource::load(storage)
}
}
impl LoadableResource for TextureMeta {
type ResourceType = TextureBuffer;
type Error = ImageError;
fn load(storage: &StorageType) -> Result<Self::ResourceType, Self::Error> {
let image = match storage {
StorageType::Path(path_buf) => image::open(path_buf),
StorageType::String(_) => todo!(),
};
image.map(|img| TextureBuffer::from(img.to_rgba8()))
}
}
/// The loaded resource information for the source code of a shader pass.
pub type PassResource = LoadedResource<PassMeta>;
/// The loaded texture resource for a shader preset.
pub type TextureResource = LoadedResource<TextureMeta>;
/// A fully loaded-in-memory shader preset, with all paths resolved to data.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderPresetPack {
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
/// Otherwise, the FBO after pass #N is passed a texture to next frame
#[cfg(feature = "parse_legacy_glsl")]
pub feedback_pass: i32,
/// The number of shaders enabled in the filter chain.
pub pass_count: i32,
// Everything is in Vecs because the expect number of values is well below 64.
/// Preset information for each shader.
pub passes: Vec<PassResource>,
/// Preset information for each texture.
pub textures: Vec<TextureResource>,
/// Preset information for each user parameter.
pub parameters: Vec<ParameterMeta>,
}
impl ShaderPresetPack {
/// Load a `ShaderPack` from a [`ShaderPreset`].
pub fn load_from_preset<E>(preset: ShaderPreset) -> Result<ShaderPresetPack, E>
where
E: From<PreprocessError>,
E: From<ImageError>,
E: Send,
{
#[cfg(not(target_arch = "wasm32"))]
let shaders_iter = preset.passes.into_par_iter();
#[cfg(target_arch = "wasm32")]
let shaders_iter = preset.shaders.into_iter();
#[cfg(not(target_arch = "wasm32"))]
let textures_iter = preset.textures.into_par_iter();
#[cfg(target_arch = "wasm32")]
let textures_iter = preset.textures.into_iter();
Ok(ShaderPresetPack {
#[cfg(feature = "parse_legacy_glsl")]
feedback_pass: preset.feedback_pass,
pass_count: preset.pass_count,
passes: shaders_iter
.map(|v| {
Ok::<_, E>(PassResource {
data: PassMeta::load(&v.storage)?,
meta: v.meta,
})
})
.collect::<Result<Vec<_>, _>>()?,
textures: textures_iter
.into_par_iter()
.map(|t| {
Ok::<_, E>(TextureResource {
data: TextureMeta::load(&t.storage)?,
meta: t.meta,
})
})
.collect::<Result<Vec<_>, _>>()?,
parameters: preset.parameters,
})
}
}
#[cfg(feature = "serde")]
mod serde_base64_or_bytes {
use base64::display::Base64Display;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde::{Deserializer, Serializer};
#[allow(clippy::ptr_arg)]
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(&Base64Display::new(v, &STANDARD))
} else {
serde_bytes::serialize(v, s)
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
if d.is_human_readable() {
struct Base64Visitor;
impl<'de> serde::de::Visitor<'de> for Base64Visitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a base64 encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
STANDARD.decode(v).map_err(serde::de::Error::custom)
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v.as_ref())
}
}
d.deserialize_str(Base64Visitor)
} else {
serde_bytes::deserialize(d)
}
}
}
#[cfg(test)]
mod test {
use crate::ShaderPresetPack;
use librashader_presets::ShaderPreset;
use std::fs::File;
use std::io::Write;
#[test]
fn test() {
let preset =
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
let mut file = File::create("crt-royale.slangpack.json").unwrap();
file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref())
.unwrap();
}
#[test]
fn test_rmp() {
let preset =
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
let mut file = File::create("crt-royale.slangpack").unwrap();
file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref())
.unwrap();
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-preprocess"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,14 +14,12 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.5.1" }
librashader-common = { path = "../librashader-common", version = "0.3.0" }
encoding_rs = "0.8.31"
serde = { version = "1.0", optional = true }
[features]
default = [ "line_directives" ]
line_directives = []
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
[dev-dependencies]
glob = "0.3.1"

View file

@ -1,4 +1,3 @@
use librashader_common::map::ShortString;
use std::convert::Infallible;
use std::path::PathBuf;
use thiserror::Error;
@ -28,7 +27,7 @@ pub enum PreprocessError {
PragmaParseError(String),
/// The given pragma was declared multiple times with differing values.
#[error("duplicate pragma found")]
DuplicatePragmaError(ShortString),
DuplicatePragmaError(String),
/// The image format requested by the shader was unknown or not supported.
#[error("shader format is unknown or not found")]
UnknownImageFormat,

View file

@ -2,9 +2,9 @@
//!
//! This crate contains facilities and types for resolving `#include` directives in `.slang`
//! into a single compilation unit. `#pragma` directives are also parsed and resolved as
//! [`ShaderParameter`] structs.
//! [`ShaderParameter`](crate::ShaderParameter) structs.
//!
//! The resulting [`ShaderSource`]can then be passed into a
//! The resulting [`ShaderSource`](crate::ShaderSource) can then be passed into a
//! reflection target for reflection and compilation into the target shader format.
//!
//! Re-exported as [`librashader::preprocess`](https://docs.rs/librashader/latest/librashader/preprocess/index.html).
@ -15,13 +15,11 @@ mod stage;
use crate::include::read_source;
pub use error::*;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::{ImageFormat, StorageType};
use std::path::Path;
use librashader_common::map::FastHashMap;
use librashader_common::ImageFormat;
/// The source file for a single shader pass.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderSource {
/// The source contents for the vertex shader.
pub vertex: String,
@ -30,10 +28,10 @@ pub struct ShaderSource {
pub fragment: String,
/// The alias of the shader if available.
pub name: Option<ShortString>,
pub name: Option<String>,
/// The list of shader parameters found in the shader source.
pub parameters: FastHashMap<ShortString, ShaderParameter>,
pub parameters: FastHashMap<String, ShaderParameter>,
/// The image format the shader expects.
pub format: ImageFormat,
@ -41,10 +39,9 @@ pub struct ShaderSource {
/// A user tweakable parameter for the shader as declared in source.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderParameter {
/// The name of the parameter.
pub id: ShortString,
pub id: String,
/// The description of the parameter.
pub description: String,
/// The initial value the parameter is set to.
@ -60,11 +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(storage: &StorageType) -> Result<ShaderSource, PreprocessError> {
match storage {
StorageType::Path(path_buf) => load_shader_source(path_buf),
StorageType::String(source) => parse_shader_source(source.clone()),
}
pub fn load(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
load_shader_source(file)
}
}
@ -83,14 +77,15 @@ impl SourceOutput for String {
}
}
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
let source = read_source(path)?;
parse_shader_source(source)
}
pub(crate) fn 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(),
};
pub(crate) fn parse_shader_source(source: String) -> Result<ShaderSource, PreprocessError> {
let meta = pragma::parse_pragma_meta(&source)?;
let text = stage::process_stages(&source)?;
let parameters = FastHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
@ -110,9 +105,9 @@ mod test {
#[test]
pub fn load_file() {
let result = load_shader_source(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
let result = load_shader_source(&librashader_common::ShaderStorage::Path(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang".into(),
))
.unwrap();
eprintln!("{:#}", result.vertex)
}

View file

@ -2,9 +2,7 @@ use crate::{PreprocessError, ShaderParameter};
use librashader_common::ImageFormat;
use nom::bytes::complete::{is_not, tag, take_while};
use librashader_common::map::ShortString;
use nom::character::complete::{multispace0, multispace1};
use nom::combinator::opt;
use nom::character::complete::multispace1;
use nom::number::complete::float;
use nom::sequence::delimited;
use nom::IResult;
@ -14,7 +12,7 @@ use std::str::FromStr;
pub(crate) struct ShaderMeta {
pub(crate) format: ImageFormat,
pub(crate) parameters: Vec<ShaderParameter>,
pub(crate) name: Option<ShortString>,
pub(crate) name: Option<String>,
}
fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessError> {
@ -37,25 +35,17 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
let (input, minimum) = float(input)?;
let (input, _) = multispace1(input)?;
let (input, maximum) = float(input)?;
// Step is actually optional and defaults to 0.02
// This behaviour can be seen in shaders like
// crt/crt-slangtest-cubic.slangp
// which doesn't have a step argument
// #pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4
// https://github.com/libretro/slang-shaders/blob/0e2939787076e4a8a83be89175557fde23abe837/crt/shaders/crt-slangtest/parameters.inc#L1
let (input, _) = multispace0(input)?;
let (input, step) = opt(float)(input)?;
let (input, _) = multispace1(input)?;
let (input, step) = float(input)?;
Ok((
input,
ShaderParameter {
id: name.into(),
id: name.to_string(),
description: description.to_string(),
initial,
minimum,
maximum,
step: step.unwrap_or(0.02),
step,
},
))
}
@ -70,7 +60,7 @@ fn parse_parameter_string(input: &str) -> Result<ShaderParameter, PreprocessErro
Ok(param)
} else {
Ok(ShaderParameter {
id: name.into(),
id: name.to_string(),
description: description.to_string(),
initial: 0f32,
minimum: 0f32,
@ -99,7 +89,7 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
if let Some(format_string) = line.strip_prefix("#pragma format ") {
if format != ImageFormat::Unknown {
return Err(PreprocessError::DuplicatePragmaError(line.into()));
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
}
let format_string = format_string.trim();
@ -110,12 +100,12 @@ pub(crate) fn parse_pragma_meta(source: impl AsRef<str>) -> Result<ShaderMeta, P
}
}
if let Some(pragma_name) = line.strip_prefix("#pragma name ") {
if line.starts_with("#pragma name ") {
if name.is_some() {
return Err(PreprocessError::DuplicatePragmaError(line.into()));
return Err(PreprocessError::DuplicatePragmaError(line.to_string()));
}
name = Some(ShortString::from(pragma_name.trim()))
name = Some(line.trim().to_string())
}
}
@ -134,7 +124,7 @@ mod test {
#[test]
fn parses_parameter_pragma() {
assert_eq!(ShaderParameter {
id: "exc".into(),
id: "exc".to_string(),
description: "orizontal correction hack (games where players stay at center)".to_string(),
initial: 0.0,
minimum: -10.0,
@ -146,7 +136,7 @@ mod test {
#[test]
fn parses_parameter_pragma_test() {
assert_eq!(ShaderParameter {
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".into(),
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".to_string(),
description: " Scanline Dir Multiplier".to_string(),
initial: 100.0,
minimum: 25.0,
@ -154,22 +144,4 @@ mod test {
step: 25.0
}, parse_parameter_string(r#"#pragma parameter HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR " Scanline Dir Multiplier" 100 25 1600 25"#).unwrap())
}
#[test]
fn parses_parameter_pragma_with_no_step() {
assert_eq!(
ShaderParameter {
id: "OUT_GAMMA".into(),
description: "Monitor Output Gamma".to_string(),
initial: 2.2,
minimum: 1.8,
maximum: 2.4,
step: 0.02
},
parse_parameter_string(
r#"#pragma parameter OUT_GAMMA "Monitor Output Gamma" 2.2 1.8 2.4"#
)
.unwrap()
)
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,18 +15,17 @@ 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.5.1" }
librashader-common = { path = "../librashader-common", version = "0.3.0" }
num-traits = "0.2"
once_cell = "1"
# we don't need unicode
regex = { version = "1", default-features = false, features = ["perf"] }
vec_extract_if_polyfill = "0.1.0"
serde = { version = "1.0", optional = true }
rustversion = "1.0"
os_str_bytes = { version = "6", features = ["conversions"] }
[features]
parse_legacy_glsl = []
serde = ["dep:serde", "serde/derive", "librashader-common/serde"]
[dev-dependencies]
glob = "0.3.1"

View file

@ -207,7 +207,6 @@ impl ContextItem {
}
}
/// The wildcard key associated with the context item.
pub fn key(&self) -> &str {
match self {
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
@ -331,7 +330,7 @@ impl WildcardContext {
///
/// This is a one way conversion, and will normalize rotation context items
/// into `VID-FINAL-ROT`.
pub fn into_hashmap(mut self) -> FastHashMap<String, String> {
pub fn to_hashmap(mut self) -> FastHashMap<String, String> {
let mut map = FastHashMap::default();
let last_user_rot = self
.0
@ -365,6 +364,7 @@ impl WildcardContext {
}
}
#[rustversion::since(1.74)]
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
use std::ffi::{OsStr, OsString};
@ -411,3 +411,53 @@ pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, St
*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

@ -0,0 +1,312 @@
//! This is a stable polyfill for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
use core::ptr;
use core::slice;
/// Polyfill trait for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
pub(crate) trait MakeExtractIf<T> {
/// Creates an iterator which uses a closure to determine if an element should be removed.
///
/// If the closure returns true, then the element is removed and yielded.
/// If the closure returns false, the element will remain in the vector and will not be yielded
/// by the iterator.
///
/// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
/// or the iteration short-circuits, then the remaining elements will be retained.
///
/// Note that `extract_if` also lets you mutate every element in the filter closure,
/// regardless of whether you choose to keep or remove it.
///
/// # Examples
///
/// Splitting an array into evens and odds, reusing the original allocation:
///
/// ```
/// use vec_extract_if_polyfill::MakeExtractIf;
/// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
///
/// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
/// let odds = numbers;
///
/// assert_eq!(evens, vec![2, 4, 6, 8, 14]);
/// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);
/// ```
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
where
F: FnMut(&mut T) -> bool;
}
impl<T> MakeExtractIf<T> for Vec<T> {
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
where
F: FnMut(&mut T) -> bool,
{
let old_len = self.len();
// Guard against us getting leaked (leak amplification)
unsafe {
self.set_len(0);
}
ExtractIf {
vec: self,
idx: 0,
del: 0,
old_len,
pred: filter,
}
}
}
/// An iterator which uses a closure to determine if an element should be removed.
///
/// This struct is created by [`Vec::extract_if`].
/// See its documentation for more.
///
/// # Example
///
/// ```
/// use vec_extract_if_polyfill::MakeExtractIf;
///
/// let mut v = vec![0, 1, 2];
/// let iter = v.extract_if(|x| *x % 2 == 0);
/// ```
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ExtractIf<'a, T, F>
where
F: FnMut(&mut T) -> bool,
{
vec: &'a mut Vec<T>,
/// The index of the item that will be inspected by the next call to `next`.
idx: usize,
/// The number of items that have been drained (removed) thus far.
del: usize,
/// The original length of `vec` prior to draining.
old_len: usize,
/// The filter test predicate.
pred: F,
}
impl<T, F> Iterator for ExtractIf<'_, T, F>
where
F: FnMut(&mut T) -> bool,
{
type Item = T;
fn next(&mut self) -> Option<T> {
unsafe {
while self.idx < self.old_len {
let i = self.idx;
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
let drained = (self.pred)(&mut v[i]);
// Update the index *after* the predicate is called. If the index
// is updated prior and the predicate panics, the element at this
// index would be leaked.
self.idx += 1;
if drained {
self.del += 1;
return Some(ptr::read(&v[i]));
} else if self.del > 0 {
let del = self.del;
let src: *const T = &v[i];
let dst: *mut T = &mut v[i - del];
ptr::copy_nonoverlapping(src, dst, 1);
}
}
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.old_len - self.idx))
}
}
impl<T, F> Drop for ExtractIf<'_, T, F>
where
F: FnMut(&mut T) -> bool,
{
fn drop(&mut self) {
unsafe {
if self.idx < self.old_len && self.del > 0 {
// This is a pretty messed up state, and there isn't really an
// obviously right thing to do. We don't want to keep trying
// to execute `pred`, so we just backshift all the unprocessed
// elements and tell the vec that they still exist. The backshift
// is required to prevent a double-drop of the last successfully
// drained item prior to a panic in the predicate.
let ptr = self.vec.as_mut_ptr();
let src = ptr.add(self.idx);
let dst = src.sub(self.del);
let tail_len = self.old_len - self.idx;
src.copy_to(dst, tail_len);
}
self.vec.set_len(self.old_len - self.del);
}
}
}
#[cfg(test)]
mod test {
use crate::extract_if::MakeExtractIf;
#[test]
fn drain_filter_empty() {
let mut vec: Vec<i32> = vec![];
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_zst() {
let mut vec = vec![(), (), (), (), ()];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
while let Some(_) = iter.next() {
count += 1;
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, initial_len);
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_false() {
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| false);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
for _ in iter.by_ref() {
count += 1;
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, 0);
assert_eq!(vec.len(), initial_len);
assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
#[test]
fn drain_filter_true() {
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let initial_len = vec.len();
let mut count = 0;
{
let mut iter = vec.extract_if(|_| true);
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
while let Some(_) = iter.next() {
count += 1;
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
}
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
assert_eq!(iter.size_hint(), (0, Some(0)));
}
assert_eq!(count, initial_len);
assert_eq!(vec.len(), 0);
assert_eq!(vec, vec![]);
}
#[test]
fn drain_filter_complex() {
{
// [+xxx++++++xxxxx++++x+x++]
let mut vec = vec![
1, 2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
37, 39,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 14);
assert_eq!(
vec,
vec![1, 7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]
);
}
{
// [xxx++++++xxxxx++++x+x++]
let mut vec = vec![
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36, 37,
39,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 13);
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]);
}
{
// [xxx++++++xxxxx++++x+x]
let mut vec = vec![
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
assert_eq!(vec.len(), 11);
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35]);
}
{
// [xxxxxxxxxx+++++++++++]
let mut vec = vec![
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
assert_eq!(vec.len(), 10);
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
}
{
// [+++++++++++xxxxxxxxxx]
let mut vec = vec![
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
];
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
assert_eq!(removed.len(), 10);
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
assert_eq!(vec.len(), 10);
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
}
}
}

View file

@ -3,13 +3,17 @@
//! This crate contains facilities and types for parsing `.slangp` shader presets files.
//!
//! Shader presets contain shader and texture parameters, and the order in which to apply a set of
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`]
//! shaders in a filter chain. A librashader runtime takes a resulting [`ShaderPreset`](crate::ShaderPreset)
//! as input to create a filter chain.
//!
//! 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;

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

@ -1,46 +1,42 @@
use crate::extract_if::MakeExtractIf;
use crate::parse::remove_if;
use crate::parse::value::Value;
use crate::{
ParameterMeta, PassConfig, PassMeta, Scale2D, Scaling, ShaderPreset, TextureConfig, TextureMeta,
};
use vec_extract_if_polyfill::MakeExtractIf;
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let textures: Vec<TextureConfig> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
let textures: Vec<TextureConfig> = values
.extract_if(|f| matches!(*f, Value::Texture { .. }))
.map(|value| {
if let Value::Texture {
name,
filter_mode,
wrap_mode,
mipmap,
path,
} = value
{
TextureConfig {
name,
filter_mode,
wrap_mode,
mipmap,
path,
} = value
{
TextureConfig {
storage: librashader_common::StorageType::Path(path),
meta: TextureMeta {
name,
wrap_mode,
filter_mode,
mipmap,
},
}
} else {
unreachable!("values should all be of type Texture")
wrap_mode,
filter_mode,
mipmap,
}
})
.collect();
let parameters: Vec<ParameterMeta> =
MakeExtractIf::extract_if(&mut values, |f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterMeta { name, value }
} else {
unreachable!("values should be all of type parameters")
}
})
.collect();
} else {
unreachable!("values should all be of type Texture")
}
})
.collect();
let parameters: Vec<ParameterConfig> = values
.extract_if(|f| matches!(*f, Value::Parameter { .. }))
.map(|value| {
if let Value::Parameter(name, value) = value {
ParameterConfig { name, value }
} else {
unreachable!("values should be all of type parameters")
}
})
.collect();
let mut shaders = Vec::new();
let shader_count =
@ -68,9 +64,9 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
&mut values,
|v| matches!(*v, Value::Shader(shader_index, _) if shader_index == shader),
) {
let shader_values: Vec<Value> =
MakeExtractIf::extract_if(&mut values, |v| v.shader_index() == Some(shader))
.collect();
let shader_values: Vec<Value> = values
.extract_if(|v| v.shader_index() == Some(shader))
.collect();
let scale_type = shader_values.iter().find_map(|f| match f {
Value::ScaleType(_, value) => Some(*value),
_ => None,
@ -117,66 +113,64 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
scale_y = scale;
}
let shader = PassConfig {
storage: librashader_common::StorageType::Path(name),
meta: PassMeta {
id,
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.clone()),
let shader = ShaderPassConfig {
id,
name: librashader_common::ShaderStorage::Path(name),
alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()),
_ => None,
}),
filter: shader_values
.iter()
.find_map(|f| match f {
Value::FilterMode(_, value) => Some(*value),
_ => None,
}),
filter: shader_values
.iter()
.find_map(|f| match f {
Value::FilterMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
wrap_mode: shader_values
.iter()
.find_map(|f| match f {
Value::WrapMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
frame_count_mod: shader_values
.iter()
.find_map(|f| match f {
Value::FrameCountMod(_, value) => Some(*value),
_ => None,
})
.unwrap_or(0),
srgb_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::SrgbFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
float_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::FloatFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
mipmap_input: shader_values
.iter()
.find_map(|f| match f {
Value::MipmapInput(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
scaling: Scale2D {
valid: scale_valid,
x: Scaling {
scale_type: scale_type_x.unwrap_or_default(),
factor: scale_x.unwrap_or_default(),
},
y: Scaling {
scale_type: scale_type_y.unwrap_or_default(),
factor: scale_y.unwrap_or_default(),
},
})
.unwrap_or_default(),
wrap_mode: shader_values
.iter()
.find_map(|f| match f {
Value::WrapMode(_, value) => Some(*value),
_ => None,
})
.unwrap_or_default(),
frame_count_mod: shader_values
.iter()
.find_map(|f| match f {
Value::FrameCountMod(_, value) => Some(*value),
_ => None,
})
.unwrap_or(0),
srgb_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::SrgbFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
float_framebuffer: shader_values
.iter()
.find_map(|f| match f {
Value::FloatFramebuffer(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
mipmap_input: shader_values
.iter()
.find_map(|f| match f {
Value::MipmapInput(_, value) => Some(*value),
_ => None,
})
.unwrap_or(false),
scaling: Scale2D {
valid: scale_valid,
x: Scaling {
scale_type: scale_type_x.unwrap_or_default(),
factor: scale_x.unwrap_or_default(),
},
y: Scaling {
scale_type: scale_type_y.unwrap_or_default(),
factor: scale_y.unwrap_or_default(),
},
},
};
@ -188,8 +182,8 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
ShaderPreset {
#[cfg(feature = "parse_legacy_glsl")]
feedback_pass,
pass_count: shader_count,
passes: shaders,
shader_count,
shaders,
textures,
parameters,
}

View file

@ -10,7 +10,7 @@ use nom::IResult;
use num_traits::cast::ToPrimitive;
use crate::parse::token::do_lex;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::map::FastHashMap;
use librashader_common::{FilterMode, WrapMode};
use std::fs::File;
use std::io::Read;
@ -18,7 +18,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::context::{apply_context, WildcardContext};
use vec_extract_if_polyfill::MakeExtractIf;
use crate::extract_if::MakeExtractIf;
#[derive(Debug)]
pub enum Value {
@ -37,10 +37,10 @@ pub enum Value {
FloatFramebuffer(i32, bool),
SrgbFramebuffer(i32, bool),
MipmapInput(i32, bool),
Alias(i32, ShortString),
Parameter(ShortString, f32),
Alias(i32, String),
Parameter(String, f32),
Texture {
name: ShortString,
name: String,
filter_mode: FilterMode,
wrap_mode: WrapMode,
mipmap: bool,
@ -195,10 +195,8 @@ fn load_child_reference_strings(
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
let mut new_tokens = do_lex(&reference_contents)?;
let new_references: Vec<PathBuf> =
MakeExtractIf::extract_if(&mut new_tokens, |token| {
*token.key.fragment() == "#reference"
})
let new_references: Vec<PathBuf> = new_tokens
.extract_if(|token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
@ -219,7 +217,7 @@ pub(crate) fn parse_preset(
) -> Result<Vec<Value>, ParsePresetError> {
let path = path.as_ref();
let mut path = path.to_path_buf();
let context = context.into_hashmap();
let context = context.to_hashmap();
apply_context(&mut path, &context);
@ -252,10 +250,10 @@ pub fn parse_values(
root_path.pop();
}
let references: Vec<PathBuf> =
MakeExtractIf::extract_if(&mut tokens, |token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
let references: Vec<PathBuf> = tokens
.extract_if(|token| *token.key.fragment() == "#reference")
.map(|value| PathBuf::from(*value.value.fragment()))
.collect();
// unfortunately we need to lex twice because there's no way to know the references ahead of time.
// the returned references should have context applied
@ -277,12 +275,10 @@ pub fn parse_values(
// collect all possible parameter names.
let mut parameter_names: Vec<&str> = Vec::new();
for (_, tokens) in all_tokens.iter_mut() {
for token in
MakeExtractIf::extract_if(tokens, |token| *token.key.fragment() == "parameters")
{
for token in tokens.extract_if(|token| *token.key.fragment() == "parameters") {
let parameter_name_string: &str = token.value.fragment();
for parameter_name in parameter_name_string.split(';') {
parameter_names.push(parameter_name.trim());
parameter_names.push(parameter_name);
}
}
}
@ -290,11 +286,10 @@ pub fn parse_values(
// collect all possible texture names.
let mut texture_names: Vec<&str> = Vec::new();
for (_, tokens) in all_tokens.iter_mut() {
for token in MakeExtractIf::extract_if(tokens, |token| *token.key.fragment() == "textures")
{
for token in tokens.extract_if(|token| *token.key.fragment() == "textures") {
let texture_name_string: &str = token.value.fragment();
for texture_name in texture_name_string.split(';') {
texture_names.push(texture_name.trim());
texture_names.push(texture_name);
}
}
}
@ -302,9 +297,7 @@ pub fn parse_values(
let mut values = Vec::new();
// resolve shader paths.
for (path, tokens) in all_tokens.iter_mut() {
for token in MakeExtractIf::extract_if(tokens, |token| {
parse_indexed_key("shader", token.key).is_ok()
}) {
for token in tokens.extract_if(|token| parse_indexed_key("shader", token.key).is_ok()) {
let (_, index) = parse_indexed_key("shader", token.key).map_err(|e| match e {
nom::Err::Error(e) | nom::Err::Failure(e) => {
let input: Span = e.input;
@ -335,11 +328,8 @@ pub fn parse_values(
// resolve texture paths
let mut textures = Vec::new();
for (path, tokens) in all_tokens.iter_mut() {
for token in
MakeExtractIf::extract_if(tokens, |token| texture_names.contains(token.key.fragment()))
{
for token in tokens.extract_if(|token| texture_names.contains(token.key.fragment())) {
let mut relative_path = path.to_path_buf();
// Don't trim paths
relative_path.push(*token.value.fragment());
relative_path
.canonicalize()
@ -390,7 +380,7 @@ pub fn parse_values(
.map_or(None, |(_, v)| Some(FilterMode::from_str(&v.value).unwrap()));
values.push(Value::Texture {
name: ShortString::from(*texture.fragment()),
name: texture.to_string(),
filter_mode: filter.unwrap_or(if linear {
FilterMode::Linear
} else {
@ -405,7 +395,7 @@ pub fn parse_values(
let mut rest_tokens = Vec::new();
// hopefully no more textures left in the token tree
for (p, token) in tokens {
if parameter_names.contains(&token.key.fragment().trim()) {
if parameter_names.contains(token.key.fragment()) {
let param_val = from_float(token.value)
// This is literally just to work around BEAM_PROFILE in crt-hyllian-sinc-glow.slangp
// which has ""0'.000000". This somehow works in RA because it defaults to 0, probably.
@ -413,7 +403,7 @@ pub fn parse_values(
// params (god help me), it would be pretty bad because we lose texture path fallback.
.unwrap_or(0.0);
values.push(Value::Parameter(
ShortString::from(token.key.fragment().trim()),
token.key.fragment().to_string(),
param_val,
));
continue;
@ -494,10 +484,7 @@ pub fn parse_values(
}
if let Ok((_, idx)) = parse_indexed_key("alias", token.key) {
values.push(Value::Alias(
idx,
ShortString::from(token.value.fragment().trim()),
));
values.push(Value::Alias(idx, token.value.to_string()));
continue;
}
if let Ok((_, idx)) = parse_indexed_key("scale_type", token.key) {
@ -560,7 +547,7 @@ pub fn parse_values(
// handle undeclared parameters after parsing everything else as a last resort.
if let Ok(param_val) = from_float(token.value) {
values.push(Value::Parameter(
ShortString::from(token.key.fragment().trim()),
token.key.fragment().to_string(),
param_val,
));
}
@ -571,7 +558,6 @@ pub fn parse_values(
.all(|k| !token.key.ends_with(k))
{
let mut relative_path = path.to_path_buf();
// Don't trim paths.
relative_path.push(*token.value.fragment());
relative_path
.canonicalize()
@ -610,7 +596,7 @@ pub fn parse_values(
});
values.push(Value::Texture {
name: ShortString::from(*texture.fragment()),
name: texture.to_string(),
filter_mode: if linear {
FilterMode::Linear
} else {
@ -635,7 +621,7 @@ mod test {
#[test]
pub fn parse_basic() {
let root =
PathBuf::from("../test/shaders_slang/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
let basic = parse_preset(root, WildcardContext::new());
eprintln!("{basic:?}");
assert!(basic.is_ok());

View file

@ -1,32 +1,18 @@
use crate::error::ParsePresetError;
use librashader_common::map::ShortString;
use librashader_common::{FilterMode, ImageFormat, WrapMode};
use std::ops::Mul;
use std::path::PathBuf;
use std::str::FromStr;
/// The configuration for a single shader pass.
pub type PassConfig = PathReference<PassMeta>;
/// Configuration options for a lookup texture used in the shader.
pub type TextureConfig = PathReference<TextureMeta>;
/// A reference to a resource on disk.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PathReference<M> {
pub storage: librashader_common::StorageType,
/// Meta information about the resource.
pub meta: M,
}
/// Meta information about a shader pass.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PassMeta {
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: librashader_common::ShaderStorage,
/// The alias of the shader pass if available.
pub alias: Option<ShortString>,
pub alias: Option<String>,
/// The filtering mode that this shader pass should expect.
pub filter: FilterMode,
/// The texture addressing (wrap) mode that this shader pass expects.
@ -43,7 +29,7 @@ pub struct PassMeta {
pub scaling: Scale2D,
}
impl PassMeta {
impl ShaderPassConfig {
/// If the framebuffer expects a different format than what was defined in the
/// shader source, returns such format.
#[inline(always)]
@ -68,7 +54,6 @@ impl PassMeta {
#[repr(i32)]
#[derive(Default, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The scaling type for the shader pass.
pub enum ScaleType {
#[default]
@ -84,7 +69,6 @@ pub enum ScaleType {
/// The scaling factor for framebuffer scaling.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ScaleFactor {
/// Scale by a fractional float factor.
Float(f32),
@ -145,7 +129,6 @@ impl FromStr for ScaleType {
/// Framebuffer scaling parameters.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Scaling {
/// The method to scale the framebuffer with.
pub scale_type: ScaleType,
@ -155,7 +138,6 @@ pub struct Scaling {
/// 2D quad scaling parameters.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Scale2D {
/// Whether or not this combination of scaling factors is valid.
pub valid: bool,
@ -167,24 +149,24 @@ pub struct Scale2D {
/// Configuration options for a lookup texture used in the shader.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextureMeta {
pub struct TextureConfig {
/// The name of the texture.
pub name: ShortString,
pub name: String,
/// The fully qualified path to the texture.
pub path: PathBuf,
/// The wrap (addressing) mode to use when sampling the texture.
pub wrap_mode: WrapMode,
/// The filter mode to use when sampling the texture.
pub filter_mode: FilterMode,
/// Whether to generate mipmaps for this texture.
/// Whether or not to generate mipmaps for this texture.
pub mipmap: bool,
}
/// Configuration options for a shader parameter.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ParameterMeta {
pub struct ParameterConfig {
/// The name of the parameter.
pub name: ShortString,
pub name: String,
/// The value it is set to in the preset.
pub value: f32,
}
@ -194,7 +176,6 @@ pub struct ParameterMeta {
/// A shader preset can be used to create a filter chain runtime instance, or reflected to get
/// parameter metadata.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderPreset {
/// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
/// Otherwise, the FBO after pass #N is passed a texture to next frame
@ -202,14 +183,14 @@ pub struct ShaderPreset {
pub feedback_pass: i32,
/// The number of shaders enabled in the filter chain.
pub pass_count: i32,
pub shader_count: i32,
// Everything is in Vecs because the expect number of values is well below 64.
/// Preset information for each shader.
pub passes: Vec<PassConfig>,
pub shaders: Vec<ShaderPassConfig>,
/// Preset information for each texture.
pub textures: Vec<TextureConfig>,
/// Preset information for each user parameter.
pub parameters: Vec<ParameterMeta>,
pub parameters: Vec<ParameterConfig>,
}

View file

@ -3,7 +3,7 @@ name = "librashader-reflect"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,24 +12,26 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
glslang = "0.6.0"
glslang = "0.3"
bytemuck = "1.13.0"
thiserror = "1.0.37"
bitflags = "2.4.2"
librashader-common = { path = "../librashader-common", version = "0.5.1" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
librashader-presets = { path = "../librashader-presets", version = "0.5.1" }
librashader-pack = { path = "../librashader-pack", version = "0.5.1" }
librashader-common = { path = "../librashader-common", version = "0.3.0" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
spirv-cross2 = { workspace = true, optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true }
naga = { version = "22", optional = true }
rspirv = { version = "0.12.0", optional = true }
spirv = { version = "0.3.0", optional = true}
serde = { version = "1.0", features = ["derive"], optional = true }
indexmap = { version = "2.1.0", features = [] }
matches = { version = "0.1.10", features = [] }
rustc-hash = "2.0.0"
[target.'cfg(windows)'.dependencies.spirv-to-dxil]
@ -37,14 +39,12 @@ version = "0.4.7"
optional = true
[features]
default = ["cross", "naga", "wgsl", "msl"]
dxil = [ "spirv-cross2?/hlsl", "dep:spirv-to-dxil" ]
wgsl = [ "cross", "naga", "naga/wgsl-out", "dep:spirv", "dep:rspirv"]
cross = [ "spirv-cross2", "spirv-cross2/glsl", "spirv-cross2/hlsl", "spirv-cross2/msl" ]
naga = [ "dep:rspirv", "dep:spirv", "dep:naga", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
serde = ["dep:serde", "serde/derive", "librashader-common/serde", "bitflags/serde"]
msl = [ "cross", "spirv-cross2/msl", "naga?/msl-out" ]
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" ]
stable = []
unstable-naga-in = ["naga/glsl-in"]
unstable-naga-in = ["naga/glsl-in"]

View file

@ -1,12 +1,11 @@
use crate::back::spirv::WriteSpirV;
use crate::back::targets::{OutputTarget, DXIL};
use crate::back::{
CompileReflectShader, CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput,
};
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::{
@ -17,12 +16,12 @@ impl OutputTarget for DXIL {
type Output = DxilObject;
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -39,28 +38,6 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
type Target = DXIL;
type Options = Option<ShaderModel>;
type Context = ();
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
Ok(CompilerBackend {
// we can just reuse WriteSpirV as the backend.
backend: Box::new(WriteSpirV {
reflect,
vertex: compile.vertex,
fragment: compile.fragment,
}),
})
}
}
impl CompileShader<DXIL> for WriteSpirV {
type Options = Option<ShaderModel>;
type Context = ();
@ -111,11 +88,4 @@ impl CompileShader<DXIL> for WriteSpirV {
context: (),
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<DxilObject, Self::Context>, ShaderCompileError> {
<WriteSpirV as CompileShader<DXIL>>::compile(*self, options)
}
}

View file

@ -1,11 +1,12 @@
use crate::back::targets::GLSL;
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
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_cross2::compile::glsl::GlslVersion;
pub use spirv_cross::glsl::Version as GlslVersion;
use crate::reflect::cross::glsl::GlslReflect;
@ -14,15 +15,15 @@ pub struct CrossGlslContext {
/// A map of bindings of sampler names to binding locations.
pub sampler_bindings: Vec<(String, u32)>,
/// The compiled program artifact after compilation.
pub artifact: CompiledProgram<spirv_cross2::targets::Glsl>,
pub artifact: CompiledProgram<spirv_cross::glsl::Target>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -32,19 +33,3 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
})
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
type Target = GLSL;
type Options = GlslVersion;
type Context = CrossGlslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(GlslReflect::try_from(&compile)?),
})
}
}

View file

@ -1,12 +1,13 @@
use crate::back::targets::HLSL;
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
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_cross2::compile::hlsl::HlslShaderModel;
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
/// Buffer assignment information
#[derive(Debug, Clone)]
@ -46,7 +47,7 @@ impl HlslBufferAssignments {
{
return true;
}
false
return false;
}
// Check if the mangled name matches.
@ -91,24 +92,24 @@ impl HlslBufferAssignments {
return true;
}
false
return false;
}
}
/// The context for a HLSL compilation via spirv-cross.
pub struct CrossHlslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross2::targets::Hlsl>,
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
pub vertex_buffers: HlslBufferAssignments,
pub fragment_buffers: HlslBufferAssignments,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -119,22 +120,6 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
type Target = HLSL;
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(HlslReflect::try_from(&compile)?),
})
}
}
#[cfg(test)]
mod test {
use crate::back::hlsl::HlslBufferAssignments;

View file

@ -32,30 +32,17 @@ pub trait CompileShader<T: OutputTarget> {
type Context;
/// Consume the object and return the compiled output of the shader.
///
/// The shader should either be reflected or validated as
/// [ReflectShader] before being compiled, or the results may not be valid.
fn compile(
self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
/// Consume the object and return the compiled output of the shader.
///
/// This is an internal implementation detail for stable building without TAIT,
/// to allow delegation when Self is unsized (i.e. dyn CompileReflectShader).
#[doc(hidden)]
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError>;
}
/// Marker trait for combinations of targets and compilations that can be reflected and compiled
/// successfully.
///
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`] implement
/// for a given target that also implement [`CompileShader`] for that target.
/// 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, S>:
CompileShader<
T,
@ -94,13 +81,6 @@ where
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
self.backend.compile(options)
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<E::Output, Self::Context>, ShaderCompileError> {
self.backend.compile(options)
}
}
/// A trait for reflectable compilations that can be transformed
@ -142,47 +122,6 @@ where
) -> Result<ShaderReflection, ShaderReflectError> {
self.backend.reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.backend.validate()
}
}
impl<T: ReflectShader + ?Sized> ReflectShader for Box<T> {
fn reflect(
&mut self,
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
(**self).reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
(**self).validate()
}
}
impl<O, T> CompileShader<T> for Box<O>
where
O: CompileShader<T> + ?Sized,
T: OutputTarget,
{
type Options = O::Options;
type Context = O::Context;
fn compile(
self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
O::compile_boxed(self, options)
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<T::Output, Self::Context>, ShaderCompileError> {
self.compile(options)
}
}
#[cfg(test)]

View file

@ -1,15 +1,16 @@
use crate::back::targets::MSL;
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
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 MSL language version to target.
pub use spirv_cross2::compile::msl::MslVersion;
/// The HLSL shader model version to target.
pub use spirv_cross::msl::Version as MslVersion;
/// Compiler options for MSL
#[derive(Debug, Default, Clone)]
@ -21,15 +22,15 @@ pub struct MslNagaCompileOptions {
/// The context for a MSL compilation via spirv-cross.
pub struct CrossMslContext {
/// The compiled HLSL program.
pub artifact: CompiledProgram<spirv_cross2::targets::Msl>,
pub artifact: CompiledProgram<spirv_cross::msl::Target>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -40,22 +41,6 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = CrossMslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(MslReflect::try_from(&compile)?),
})
}
}
/// The naga module for a shader after compilation
pub struct NagaMslModule {
pub translation_info: TranslationInfo,
@ -68,12 +53,12 @@ pub struct NagaMslContext {
pub next_free_binding: u32,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -83,19 +68,3 @@ impl FromCompilation<SpirvCompilation, Naga> for MSL {
})
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for MSL {
type Target = MSL;
type Options = Option<self::MslVersion>;
type Context = NagaMslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}

View file

@ -1,7 +1,5 @@
use crate::back::targets::SPIRV;
use crate::back::{
CompileReflectShader, CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput,
};
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
use crate::error::{ShaderCompileError, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::cross::glsl::GlslReflect;
@ -18,12 +16,12 @@ pub(crate) struct WriteSpirV {
pub(crate) fragment: Vec<u32>,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -41,29 +39,6 @@ impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
type Target = SPIRV;
type Options = Option<()>;
type Context = ();
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, SpirvCross> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
backend: Box::new(WriteSpirV {
reflect,
vertex,
fragment,
}),
})
}
}
impl ReflectShader for WriteSpirV {
fn reflect(
&mut self,
@ -72,10 +47,6 @@ impl ReflectShader for WriteSpirV {
) -> Result<ShaderReflection, ShaderReflectError> {
self.reflect.reflect(pass_number, semantics)
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.reflect.validate()
}
}
impl CompileShader<SPIRV> for WriteSpirV {
@ -92,17 +63,6 @@ impl CompileShader<SPIRV> for WriteSpirV {
context: (),
})
}
fn compile_boxed(
self: Box<Self>,
_options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
Ok(ShaderCompilerOutput {
vertex: self.vertex,
fragment: self.fragment,
context: (),
})
}
}
/// The context for a SPIRV compilation via Naga
@ -111,12 +71,12 @@ pub struct NagaSpirvContext {
pub vertex: Module,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -127,22 +87,6 @@ impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
type Target = SPIRV;
type Options = NagaSpirvOptions;
type Context = NagaSpirvContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}
pub struct NagaSpirvOptions {
pub lowering: NagaLoweringOptions,
pub version: (u8, u8),

View file

@ -1,8 +1,9 @@
use crate::back::targets::WGSL;
use crate::back::{CompileReflectShader, CompilerBackend, FromCompilation};
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
@ -11,12 +12,12 @@ pub struct NagaWgslContext {
pub vertex: Module,
}
#[cfg(not(feature = "stable"))]
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = impl CompileReflectShader<Self::Target, SpirvCompilation, Naga>;
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
+ ReflectShader;
fn from_compilation(
compile: SpirvCompilation,
@ -27,22 +28,6 @@ impl FromCompilation<SpirvCompilation, Naga> for WGSL {
}
}
#[cfg(feature = "stable")]
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
type Target = WGSL;
type Options = NagaLoweringOptions;
type Context = NagaWgslContext;
type Output = Box<dyn CompileReflectShader<Self::Target, SpirvCompilation, Naga> + Send>;
fn from_compilation(
compile: SpirvCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: Box::new(NagaReflect::try_from(&compile)?),
})
}
}
#[cfg(test)]
mod test {
use crate::back::targets::WGSL;
@ -51,19 +36,16 @@ mod test {
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
#[test]
pub fn test_into() {
let result =
ShaderSource::load("../test/shaders_slang/crt/shaders/slotmask.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/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let result = ShaderSource::load("../test/basic.slang").unwrap();
let result = ShaderSource::load("../test/basic.slang").unwrap();
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(

View file

@ -20,7 +20,7 @@ pub enum ShaderCompileError {
/// Error when transpiling from spirv-cross.
#[error("spirv-cross error: {0:?}")]
SpirvCrossCompileError(#[from] spirv_cross2::SpirvCrossError),
SpirvCrossCompileError(#[from] spirv_cross::ErrorCode),
/// Error when transpiling from spirv-to-dxil
#[cfg(all(target_os = "windows", feature = "dxil"))]
@ -87,7 +87,7 @@ pub enum SemanticsErrorKind {
pub enum ShaderReflectError {
/// Reflection error from spirv-cross.
#[error("spirv cross error: {0}")]
SpirvCrossError(#[from] spirv_cross2::SpirvCrossError),
SpirvCrossError(#[from] spirv_cross::ErrorCode),
/// Error when validating vertex shader semantics.
#[error("error when verifying vertex semantics: {0:?}")]
VertexSemanticError(SemanticsErrorKind),

View file

@ -1,5 +1,5 @@
use crate::error::ShaderCompileError;
use glslang::{CompilerOptions, ShaderInput, ShaderMessage};
use glslang::{CompilerOptions, ShaderInput};
use librashader_preprocess::ShaderSource;
use rspirv::binary::Assemble;
use rspirv::dr::Builder;
@ -25,21 +25,14 @@ pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, S
spirv_version: glslang::SpirvVersion::SPIRV1_0,
},
version_profile: None,
messages: ShaderMessage::DEFAULT,
};
let vertex = glslang::ShaderSource::from(source.vertex.as_str());
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None, None)?;
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None)?;
let vertex = compiler.create_shader(vertex)?;
let fragment = glslang::ShaderSource::from(source.fragment.as_str());
let fragment = ShaderInput::new(
&fragment,
glslang::ShaderStage::Fragment,
&options,
None,
None,
)?;
let fragment = ShaderInput::new(&fragment, glslang::ShaderStage::Fragment, &options, None)?;
let fragment = compiler.create_shader(fragment)?;
let vertex = vertex.compile()?;
@ -48,12 +41,11 @@ pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, S
let vertex = load_module(&vertex);
let fragment = load_module(&fragment);
let mut fragment = Builder::new_from_module(fragment);
let mut vertex = Builder::new_from_module(vertex);
let mut pass = link_input_outputs::LinkInputs::new(&mut vertex, &mut fragment, false);
let mut pass = link_input_outputs::LinkInputs::new(&vertex, &mut fragment);
pass.do_pass();
let vertex = vertex.module().assemble();
let vertex = vertex.assemble();
let fragment = fragment.module().assemble();
Ok(SpirvCompilation { vertex, fragment })

View file

@ -1,5 +1,6 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use serde::{Deserialize, Serialize};
pub(crate) mod spirv_passes;
mod glslang;
@ -24,13 +25,20 @@ impl ShaderReflectObject for SpirvCompilation {
}
/// A reflectable shader compilation via glslang.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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;

View file

@ -2,21 +2,12 @@ use rspirv::dr::{Builder, Module, Operand};
use rustc_hash::{FxHashMap, FxHashSet};
use spirv::{Decoration, Op, StorageClass};
/// Do DCE on inputs of the fragment shader, then
/// link by downgrading outputs of unused fragment inputs
/// to global variables on the vertex shader.
pub struct LinkInputs<'a> {
pub frag_builder: &'a mut Builder,
pub vert_builder: &'a mut Builder,
// binding -> ID
pub outputs: FxHashMap<u32, spirv::Word>,
// id -> binding
pub inputs_to_remove: FxHashMap<spirv::Word, u32>,
pub inputs: FxHashSet<spirv::Word>,
}
impl<'a> LinkInputs<'a> {
/// Get the value of the location of the inout in the module
fn find_location(module: &Module, id: spirv::Word) -> Option<u32> {
module.annotations.iter().find_map(|op| {
if op.class.opcode != Op::Decorate {
@ -42,11 +33,9 @@ impl<'a> LinkInputs<'a> {
})
}
pub fn new(vert: &'a mut Builder, frag: &'a mut Builder, keep_if_bound: bool) -> Self {
let mut outputs = FxHashMap::default();
let mut inputs_to_remove = FxHashMap::default();
let mut inputs = FxHashMap::default();
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)
@ -56,32 +45,24 @@ impl<'a> LinkInputs<'a> {
continue;
};
inputs_to_remove.insert(id, location);
inputs.insert(location, id);
inputs.insert(id);
bindings.insert(location, id);
}
}
}
for global in vert.module_ref().types_global_values.iter() {
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.module_ref(), id) else {
let Some(location) = Self::find_location(vert, id) else {
continue;
};
// Add to list of outputs
outputs.insert(location, id);
// Keep the input, if it is bound to both stages.Otherwise, do DCE analysis on
// the input, and remove it regardless if bound, if unused by the fragment stage.
if keep_if_bound {
if let Some(frag_ref) = inputs.get(&location) {
// if something is bound to the same location in the vertex shader,
// we're good.
inputs_to_remove.remove(&frag_ref);
}
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);
}
}
}
@ -89,189 +70,11 @@ impl<'a> LinkInputs<'a> {
Self {
frag_builder: frag,
vert_builder: vert,
outputs,
inputs_to_remove,
inputs,
}
}
pub fn do_pass(&mut self) {
self.trim_inputs();
self.downgrade_outputs();
self.put_vertex_variables_to_end();
}
fn put_vertex_variables_to_end(&mut self) {
// this is easier than doing proper topo sort.
// we need it so that all type definitions are valid before
// being referred to by a variable.
let mut vars = Vec::new();
self.vert_builder
.module_mut()
.types_global_values
.retain(|instr| {
if instr.class.opcode == spirv::Op::Variable {
vars.push(instr.clone());
return false;
};
true
});
self.vert_builder
.module_mut()
.types_global_values
.append(&mut vars);
}
/// Downgrade dead inputs corresponding to outputs to global variables, keeping existing mappings.
fn downgrade_outputs(&mut self) {
let dead_outputs = self
.inputs_to_remove
.values()
.filter_map(|i| self.outputs.get(i).cloned())
.collect::<FxHashSet<spirv::Word>>();
let mut pointer_types_to_downgrade = FxHashSet::default();
// Map from Pointer type to pointee
let mut pointer_type_pointee = FxHashMap::default();
// Map from StorageClass Output to StorageClass Private
let mut downgraded_pointer_types = FxHashMap::default();
// First collect all the pointer types that are needed for dead outputs.
for global in self.vert_builder.module_ref().types_global_values.iter() {
if global.class.opcode != spirv::Op::Variable
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !dead_outputs.contains(&id) {
continue;
}
if let Some(result_type) = global.result_type {
pointer_types_to_downgrade.insert(result_type);
}
}
}
// Collect all the pointee types of pointer types to downgrade
for global in self.vert_builder.module_ref().types_global_values.iter() {
if global.class.opcode != spirv::Op::TypePointer
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !pointer_types_to_downgrade.contains(&id) {
continue;
}
let Some(pointee_type) = global.operands[1].id_ref_any() else {
continue;
};
pointer_type_pointee.insert(id, pointee_type);
}
}
// Create pointer types for everything we saw above with Private storage class.
// We don't have to deal with OpTypeForwardPointer, because PhysicalStorageBuffer
// is not valid in slang shaders, and we're only working with Vulkan inputs.
for (pointer_type, pointee_type) in pointer_type_pointee.iter() {
// Create a new private type
let private_pointer_type =
self.vert_builder
.type_pointer(None, StorageClass::Private, *pointee_type);
// Add it to the mapping
downgraded_pointer_types.insert(pointer_type, private_pointer_type);
}
// Downgrade the OpVariable storage class and reassign the types.
for global in self
.vert_builder
.module_mut()
.types_global_values
.iter_mut()
{
if global.class.opcode != spirv::Op::Variable
|| global.operands[0] != Operand::StorageClass(StorageClass::Output)
{
continue;
}
if let Some(id) = global.result_id {
if !dead_outputs.contains(&id) {
continue;
}
// downgrade the OpVariable if it's in dead_outputs
global.operands[0] = Operand::StorageClass(StorageClass::Private);
// Get the result type. If there's no result type it's invalid anyways
// so it doesn't matter that we downgraded early (better downgraded than unmatched)
let Some(result_type) = &mut global.result_type else {
continue;
};
let Some(new_type) = downgraded_pointer_types.get(&*result_type) else {
// We should have created one above.
continue;
};
// Set the type of the OpVariable to the same type with Private storageclass.
*result_type = *new_type;
}
}
// Strip decorations of downgraded variables.
self.vert_builder.module_mut().annotations.retain_mut(|op| {
if op.class.opcode != Op::Decorate {
return true;
}
let Some(Operand::Decoration(Decoration::Location)) = op.operands.get(1) else {
return true;
};
let Some(&Operand::IdRef(target)) = op.operands.get(0) else {
return true;
};
// If target is in dead outputs, then don't keep it.
!dead_outputs.contains(&target)
});
for entry_point in self.vert_builder.module_mut().entry_points.iter_mut() {
let mut index = 0;
entry_point.operands.retain(|s| {
// Skip the execution mode, entry point reference, and name.
if index < 3 {
index += 1;
return true;
}
index += 1;
// Ignore any non-IdRef
let Operand::IdRef(id_ref) = s else {
return true;
};
// If the entry point contains a dead outputs, remove it from the interface.
!dead_outputs.contains(id_ref)
});
}
}
// Trim unused fragment shader inputs
fn trim_inputs(&mut self) {
let functions = &self.frag_builder.module_ref().functions;
// literally if it has any reference at all we can keep it
@ -279,8 +82,8 @@ impl<'a> LinkInputs<'a> {
for param in &function.parameters {
for op in &param.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs_to_remove.contains_key(&word) {
self.inputs_to_remove.remove(&word);
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
@ -290,8 +93,8 @@ impl<'a> LinkInputs<'a> {
for inst in &block.instructions {
for op in &inst.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs_to_remove.contains_key(&word) {
self.inputs_to_remove.remove(&word);
if self.inputs.contains(&word) {
self.inputs.remove(&word);
}
}
}
@ -300,10 +103,11 @@ impl<'a> LinkInputs<'a> {
}
// 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_to_remove.contains_key(&word) {
if self.inputs.contains(&word) {
return false;
}
}
@ -314,7 +118,7 @@ impl<'a> LinkInputs<'a> {
self.frag_builder.module_mut().annotations.retain(|instr| {
for op in &instr.operands {
if let Some(word) = op.id_ref_any() {
if self.inputs_to_remove.contains_key(&word) {
if self.inputs.contains(&word) {
return false;
}
}
@ -325,7 +129,7 @@ impl<'a> LinkInputs<'a> {
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_to_remove.contains_key(&word) {
if self.inputs.contains(&word) {
return false;
}
}
@ -341,7 +145,7 @@ impl<'a> LinkInputs<'a> {
return true;
};
!self.inputs_to_remove.contains_key(&id)
!self.inputs.contains(&id)
});
}
}

View file

@ -34,7 +34,7 @@
//! pub fn compile_preset(preset: ShaderPreset) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), Box<dyn Error>>
//! {
//! let (passes, semantics) = SPIRV::compile_preset_passes::<SpirvCompilation, SpirvCross, Box<dyn Error>>(
//! preset.passes, &preset.textures)?;
//! preset.shaders, &preset.textures)?;
//! Ok((passes, semantics))
//! }
//! ```
@ -43,7 +43,9 @@
//! librashader-reflect is designed to be compiler-agnostic. [naga](https://docs.rs/naga/latest/naga/index.html),
//! a pure-Rust shader compiler, as well as SPIRV-Cross via [SpirvCompilation](crate::front::SpirvCompilation)
//! is supported.
#![cfg_attr(not(feature = "stable"), feature(impl_trait_in_assoc_type))]
#![feature(impl_trait_in_assoc_type)]
#![feature(let_chains)]
/// Shader codegen backends.
pub mod back;
/// Error types.

View file

@ -2,173 +2,138 @@ use crate::back::glsl::CrossGlslContext;
use crate::back::targets::GLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
use spirv::Decoration;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
use spirv_cross2::compile::CompilableTarget;
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, SpirvCrossError};
pub(crate) type GlslReflect = CrossReflect<spirv_cross::glsl::Target>;
pub(crate) type GlslReflect = CrossReflect<targets::Glsl>;
impl CompileShader<GLSL> for CrossReflect<targets::Glsl> {
type Options = spirv_cross2::compile::glsl::GlslVersion;
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 = targets::Glsl::options();
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;
options.es_default_float_precision_highp = true;
options.es_default_int_precision_highp = true;
options.enable_420pack_extension = false;
self.vertex.set_compiler_options(&options)?;
self.fragment.set_compiler_options(&options)?;
let vertex_resources = self.vertex.shader_resources()?;
let fragment_resources = self.fragment.shader_resources()?;
let vertex_resources = self.vertex.get_shader_resources()?;
let fragment_resources = self.fragment.get_shader_resources()?;
for res in vertex_resources.resources_for_type(ResourceType::StageOutput)? {
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
.set_decoration(res.id, Decoration::Location, DecorationValue::unset())?;
self.vertex.unset_decoration(res.id, Decoration::Location)?;
}
for res in fragment_resources.resources_for_type(ResourceType::StageInput)? {
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
.set_decoration(res.id, Decoration::Location, DecorationValue::unset())?;
.unset_decoration(res.id, Decoration::Location)?;
}
let vertex_pcb = vertex_resources.resources_for_type(ResourceType::PushConstant)?;
if vertex_pcb.len() > 1 {
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in vertex_pcb {
for res in &vertex_resources.push_constant_buffers {
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.id, c"LIBRA_PUSH_VERTEX_INSTANCE")?;
self.vertex
.set_name(res.base_type_id, c"LIBRA_PUSH_VERTEX")?;
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
}
// todo: options
let _flatten = false;
let vertex_ubo = vertex_resources.resources_for_type(ResourceType::UniformBuffer)?;
if vertex_ubo.len() > 1 {
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in vertex_ubo {
for res in &vertex_resources.uniform_buffers {
// if flatten {
// self.vertex.flatten_buffer_block(res.id)?;
// }
self.vertex.set_name(res.id, c"LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
self.vertex
.set_name(res.base_type_id, c"LIBRA_UBO_VERTEX")?;
self.vertex.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
self.vertex
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
.unset_decoration(res.id, Decoration::DescriptorSet)?;
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
}
let fragment_pcb = fragment_resources.resources_for_type(ResourceType::PushConstant)?;
if fragment_pcb.len() > 1 {
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
for res in fragment_pcb {
for res in &fragment_resources.push_constant_buffers {
self.fragment
.set_name(res.id, c"LIBRA_PUSH_FRAGMENT_INSTANCE")?;
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, c"LIBRA_PUSH_FRAGMENT")?;
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
}
let fragment_ubo = fragment_resources.resources_for_type(ResourceType::UniformBuffer)?;
if fragment_ubo.len() > 1 {
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
for res in fragment_ubo {
for res in &fragment_resources.uniform_buffers {
// if flatten {
// self.fragment.flatten_buffer_block(res.id)?;
// }
self.fragment
.set_name(res.id, c"LIBRA_UBO_FRAGMENT_INSTANCE")?;
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
self.fragment
.set_name(res.base_type_id, c"LIBRA_UBO_FRAGMENT")?;
self.fragment.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
self.fragment
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
.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.resources_for_type(ResourceType::SampledImage)? {
let Some(DecorationValue::Literal(binding)) =
self.fragment.decoration(res.id, Decoration::Binding)?
else {
continue;
};
self.fragment.set_decoration(
res.id,
Decoration::DescriptorSet,
DecorationValue::unset(),
)?;
for res in fragment_resources.sampled_images {
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
self.fragment
.set_decoration(res.id, Decoration::Binding, DecorationValue::unset())?;
let name = res.name.to_string();
.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));
}
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
Ok(ShaderCompilerOutput {
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossGlslContext {
sampler_bindings: texture_fixups,
artifact: CompiledProgram {
vertex: vertex_compiled,
fragment: fragment_compiled,
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Glsl> as CompileShader<GLSL>>::compile(*self, options)
}
}

View file

@ -2,17 +2,14 @@ use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssign
use crate::back::targets::HLSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
use spirv::Decoration;
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
use spirv_cross::spirv::Decoration;
use spirv_cross::ErrorCode;
use spirv_cross2::compile::hlsl::HlslShaderModel;
use spirv_cross2::compile::CompilableTarget;
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, SpirvCrossError};
pub(crate) type HlslReflect = CrossReflect<spirv_cross::hlsl::Target>;
pub(crate) type HlslReflect = CrossReflect<targets::Hlsl>;
impl CompileShader<HLSL> for CrossReflect<targets::Hlsl> {
impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
type Options = Option<HlslShaderModel>;
type Context = CrossHlslContext;
@ -20,107 +17,98 @@ impl CompileShader<HLSL> for CrossReflect<targets::Hlsl> {
mut self,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
let sm = options.unwrap_or(HlslShaderModel::ShaderModel5_0);
let mut options = targets::Hlsl::options();
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.shader_resources()?;
let fragment_resources = self.fragment.shader_resources()?;
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();
let mut vertex_ubo = vertex_resources.resources_for_type(ResourceType::UniformBuffer)?;
if vertex_ubo.len() > 1 {
if vertex_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = vertex_ubo.next() {
if let Some(buf) = vertex_resources.uniform_buffers.first() {
vertex_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.to_string(),
id: buf.id.id(),
name: buf.name.clone(),
id: buf.id,
})
}
let mut vertex_pcb = vertex_resources.resources_for_type(ResourceType::PushConstant)?;
if vertex_pcb.len() > 1 {
if vertex_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = vertex_pcb.next() {
if let Some(buf) = vertex_resources.push_constant_buffers.first() {
vertex_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.to_string(),
id: buf.id.id(),
name: buf.name.clone(),
id: buf.id,
})
}
let mut fragment_ubo =
fragment_resources.resources_for_type(ResourceType::UniformBuffer)?;
if fragment_ubo.len() > 1 {
if fragment_resources.uniform_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one uniform buffer",
)),
));
}
if let Some(buf) = fragment_ubo.next() {
if let Some(buf) = fragment_resources.uniform_buffers.first() {
fragment_buffer_assignment.ubo = Some(HlslBufferAssignment {
name: buf.name.to_string(),
id: buf.id.id(),
name: buf.name.clone(),
id: buf.id,
})
}
let mut fragment_pcb = fragment_resources.resources_for_type(ResourceType::PushConstant)?;
if fragment_pcb.len() > 1 {
if fragment_resources.push_constant_buffers.len() > 1 {
return Err(ShaderCompileError::SpirvCrossCompileError(
SpirvCrossError::InvalidArgument(String::from(
ErrorCode::CompilationError(String::from(
"Cannot have more than one push constant buffer",
)),
));
}
if let Some(buf) = fragment_pcb.next() {
if let Some(buf) = fragment_resources.push_constant_buffers.first() {
fragment_buffer_assignment.push = Some(HlslBufferAssignment {
name: buf.name.to_string(),
id: buf.id.id(),
name: buf.name.clone(),
id: buf.id,
})
}
if sm == HlslShaderModel::ShaderModel3_0 {
for res in fragment_resources.resources_for_type(ResourceType::SampledImage)? {
let Some(DecorationValue::Literal(binding)) =
self.fragment.decoration(res.id, Decoration::Binding)?
else {
continue;
};
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}"))?;
.set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?;
// self.fragment
// .unset_decoration(res.id, Decoration::Binding)?;
}
}
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
Ok(ShaderCompilerOutput {
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossHlslContext {
artifact: CompiledProgram {
vertex: vertex_compiled,
fragment: fragment_compiled,
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
vertex_buffers: vertex_buffer_assignment,
@ -128,11 +116,4 @@ impl CompileShader<HLSL> for CrossReflect<targets::Hlsl> {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Hlsl> as CompileShader<HLSL>>::compile(*self, options)
}
}

View file

@ -9,7 +9,6 @@ pub mod msl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::SpirvCompilation;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
@ -17,15 +16,13 @@ use crate::reflect::semantics::{
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader};
use librashader_common::map::ShortString;
use spirv_cross2::compile::CompiledArtifact;
use spirv_cross2::reflect::{
AllResources, BitWidth, DecorationValue, Resource, Scalar, ScalarKind, TypeInner,
};
use spirv_cross2::spirv::Decoration;
use spirv_cross2::Compiler;
use spirv_cross2::Module;
use std::fmt::Debug;
use std::ops::Deref;
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
use spirv_cross::ErrorCode;
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
/// Reflect shaders under SPIRV-Cross semantics.
///
@ -33,115 +30,135 @@ use std::fmt::Debug;
#[derive(Debug)]
pub struct SpirvCross;
// todo: make this under a mutex
// This is "probably" OK.
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_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
vertex: Compiler<T>,
fragment: Compiler<T>,
vertex: Ast<T>,
fragment: Ast<T>,
}
///The output of the SPIR-V AST after compilation.
///
/// This output is immutable and can not be recompiled later.
pub struct CompiledAst<T: spirv_cross::spirv::Target>(Ast<T>);
impl<T: spirv_cross::spirv::Target> Deref for CompiledAst<T> {
type Target = Ast<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// The compiled SPIR-V program after compilation.
pub struct CompiledProgram<T>
where
T: spirv_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
{
pub vertex: CompiledArtifact<T>,
pub fragment: CompiledArtifact<T>,
pub vertex: CompiledAst<T>,
pub fragment: CompiledAst<T>,
}
impl ValidateTypeSemantics<TypeInner<'_>> for UniqueSemantics {
fn validate_type(&self, ty: &TypeInner) -> Option<TypeInfo> {
let (TypeInner::Vector { .. } | TypeInner::Scalar { .. } | TypeInner::Matrix { .. }) = *ty
impl ValidateTypeSemantics<Type> for UniqueSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let (Type::Float {
ref array,
vecsize,
columns,
..
}
| Type::Int {
ref array,
vecsize,
columns,
..
}
| Type::UInt {
ref array,
vecsize,
columns,
..
}) = *ty
else {
return None;
};
match self {
if !array.is_empty() {
return None;
}
let valid = match self {
UniqueSemantics::MVP => {
if matches!(ty, TypeInner::Matrix { columns, rows, scalar: Scalar { size, .. } } if *columns == 4
&& *rows == 4 && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 4,
columns: 4,
});
}
matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 4
}
UniqueSemantics::FrameCount
| UniqueSemantics::Rotation
| UniqueSemantics::CurrentSubFrame
| UniqueSemantics::TotalSubFrames => {
// Uint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Uint && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
| UniqueSemantics::TotalSubFrames
| UniqueSemantics::CurrentSubFrame => {
matches!(ty, Type::UInt { .. }) && vecsize == 1 && columns == 1
}
UniqueSemantics::FrameDirection => {
// iint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Int && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
matches!(ty, Type::Int { .. }) && vecsize == 1 && columns == 1
}
UniqueSemantics::FloatParameter => {
// Float32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, size }) if *kind == ScalarKind::Float && *size == BitWidth::Word)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
_ => {
if matches!(ty, TypeInner::Vector { scalar: Scalar { size, kind }, width: vecwidth, .. }
if *kind == ScalarKind::Float && *size == BitWidth::Word && *vecwidth == 4)
{
return Some(TypeInfo {
size: 4,
columns: 1,
});
}
matches!(ty, Type::Float { .. }) && vecsize == 1 && columns == 1
}
_ => matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 1,
};
None
if valid {
Some(TypeInfo {
size: vecsize,
columns,
})
} else {
None
}
}
}
impl ValidateTypeSemantics<TypeInner<'_>> for TextureSemantics {
fn validate_type(&self, ty: &TypeInner) -> Option<TypeInfo> {
let TypeInner::Vector {
scalar: Scalar { size, kind },
width: vecwidth,
} = ty
impl ValidateTypeSemantics<Type> for TextureSemantics {
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
let Type::Float {
ref array,
vecsize,
columns,
..
} = *ty
else {
return None;
};
if *kind == ScalarKind::Float && *size == BitWidth::Word && *vecwidth == 4 {
return Some(TypeInfo {
size: 4,
columns: 1,
});
if !array.is_empty() {
return None;
}
None
if vecsize == 4 && columns == 1 {
Some(TypeInfo {
size: vecsize,
columns,
})
} else {
None
}
}
}
impl<T> TryFrom<&SpirvCompilation> for CrossReflect<T>
where
T: spirv_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
type Error = ShaderReflectError;
@ -149,8 +166,8 @@ where
let vertex_module = Module::from_words(&value.vertex);
let fragment_module = Module::from_words(&value.fragment);
let vertex = Compiler::new(vertex_module)?;
let fragment = Compiler::new(fragment_module)?;
let vertex = Ast::parse(&vertex_module)?;
let fragment = Ast::parse(&fragment_module)?;
Ok(CrossReflect { vertex, fragment })
}
@ -158,12 +175,14 @@ where
impl<T> CrossReflect<T>
where
T: spirv_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
fn validate_semantics(
fn validate(
&self,
vertex_res: &AllResources,
fragment_res: &AllResources,
vertex_res: &ShaderResources,
fragment_res: &ShaderResources,
) -> Result<(), ShaderReflectError> {
if !vertex_res.sampled_images.is_empty()
|| !vertex_res.storage_buffers.is_empty()
@ -200,15 +219,9 @@ where
));
}
let Some(DecorationValue::Literal(fragment_location)) = self
let fragment_location = self
.fragment
.decoration(fragment_res.stage_outputs[0].id, Decoration::Location)?
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
.get_decoration(fragment_res.stage_outputs[0].id, Decoration::Location)?;
if fragment_location != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidLocation(fragment_location),
@ -216,54 +229,15 @@ where
}
// Ensure that vertex attributes use location 0 and 1
// Verify Vertex inputs
'vertex: {
let entry_points = self.vertex.entry_points()?;
if entry_points.len() != 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidEntryPointCount(entry_points.len()),
));
}
let vert_inputs = vertex_res.stage_inputs.len();
if vert_inputs != 2 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidInputCount(vert_inputs),
));
}
for input in &vertex_res.stage_inputs {
let location = self.vertex.decoration(input.id, Decoration::Location)?;
let Some(DecorationValue::Literal(location)) = location else {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if location == 0 {
let pos_type = &self.vertex.type_description(input.base_type_id)?;
if !matches!(pos_type.inner, TypeInner::Vector { width, ..} if width == 4) {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
if location == 1 {
let coord_type = &self.vertex.type_description(input.base_type_id)?;
if !matches!(coord_type.inner, TypeInner::Vector { width, ..} if width == 2) {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| {
Ok::<u32, ErrorCode>(
mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?,
)
})?;
if vert_mask != 0x3 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(vert_mask),
));
}
if vertex_res.uniform_buffers.len() > 1 {
@ -297,27 +271,17 @@ where
impl<T> CrossReflect<T>
where
T: spirv_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
fn get_ubo_data(
ast: &Compiler<T>,
ast: &Ast<T>,
ubo: &Resource,
blame: SemanticErrorBlame,
) -> Result<UboData, ShaderReflectError> {
let Some(descriptor_set) = ast
.decoration(ubo.id, Decoration::DescriptorSet)?
.and_then(|l| l.as_literal())
else {
return Err(blame.error(SemanticsErrorKind::MissingBinding));
};
let Some(binding) = ast
.decoration(ubo.id, Decoration::Binding)?
.and_then(|l| l.as_literal())
else {
return Err(blame.error(SemanticsErrorKind::MissingBinding));
};
let descriptor_set = ast.get_decoration(ubo.id, Decoration::DescriptorSet)?;
let binding = ast.get_decoration(ubo.id, Decoration::Binding)?;
if binding >= MAX_BINDINGS_COUNT {
return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding)));
}
@ -325,19 +289,21 @@ where
return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(descriptor_set)));
}
let size = ast.type_description(ubo.base_type_id)?.size_hint.declared() as u32;
Ok(UboData { binding, size })
let size = ast.get_declared_struct_size(ubo.base_type_id)?;
Ok(UboData {
// descriptor_set,
// id: ubo.id,
binding,
size,
})
}
fn get_push_size(
ast: &Compiler<T>,
ast: &Ast<T>,
push: &Resource,
blame: SemanticErrorBlame,
) -> Result<u32, ShaderReflectError> {
let size = ast
.type_description(push.base_type_id)?
.size_hint
.declared() as u32;
let size = ast.get_declared_struct_size(push.base_type_id)?;
if size > MAX_PUSH_BUFFER_SIZE {
return Err(blame.error(SemanticsErrorKind::InvalidPushBufferSize(size)));
}
@ -345,7 +311,7 @@ where
}
fn reflect_buffer_range_metas(
ast: &Compiler<T>,
ast: &Ast<T>,
resource: &Resource,
pass_number: usize,
semantics: &ShaderSemantics,
@ -353,43 +319,35 @@ where
offset_type: UniformMemberBlock,
blame: SemanticErrorBlame,
) -> Result<(), ShaderReflectError> {
let ranges = ast.active_buffer_ranges(resource.id)?;
let ranges = ast.get_active_buffer_ranges(resource.id)?;
for range in ranges {
let Some(name) = ast.member_name(resource.base_type_id, range.index)? else {
// member has no name!
return Err(blame.error(SemanticsErrorKind::InvalidRange(range.index)));
};
let ubo_type = ast.type_description(resource.base_type_id)?;
let range_type = match ubo_type.inner {
TypeInner::Struct(struct_def) => {
let range_type = struct_def
.members
let name = ast.get_member_name(resource.base_type_id, range.index)?;
let ubo_type = ast.get_type(resource.base_type_id)?;
let range_type = match ubo_type {
Type::Struct { member_types, .. } => {
let range_type = member_types
.get(range.index as usize)
.cloned()
.ok_or(blame.error(SemanticsErrorKind::InvalidRange(range.index)))?;
ast.type_description(range_type.id)?
ast.get_type(range_type)?
}
_ => return Err(blame.error(SemanticsErrorKind::InvalidResourceType)),
};
if let Some(parameter) = semantics.uniform_semantics.unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&range_type.inner) else {
return Err(
blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name.to_string()))
);
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = range.offset;
if let Some(meta) = meta.parameter_meta.get_mut::<str>(&name.as_ref()) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
if let Some(meta) = meta.parameter_meta.get_mut(&name) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name.to_string(),
semantic: name,
expected,
received: offset,
ty: offset_type,
@ -398,7 +356,7 @@ where
}
if meta.size != typeinfo.size {
return Err(ShaderReflectError::MismatchedSize {
semantic: name.to_string(),
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
@ -407,7 +365,6 @@ where
*meta.offset.offset_mut(offset_type) = Some(offset);
} else {
let name = ShortString::from(name.as_ref());
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
@ -421,13 +378,11 @@ where
semantics => {
let offset = range.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name.to_string(),
semantic: name,
expected,
received: offset,
ty: offset_type,
@ -436,7 +391,7 @@ where
}
if meta.size != typeinfo.size * typeinfo.columns {
return Err(ShaderReflectError::MismatchedSize {
semantic: name.to_string(),
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
@ -448,7 +403,7 @@ where
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: ShortString::from(name.as_ref()),
id: name,
offset: MemberOffset::new(offset, offset_type),
size: typeinfo.size * typeinfo.columns,
},
@ -456,11 +411,9 @@ where
}
}
}
} else if let Some(texture) = semantics.uniform_semantics.texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&range_type.inner) else {
return Err(
blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name.to_string()))
);
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&range_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
if let TextureSemantics::PassOutput = texture.semantics {
@ -474,13 +427,11 @@ where
let offset = range.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset)
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name.to_string(),
semantic: name,
expected,
received: offset,
ty: offset_type,
@ -503,12 +454,12 @@ where
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
},
id: ShortString::from(name.as_ref()),
id: name,
},
);
}
} else {
return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name.to_string())));
return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name)));
}
}
Ok(())
@ -521,12 +472,12 @@ where
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
if let Some(vertex_ubo) = vertex_ubo {
self.vertex
.set_decoration(vertex_ubo.id, Decoration::Binding, Some(0))?;
.set_decoration(vertex_ubo.id, Decoration::Binding, 0)?;
}
if let Some(fragment_ubo) = fragment_ubo {
self.fragment
.set_decoration(fragment_ubo.id, Decoration::Binding, Some(0))?;
.set_decoration(fragment_ubo.id, Decoration::Binding, 0)?;
}
match (vertex_ubo, fragment_ubo) {
@ -578,7 +529,10 @@ where
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics.texture_semantics.texture_semantic(texture.name) else {
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
@ -606,25 +560,12 @@ where
&'a self,
texture: &'a Resource,
) -> Result<TextureData<'a>, ShaderReflectError> {
let Some(descriptor_set) = self
let descriptor_set = self
.fragment
.decoration(texture.id, Decoration::DescriptorSet)?
.and_then(|l| l.as_literal())
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
let Some(binding) = self
.get_decoration(texture.id, Decoration::DescriptorSet)?;
let binding = self
.fragment
.decoration(texture.id, Decoration::Binding)?
.and_then(|l| l.as_literal())
else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
.get_decoration(texture.id, Decoration::Binding)?;
if descriptor_set != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidDescriptorSet(descriptor_set),
@ -651,12 +592,12 @@ where
) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
if let Some(vertex_pcb) = vertex_pcb {
self.vertex
.set_decoration(vertex_pcb.id, Decoration::Binding, Some(1))?;
.set_decoration(vertex_pcb.id, Decoration::Binding, 1)?;
}
if let Some(fragment_pcb) = fragment_pcb {
self.fragment
.set_decoration(fragment_pcb.id, Decoration::Binding, Some(1))?;
.set_decoration(fragment_pcb.id, Decoration::Binding, 1)?;
}
match (vertex_pcb, fragment_pcb) {
@ -705,16 +646,18 @@ where
impl<T> ReflectShader for CrossReflect<T>
where
T: spirv_cross2::compile::CompilableTarget,
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
Ast<T>: spirv_cross::spirv::Parse<T>,
{
fn reflect(
&mut self,
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
let vertex_res = self.vertex.shader_resources()?.all_resources()?;
let fragment_res = self.fragment.shader_resources()?.all_resources()?;
self.validate_semantics(&vertex_res, &fragment_res)?;
let vertex_res = self.vertex.get_shader_resources()?;
let fragment_res = self.fragment.get_shader_resources()?;
self.validate(&vertex_res, &fragment_res)?;
let vertex_ubo = vertex_res.uniform_buffers.first();
let fragment_ubo = fragment_res.uniform_buffers.first();
@ -797,23 +740,6 @@ where
meta,
})
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
let vertex_res = self.vertex.shader_resources()?.all_resources()?;
let fragment_res = self.fragment.shader_resources()?.all_resources()?;
self.validate_semantics(&vertex_res, &fragment_res)?;
let vertex_ubo = vertex_res.uniform_buffers.first();
let fragment_ubo = fragment_res.uniform_buffers.first();
self.reflect_ubos(vertex_ubo, fragment_ubo)?;
let vertex_push = vertex_res.push_constant_buffers.first();
let fragment_push = fragment_res.push_constant_buffers.first();
self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
Ok(())
}
}
#[cfg(test)]
@ -827,53 +753,59 @@ mod test {
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::front::{Glslang, ShaderInputCompiler};
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
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: FastHashMap<ShortString, 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 spirv = Glslang::compile(&result).unwrap();
// let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
// let shader_reflection = reflect
// .reflect(
// 0,
// &ShaderSemantics {
// uniform_semantics,
// texture_semantics: Default::default(),
// },
// )
// .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();
// // rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// // let module = loader.module();
// // println!("{:#}", module.disassemble());
// }
#[test]
pub fn test_into() {
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let spirv = Glslang::compile(&result).unwrap();
let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
let shader_reflection = reflect
.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.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();
// rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// let module = loader.module();
// println!("{:#}", module.disassemble());
}
}

View file

@ -2,112 +2,103 @@ use crate::back::msl::CrossMslContext;
use crate::back::targets::MSL;
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::error::ShaderCompileError;
use crate::reflect::cross::{CompiledProgram, CrossReflect};
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;
use spirv::Decoration;
use spirv_cross2::compile::msl::{BindTarget, ResourceBinding};
use spirv_cross2::compile::{msl, CompilableTarget};
use spirv_cross2::reflect::{DecorationValue, ResourceType};
use spirv_cross2::{targets, Compiler};
pub(crate) type MslReflect = CrossReflect<spirv_cross::msl::Target>;
pub(crate) type MslReflect = CrossReflect<targets::Msl>;
impl CompileShader<MSL> for CrossReflect<targets::Msl> {
type Options = Option<msl::MslVersion>;
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::MslVersion::new(2, 0, 0));
let mut options = targets::Msl::options();
options.version = version;
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: &mut Compiler<targets::Msl>,
stage: spirv::ExecutionModel,
ast: &Ast<msl::Target>,
stage: ExecutionModel,
binding_map: &mut BTreeMap<ResourceBindingLocation, ResourceBinding>,
) -> Result<(), ShaderCompileError> {
let resources = ast.shader_resources()?;
for resource in resources.resources_for_type(ResourceType::PushConstant)? {
let Some(DecorationValue::Literal(buffer)) =
ast.decoration(resource.id, Decoration::Binding)?
else {
continue;
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
};
ast.add_resource_binding(
stage,
ResourceBinding::PushConstantBuffer,
&BindTarget {
buffer,
texture: 0,
sampler: 0,
count: None,
},
)?
binding_map.insert(location, overridden);
}
let ubos = resources.resources_for_type(ResourceType::UniformBuffer)?;
let sampled = resources.resources_for_type(ResourceType::SampledImage)?;
for resource in ubos.chain(sampled) {
let Some(DecorationValue::Literal(binding)) =
ast.decoration(resource.id, Decoration::Binding)?
else {
continue;
};
let Some(DecorationValue::Literal(desc_set)) =
ast.decoration(resource.id, Decoration::DescriptorSet)?
else {
continue;
};
let overridden = BindTarget {
buffer: binding,
texture: binding,
sampler: binding,
count: None,
};
ast.add_resource_binding(
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,
ResourceBinding::Qualified {
set: desc_set,
binding,
},
&overridden,
)?
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(&mut self.vertex, spirv::ExecutionModel::Vertex)?;
set_bindings(
&self.vertex,
ExecutionModel::Vertex,
&mut vert_options.resource_binding_overrides,
)?;
set_bindings(&mut self.fragment, spirv::ExecutionModel::Fragment)?;
set_bindings(
&self.fragment,
ExecutionModel::Fragment,
&mut frag_options.resource_binding_overrides,
)?;
let vertex_compiled = self.vertex.compile(&options)?;
let fragment_compiled = self.fragment.compile(&options)?;
self.vertex.set_compiler_options(&vert_options)?;
self.fragment.set_compiler_options(&frag_options)?;
Ok(ShaderCompilerOutput {
vertex: vertex_compiled.to_string(),
fragment: fragment_compiled.to_string(),
vertex: self.vertex.compile()?,
fragment: self.fragment.compile()?,
context: CrossMslContext {
artifact: CompiledProgram {
vertex: vertex_compiled,
fragment: fragment_compiled,
vertex: CompiledAst(self.vertex),
fragment: CompiledAst(self.fragment),
},
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<CrossReflect<targets::Msl> as CompileShader<MSL>>::compile(*self, options)
}
}
#[cfg(test)]
@ -115,21 +106,26 @@ 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, ShortString};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
use spirv_cross2::compile::msl::MslVersion;
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 result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
@ -155,7 +151,7 @@ mod test {
)
.expect("");
let compiled = msl.compile(Some(MslVersion::new(2, 0, 0))).unwrap();
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!("{}", compiled.vertex);
}

View file

@ -25,9 +25,6 @@ pub trait ReflectShader {
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError>;
/// Validate the shader without doing reflection against a set of semantics.
fn validate(&mut self) -> Result<(), ShaderReflectError>;
}
pub use semantics::ShaderReflection;

View file

@ -10,8 +10,16 @@ pub mod wgsl;
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use std::fmt::Debug;
use crate::front::spirv_passes::lower_samplers;
use crate::front::SpirvCompilation;
use naga::{
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,
@ -20,14 +28,6 @@ use crate::reflect::semantics::{
MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader, ShaderReflection};
use librashader_common::map::ShortString;
use naga::{
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;
/// Reflect under Naga semantics
///
@ -212,7 +212,7 @@ impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
}
};
None
return None;
}
}
@ -239,7 +239,7 @@ impl ValidateTypeSemantics<&TypeInner> for TextureSemantics {
impl NagaReflect {
fn reflect_ubos(
&self,
&mut self,
vertex_ubo: Option<Handle<GlobalVariable>>,
fragment_ubo: Option<Handle<GlobalVariable>>,
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
@ -429,7 +429,7 @@ impl NagaReflect {
}
}
fn validate_semantics(&self) -> Result<(), ShaderReflectError> {
fn validate(&self) -> Result<(), ShaderReflectError> {
// Verify types
if self.vertex.global_variables.iter().any(|(_, gv)| {
let ty = &self.vertex.types[gv.ty];
@ -566,7 +566,7 @@ impl NagaReflect {
};
let &Some(Binding::Location { location, .. }) = &frag_output.binding else {
return Err(ShaderReflectError::FragmentSemanticError(
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
@ -670,7 +670,7 @@ impl NagaReflect {
let member_type = &module.types[member.ty].inner;
if let Some(parameter) = semantics.uniform_semantics.unique_semantic(&name) {
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
let Some(typeinfo) = parameter.semantics.validate_type(&member_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
@ -678,11 +678,9 @@ impl NagaReflect {
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = member.offset;
if let Some(meta) = meta.parameter_meta.get_mut::<str>(name.as_ref()) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
if let Some(meta) = meta.parameter_meta.get_mut(&name) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -703,7 +701,6 @@ impl NagaReflect {
*meta.offset.offset_mut(offset_type) = Some(offset as usize);
} else {
let name = ShortString::from(name);
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
@ -717,10 +714,8 @@ impl NagaReflect {
semantics => {
let offset = member.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -744,7 +739,7 @@ impl NagaReflect {
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: ShortString::from(name),
id: name,
offset: MemberOffset::new(offset as usize, offset_type),
size: typeinfo.size * typeinfo.columns,
},
@ -752,7 +747,7 @@ impl NagaReflect {
}
}
}
} else if let Some(texture) = semantics.uniform_semantics.texture_semantic(&name) {
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
let Some(_typeinfo) = texture.semantics.validate_type(&member_type) else {
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
};
@ -768,10 +763,8 @@ impl NagaReflect {
let offset = member.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if let Some(expected) = meta
.offset
.offset(offset_type)
.filter(|expected| *expected != offset as usize)
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
@ -797,7 +790,7 @@ impl NagaReflect {
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
},
id: ShortString::from(name),
id: name,
},
);
}
@ -851,7 +844,10 @@ impl NagaReflect {
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics.texture_semantics.texture_semantic(texture.name) else {
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
@ -882,7 +878,7 @@ impl ReflectShader for NagaReflect {
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
self.validate_semantics()?;
self.validate()?;
// Validate verifies that there's only one uniform block.
let vertex_ubo = self
@ -1012,41 +1008,10 @@ impl ReflectShader for NagaReflect {
meta,
})
}
fn validate(&mut self) -> Result<(), ShaderReflectError> {
self.validate_semantics()?;
let vertex_push = self
.vertex
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::PushConstant {
Some(handle)
} else {
None
}
});
let fragment_push = self
.fragment
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::PushConstant {
Some(handle)
} else {
None
}
});
self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::front::ShaderInputCompiler;
use crate::reflect::semantics::{Semantic, TextureSemantics, UniformSemantic};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
@ -1054,19 +1019,18 @@ mod test {
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/slang-shaders/misc/shaders/simple_color_controls.slang").unwrap();
// let compilation = crate::front::Glslang::compile(&result).unwrap();
// let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
//
// crate::front::n
// // 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();
// 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

@ -8,9 +8,19 @@ use naga::back::msl::{
};
use naga::valid::{Capabilities, ValidationFlags};
use naga::{Module, TypeInner};
use spirv_cross::msl::Version;
fn msl_version_to_naga_msl(version: MslVersion) -> (u8, u8) {
(version.major as u8, version.minor as 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 {
@ -23,7 +33,7 @@ impl CompileShader<MSL> for NagaReflect {
) -> 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::new(2, 0, 0)));
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::V2_0));
let mut vert_options = Options {
lang_version,
@ -146,13 +156,6 @@ impl CompileShader<MSL> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<MSL>>::compile(*self, options)
}
}
#[cfg(test)]
@ -163,44 +166,47 @@ mod test {
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
use crate::reflect::ReflectShader;
use bitflags::Flags;
use librashader_common::map::{FastHashMap, ShortString};
use librashader_common::map::FastHashMap;
use librashader_preprocess::ShaderSource;
// use spirv_cross::msl;
use spirv_cross::msl;
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/basic.slang").unwrap();
//
// let mut uniform_semantics: FastHashMap<ShortString, 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
// );
// }
#[test]
pub fn test_into() {
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
"../test/basic.slang".into(),
))
.unwrap();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
for (_index, param) in result.parameters.iter().enumerate() {
uniform_semantics.insert(
param.1.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
msl.reflect(
0,
&ShaderSemantics {
uniform_semantics,
texture_semantics: Default::default(),
},
)
.expect("");
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
println!(
"{:?}",
compiled.context.fragment.translation_info.entry_point_names
);
}
}

View file

@ -51,11 +51,4 @@ impl CompileShader<SPIRV> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<SPIRV>>::compile(*self, options)
}
}

View file

@ -38,11 +38,4 @@ impl CompileShader<WGSL> for NagaReflect {
},
})
}
fn compile_boxed(
self: Box<Self>,
options: Self::Options,
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
<NagaReflect as CompileShader<WGSL>>::compile(*self, options)
}
}

View file

@ -5,10 +5,9 @@ use crate::front::{ShaderInputCompiler, ShaderReflectObject};
use crate::reflect::semantics::{
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
};
use librashader_common::map::{FastHashMap, ShortString};
use librashader_pack::PassResource;
use librashader_common::map::FastHashMap;
use librashader_preprocess::{PreprocessError, ShaderSource};
use librashader_presets::{ShaderPreset, TextureMeta};
use librashader_presets::{ShaderPassConfig, TextureConfig};
/// Artifacts of a reflected and compiled shader pass.
///
@ -28,7 +27,7 @@ use librashader_presets::{ShaderPreset, TextureMeta};
/// ```
///
/// This allows a runtime to not name the backing type of the compiled artifact if not necessary.
pub type ShaderPassArtifact<T> = (PassResource, CompilerBackend<T>);
pub type ShaderPassArtifact<T> = (ShaderPassConfig, ShaderSource, CompilerBackend<T>);
impl<T: OutputTarget> CompilePresetTarget for T {}
@ -37,9 +36,9 @@ 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<'a, I, R, E>(
passes: impl IntoIterator<Item = PassResource>,
textures: impl Iterator<Item = &'a TextureMeta>,
fn compile_preset_passes<I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<Self as FromCompilation<I, R>>::Output>>,
@ -62,9 +61,9 @@ 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<'a, T, I, R, E>(
passes: impl IntoIterator<Item = PassResource>,
textures: impl Iterator<Item = &'a TextureMeta>,
fn compile_preset_passes<T, I, R, E>(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<
(
Vec<ShaderPassArtifact<<T as FromCompilation<I, R>>::Output>>,
@ -81,15 +80,15 @@ where
E: From<ShaderReflectError>,
E: From<ShaderCompileError>,
{
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>> =
Default::default();
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
let artifacts = passes
let passes = passes
.into_iter()
.map(|shader| {
let source = &shader.data;
let compiled = I::Compiler::compile(source)?;
let source: ShaderSource = ShaderSource::load(&shader.name)?;
let compiled = I::Compiler::compile(&source)?;
let reflect = T::from_compilation(compiled)?;
for parameter in source.parameters.values() {
@ -101,25 +100,13 @@ where
}),
);
}
Ok::<_, E>((shader, reflect))
Ok::<_, E>((shader, source, reflect))
})
.collect::<Result<Vec<(PassResource, CompilerBackend<_>)>, E>>()?;
.collect::<Result<Vec<(ShaderPassConfig, ShaderSource, CompilerBackend<_>)>, E>>()?;
for (pass, _) in artifacts.iter() {
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
pass.meta.alias.as_ref(),
pass.meta.id as usize,
);
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
pass.data.name.as_ref(),
pass.meta.id as usize,
);
for details in &passes {
insert_pass_semantics(&mut uniform_semantics, &mut texture_semantics, &details.0)
}
insert_lut_semantics(textures, &mut uniform_semantics, &mut texture_semantics);
let semantics = ShaderSemantics {
@ -127,17 +114,16 @@ where
texture_semantics,
};
Ok((artifacts, semantics))
Ok((passes, semantics))
}
/// Insert the available semantics for the input pass config into the provided semantic maps.
fn insert_pass_semantics(
uniform_semantics: &mut FastHashMap<ShortString, UniformSemantic>,
texture_semantics: &mut FastHashMap<ShortString, Semantic<TextureSemantics>>,
alias: Option<&ShortString>,
index: usize,
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
config: &ShaderPassConfig,
) {
let Some(alias) = alias else {
let Some(alias) = &config.alias else {
return;
};
@ -146,6 +132,8 @@ fn insert_pass_semantics(
return;
}
let index = config.id as usize;
// PassOutput
texture_semantics.insert(
alias.clone(),
@ -154,32 +142,24 @@ fn insert_pass_semantics(
index,
},
);
let mut alias_size = alias.clone();
alias_size.push_str("Size");
uniform_semantics.insert(
alias_size,
format!("{alias}Size"),
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::PassOutput,
index,
}),
);
let mut alias_feedback = alias.clone();
alias_feedback.push_str("Feedback");
// PassFeedback
texture_semantics.insert(
alias_feedback,
format!("{alias}Feedback"),
Semantic {
semantics: TextureSemantics::PassFeedback,
index,
},
);
let mut alias_feedback_size = alias.clone();
alias_feedback_size.push_str("FeedbackSize");
uniform_semantics.insert(
alias_feedback_size,
format!("{alias}FeedbackSize"),
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::PassFeedback,
index,
@ -188,15 +168,12 @@ fn insert_pass_semantics(
}
/// Insert the available semantics for the input texture config into the provided semantic maps.
fn insert_lut_semantics<'a>(
textures: impl Iterator<Item = &'a TextureMeta>,
uniform_semantics: &mut FastHashMap<ShortString, UniformSemantic>,
texture_semantics: &mut FastHashMap<ShortString, Semantic<TextureSemantics>>,
fn insert_lut_semantics(
textures: &[TextureConfig],
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
) {
for (index, texture) in textures.enumerate() {
let mut size_semantic = texture.name.clone();
size_semantic.push_str("Size");
for (index, texture) in textures.iter().enumerate() {
texture_semantics.insert(
texture.name.clone(),
Semantic {
@ -206,7 +183,7 @@ fn insert_lut_semantics<'a>(
);
uniform_semantics.insert(
size_semantic,
format!("{}Size", texture.name),
UniformSemantic::Texture(Semantic {
semantics: TextureSemantics::User,
index,
@ -214,61 +191,3 @@ fn insert_lut_semantics<'a>(
);
}
}
impl ShaderSemantics {
/// Create pass semantics for a single pass in the given shader preset.
///
/// This is meant as a convenience function for reflection use only.
pub fn create_pass_semantics<E>(
preset: &ShaderPreset,
index: usize,
) -> Result<ShaderSemantics, E>
where
E: From<ShaderReflectError>,
E: From<PreprocessError>,
{
let mut uniform_semantics: FastHashMap<ShortString, UniformSemantic> = Default::default();
let mut texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>> =
Default::default();
let config = preset
.passes
.get(index)
.ok_or_else(|| PreprocessError::InvalidStage)?;
let source = ShaderSource::load(&config.storage)?;
for parameter in source.parameters.values() {
uniform_semantics.insert(
parameter.id.clone(),
UniformSemantic::Unique(Semantic {
semantics: UniqueSemantics::FloatParameter,
index: (),
}),
);
}
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
config.meta.alias.as_ref(),
config.meta.id as usize,
);
insert_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
source.name.as_ref(),
config.meta.id as usize,
);
insert_lut_semantics(
preset.textures.iter().map(|t| &t.meta),
&mut uniform_semantics,
&mut texture_semantics,
);
Ok(ShaderSemantics {
uniform_semantics,
texture_semantics,
})
}
}

View file

@ -1,6 +1,5 @@
use bitflags::bitflags;
use librashader_common::map::{FastHashMap, ShortString};
use std::fmt::{Display, Formatter};
use librashader_common::map::FastHashMap;
use std::str::FromStr;
/// The maximum number of bindings allowed in a shader.
@ -10,7 +9,6 @@ pub const MAX_PUSH_BUFFER_SIZE: u32 = 128;
/// The type of a uniform.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformType {
/// A matrix of 4x4 floats (`mat4`).
Mat4,
@ -28,7 +26,6 @@ pub enum UniformType {
/// that are always available.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[repr(i32)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniqueSemantics {
// mat4, MVP
/// The Model View Projection matrix for the frame.
@ -80,27 +77,6 @@ impl UniqueSemantics {
UniqueSemantics::FloatParameter => UniformType::Float,
}
}
/// Get the name of the semantic as a string.
pub const fn as_str(&self) -> &'static str {
match self {
UniqueSemantics::MVP => "MVP",
UniqueSemantics::Output => "Output",
UniqueSemantics::FinalViewport => "FinalViewport",
UniqueSemantics::FrameCount => "FrameCount",
UniqueSemantics::FrameDirection => "FrameDirection",
UniqueSemantics::Rotation => "Rotation",
UniqueSemantics::TotalSubFrames => "TotalSubFrames",
UniqueSemantics::CurrentSubFrame => "CurrentSubFrame",
UniqueSemantics::FloatParameter => "FloatParameter",
}
}
}
impl Display for UniqueSemantics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Texture semantics relate to input or output textures.
@ -108,7 +84,6 @@ impl Display for UniqueSemantics {
/// Texture semantics are used to relate both texture samplers and `*Size` uniforms.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
#[repr(i32)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TextureSemantics {
/// The original input of the filter chain.
Original = 0,
@ -198,8 +173,6 @@ 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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct BindingStage: u8 {
const NONE = 0b00000000;
const VERTEX = 0b00000001;
@ -208,8 +181,7 @@ bitflags! {
}
/// Reflection information for the Uniform Buffer or Push Constant Block
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct BufferReflection<T> {
/// The binding point for this buffer, if applicable
pub binding: T,
@ -223,7 +195,6 @@ pub struct BufferReflection<T> {
///
/// A uniform can be bound to both the UBO, or as a Push Constant.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemberOffset {
/// The offset of the uniform member within the UBO.
pub ubo: Option<usize>,
@ -232,7 +203,6 @@ pub struct MemberOffset {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// The block where a uniform member is located.
pub enum UniformMemberBlock {
/// The offset is for a UBO.
@ -277,20 +247,19 @@ impl MemberOffset {
}
/// Reflection information about a non-texture related uniform variable.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct VariableMeta {
// this might bite us in the back because retroarch keeps separate UBO/push offsets.. eh
/// The offset of this variable uniform.
pub offset: MemberOffset,
/// The size of the uniform.
pub size: u32,
/// The name of the uniform.
pub id: ShortString,
pub id: String,
}
/// Reflection information about a texture size uniform variable.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct TextureSizeMeta {
// this might bite us in the back because retroarch keeps separate UBO/push offsets..
/// The offset of this size uniform.
@ -298,20 +267,18 @@ pub struct TextureSizeMeta {
/// The mask indicating for which stages the texture size uniform should be bound.
pub stage_mask: BindingStage,
/// The name of the uniform.
pub id: ShortString,
pub id: String,
}
/// Reflection information about texture samplers.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct TextureBinding {
/// The binding index of the texture.
pub binding: u32,
}
/// Reflection information about a shader.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct ShaderReflection {
/// Reflection information about the UBO for this shader.
pub ubo: Option<BufferReflection<u32>>,
@ -351,11 +318,11 @@ impl UniformMeta for TextureSizeMeta {
/// A trait for maps that can return texture semantic units.
pub trait TextureSemanticMap {
/// Get the texture semantic for the given variable name.
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
}
impl TextureSemanticMap for FastHashMap<ShortString, UniformSemantic> {
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
if let Some(semantics) = TextureSemantics::TEXTURE_SEMANTICS
@ -386,8 +353,8 @@ impl TextureSemanticMap for FastHashMap<ShortString, UniformSemantic> {
}
}
impl TextureSemanticMap for FastHashMap<ShortString, Semantic<TextureSemantics>> {
fn texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
match self.get(name) {
None => {
if let Some(semantics) = TextureSemantics::TEXTURE_SEMANTICS
@ -420,11 +387,11 @@ impl TextureSemanticMap for FastHashMap<ShortString, Semantic<TextureSemantics>>
/// A trait for maps that can return unique semantic units.
pub trait UniqueSemanticMap {
/// Get the unique semantic for the given variable name.
fn unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
}
impl UniqueSemanticMap for FastHashMap<ShortString, UniformSemantic> {
fn unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
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
None => match name {
@ -457,7 +424,7 @@ impl UniqueSemanticMap for FastHashMap<ShortString, UniformSemantic> {
index: (),
}),
"CurrentSubFrame" => Some(Semantic {
semantics: UniqueSemantics::CurrentSubFrame,
semantics: UniqueSemantics::TotalSubFrames,
index: (),
}),
_ => None,
@ -470,7 +437,6 @@ impl UniqueSemanticMap for FastHashMap<ShortString, UniformSemantic> {
/// Semantic assignment of a shader uniform to filter chain semantics.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformSemantic {
/// A unique semantic.
Unique(Semantic<UniqueSemantics, ()>),
@ -480,23 +446,21 @@ pub enum UniformSemantic {
/// The runtime provided maps of uniform and texture variables to filter chain semantics.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderSemantics {
/// A map of uniform names to filter chain semantics.
pub uniform_semantics: FastHashMap<ShortString, UniformSemantic>,
pub uniform_semantics: FastHashMap<String, UniformSemantic>,
/// A map of texture names to filter chain semantics.
pub texture_semantics: FastHashMap<ShortString, Semantic<TextureSemantics>>,
pub texture_semantics: FastHashMap<String, Semantic<TextureSemantics>>,
}
/// The binding of a uniform after the shader has been linked.
///
/// Used in combination with [`MemberOffset`] to keep track
/// Used in combination with [`MemberOffset`](crate::reflect::semantics::MemberOffset) to keep track
/// of semantics at each frame pass.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UniformBinding {
/// A user parameter (`float`) binding.
Parameter(ShortString),
Parameter(String),
/// A known semantic binding.
SemanticVariable(UniqueSemantics),
/// A texture size (`float4`) binding.
@ -516,162 +480,14 @@ impl From<Semantic<TextureSemantics>> for UniformBinding {
}
/// Reflection metadata about the various bindings for this shader.
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default)]
pub struct BindingMeta {
#[cfg_attr(feature = "serde", serde(rename = "param"))]
/// A map of parameter names to uniform binding metadata.
pub parameter_meta: FastHashMap<ShortString, VariableMeta>,
#[cfg_attr(feature = "serde", serde(rename = "unique"))]
pub parameter_meta: FastHashMap<String, VariableMeta>,
/// A map of unique semantics to uniform binding metadata.
pub unique_meta: FastHashMap<UniqueSemantics, VariableMeta>,
#[cfg_attr(feature = "serde", serde(rename = "texture"))]
/// A map of texture semantics to texture binding points.
pub texture_meta: FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
#[cfg_attr(feature = "serde", serde(rename = "texture_size"))]
/// A map of texture semantics to texture size uniform binding metadata.
pub texture_size_meta: FastHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde::de::{Deserialize, Visitor};
use serde::ser::Serialize;
use serde::{Deserializer, Serializer};
struct TextureSemanticVisitor;
impl<'de> Visitor<'de> for TextureSemanticVisitor {
type Value = Semantic<TextureSemantics>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a string of the form (Semantic)N?")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
"Original" => Ok(TextureSemantics::Original.semantics(0)),
"Source" => Ok(TextureSemantics::Source.semantics(0)),
other => {
let Some(index) = other.find(|c: char| c.is_digit(10)) else {
return Err(E::custom(format!(
"expected index for indexed texture semantic {v}"
)));
};
let (semantic, index) = other.split_at(index);
let Ok(index) = index.parse::<usize>() else {
return Err(E::custom(format!(
"could not parse index {index} of texture semantic {v}"
)));
};
match semantic {
"OriginalHistory" => Ok(TextureSemantics::OriginalHistory.semantics(index)),
"PassOutput" => Ok(TextureSemantics::PassOutput.semantics(index)),
"PassFeedback" => Ok(TextureSemantics::PassFeedback.semantics(index)),
// everything else (including "User") is a user semantic.
_ => Ok(TextureSemantics::User.semantics(index)),
}
}
}
}
}
impl<'de> Deserialize<'de> for Semantic<TextureSemantics> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(TextureSemanticVisitor)
}
}
impl Serialize for Semantic<TextureSemantics> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.semantics.is_indexed() {
serializer.serialize_str(&format!(
"{}{}",
self.semantics.texture_name(),
self.index
))
} else {
serializer.serialize_str(&format!("{}", self.semantics.texture_name()))
}
}
}
struct UniqueSemanticsVisitor;
impl<'de> Visitor<'de> for UniqueSemanticsVisitor {
type Value = Semantic<UniqueSemantics, ()>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a valid uniform semantic name")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(match v {
"MVP" => Semantic {
semantics: UniqueSemantics::MVP,
index: (),
},
"OutputSize" => Semantic {
semantics: UniqueSemantics::Output,
index: (),
},
"FinalViewportSize" => Semantic {
semantics: UniqueSemantics::FinalViewport,
index: (),
},
"FrameCount" => Semantic {
semantics: UniqueSemantics::FrameCount,
index: (),
},
"FrameDirection" => Semantic {
semantics: UniqueSemantics::FrameDirection,
index: (),
},
"Rotation" => Semantic {
semantics: UniqueSemantics::Rotation,
index: (),
},
"TotalSubFrames" => Semantic {
semantics: UniqueSemantics::TotalSubFrames,
index: (),
},
"CurrentSubFrame" => Semantic {
semantics: UniqueSemantics::CurrentSubFrame,
index: (),
},
_ => return Err(E::custom(format!("unknown unique semantic {v}"))),
})
}
}
impl Serialize for Semantic<UniqueSemantics, ()> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.semantics.as_str())
}
}
impl<'de> Deserialize<'de> for Semantic<UniqueSemantics, ()> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(UniqueSemanticsVisitor)
}
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d11"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.5.1"
version = "0.3.0"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,23 +12,20 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.5.1" }
librashader-presets = { path = "../librashader-presets", version = "0.5.1" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.5.1" }
librashader-pack = { path = "../librashader-pack", version = "0.5.1" }
librashader-reflect = { path = "../librashader-reflect", version = "0.5.1" }
librashader-runtime = { path = "../librashader-runtime", version = "0.5.1" }
librashader-cache = { path = "../librashader-cache", version = "0.5.1", features = ["d3d"] }
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.3.0" }
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0" }
librashader-runtime = { path = "../librashader-runtime", version = "0.3.0" }
librashader-cache = { path = "../librashader-cache", version = "0.3.0", features = ["d3d"] }
thiserror = "1.0.37"
bytemuck = "1.12.3"
rayon = "1.6.1"
array-concat = "0.5.2"
rayon = { workspace = true }
[features]
debug-shader = []
stable = ["librashader-reflect/stable"]
[target.'cfg(windows)'.dependencies.windows]
workspace = true

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