Compare commits

...

53 commits

Author SHA1 Message Date
Alex Janka 446b052657 fix double free 2024-02-07 15:52:36 +11:00
Alex Janka 9d926b0051 shaders can be either a path or a string 2024-02-07 15:52:36 +11:00
chyyran fb2bcc5d52 chore: Release 2024-02-06 21:41:00 -05:00
chyyran 8015a2a796 fmt: cleanup repo 2024-02-06 21:38:17 -05:00
chyyran 5c8428eac8 lib: add wgpu dependency in runtime-wgpu 2024-02-06 21:38:17 -05:00
chyyran 8fb2179ae8 lib: add wgpu dependency in runtime-wgpu 2024-02-06 21:31:00 -05:00
chyyran f6268a621c chore: Release 2024-02-06 19:20:37 -05:00
chyyran bbfd5153da rt(wgpu): rename OutputView to WgpuOutputView 2024-02-06 19:04:28 -05:00
chyyran 37397ff216 lib: add wgpu to librashader library 2024-02-06 19:04:28 -05:00
chyyran 754e8da620 runtime: remove unused AsDerefable trait 2024-02-06 19:04:28 -05:00
chyyran 6c50880600 reflect: remove spirt test outputs 2024-02-06 19:04:28 -05:00
chyyran acc9bfeb53 rt(wgpu): add blocking submission API 2024-02-06 19:04:28 -05:00
chyyran 54e86e7b06 fmt: clean up unused imports 2024-02-06 19:04:28 -05:00
chyyran 2450217c29 fmt: cargo fmt 2024-02-06 19:04:28 -05:00
chyyran 962a81c2e3 rt(wgsl): mipmaps 2024-02-06 19:04:28 -05:00
chyyran 11ab4b7c9a build: tag RPITIT cause ubuntu 23.10 uses an older compiler 2024-02-06 19:04:28 -05:00
chyyran 121dbc4ed6 reflect(wgsl): properly adjust coordinate space to WGSL-expected 2024-02-06 19:04:28 -05:00
chyyran 31891e414f rt(wgpu): fix enough stuff to get it to draw a frame 2024-02-06 19:04:28 -05:00
chyyran e39834547c rt(wgpu): take an arc of the source texture 2024-02-06 19:04:28 -05:00
chyyran d5aa6b2e4a rt(wgpu): sketch out skeleton for filter chain logic 2024-02-06 19:04:28 -05:00
chyyran cc26be486b rt(wgpu): fix compiler errors for filer pass 2024-02-06 19:04:28 -05:00
chyyran 10358b4966 rt(wgpu): wip filter chain logic 2024-02-06 19:04:28 -05:00
chyyran 32148cdff4 rt(wgpu): filter pass logic 2024-02-06 19:04:28 -05:00
chyyran 555ff6f9fc rt(wgpu): update to wgpu 0.19 2024-02-06 19:04:28 -05:00
chyyran 34f224cc5d build: try to fix build for arm 2024-02-06 19:04:28 -05:00
chyyran 7586ed4633 build: don't specify python patch
# Conflicts:
#	.idea/workspace.xml
2024-02-06 19:04:28 -05:00
chyyran 2b995539f2 rt(wgpu): add structure to wgpu backend 2024-02-06 19:04:28 -05:00
chyyran 8cfe1e9da2 rt(wgpu): create pipeline 2024-02-06 19:04:28 -05:00
chyyran f9df72a02d rt(wgpu): sampler set 2024-02-06 19:04:28 -05:00
chyyran 4e052159e7 rt(wgpu): create pipeline bind group layouts 2024-02-06 19:04:28 -05:00
chyyran 171c842c97 reflect(wgsl): implement WGSL reflection 2024-02-06 19:04:28 -05:00
chyyran 4dfcdf2725 reflect(wgsl): wgsl compile backend 2024-02-06 19:04:28 -05:00
chyyran 1a16c4fadf rt(wgpu): load shaders 2024-02-06 19:04:28 -05:00
chyyran c05d8ff06a rt(wgpu): basic triangle example 2024-02-06 19:04:28 -05:00
Ronny Chan ec98494202
build(obs): remove old obscpio before pushing 2024-02-04 18:06:39 -05:00
Ronny Chan ef4e2353ff
build(obs): remove old oscpio 2024-02-04 18:05:58 -05:00
chyyran a5e978f158 doc: add download link 2024-02-04 13:16:48 -05:00
chyyran 97ff76276f doc: add obs badge 2024-02-04 13:16:48 -05:00
chyyran 797625903a build(obs): add openbuildservice 2024-02-04 13:16:48 -05:00
Ronny Chan 81ba694ba4 build(copr): require git 2024-02-04 13:16:48 -05:00
chyyran 18ff9cf05a build(copr): add copr 2024-02-04 13:16:48 -05:00
chyyran ae2a427b5e pkg(rpm): add RPM spec file 2024-02-04 13:16:48 -05:00
chyyran 552be8c34e rt(gl): remove unnecessary unstable features
polyfill strict_provenance with sptr
2024-02-03 22:06:55 -05:00
chyyran 7c0190004f rt(d3d12): remove unnecessary unstable features
div_ceil has been stabilized, and we don't really make use of const trait impl
2024-02-03 22:06:55 -05:00
chyyran a6c91a07df build: use stable polyfills for extract_if and array_chunks_mut 2024-02-03 22:06:55 -05:00
chyyran d700234c3c presets: polyfill extract_if 2024-02-03 22:06:55 -05:00
chyyran 96f937586c build: clean unused features 2024-02-03 03:01:16 -05:00
chyyran 9c5a8f4042 gh: fix python requirement 2024-02-03 02:46:18 -05:00
chyyran d5bf7e312c build: update lockfile 2024-02-03 02:42:52 -05:00
chyyran 80325fda9e fmt: cargo fmt 2024-02-03 02:42:52 -05:00
chyyran 60fac06332 build: fix rename when pdb is missing 2024-02-03 02:42:52 -05:00
chyyran 92e8a05f8a build: fix cbindgen version 2024-02-03 02:42:52 -05:00
chyyran 617bfdd93e ide: fix rustrover iml 2024-02-03 02:42:52 -05:00
108 changed files with 7659 additions and 783 deletions

View file

@ -29,10 +29,11 @@ jobs:
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
name: Setup Python 3.11 name: Setup Python 3.11
with: with:
python-version: '3.11.6' python-version: '3.11'
- name: Install ARM64 cross-compilation dependencies - name: Install ARM64 cross-compilation dependencies
continue-on-error: true continue-on-error: true
run: | run: |
sudo apt-get update || true
sudo dpkg --add-architecture arm64 sudo dpkg --add-architecture arm64
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy 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 echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list

View file

@ -29,15 +29,12 @@ jobs:
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
name: Setup Python 3.11 name: Setup Python 3.11
with: with:
python-version: '3.11.6' python-version: '3.11'
- name: Install Vulkan SDK - name: Install Vulkan SDK
uses: humbletim/install-vulkan-sdk@v1.1.1 uses: humbletim/install-vulkan-sdk@v1.1.1
with: with:
version: latest version: latest
cache: true cache: true
- if: runner.os == 'Windows'
run: pip install meson ninja mako
name: Install Meson for spirv-to-dxil-sys
- name: Build dynamic library - name: Build dynamic library
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }}
- name: Upload build artifacts - name: Upload build artifacts

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

@ -0,0 +1,48 @@
name: build Linux packages with Open Build Service
on:
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build-obs-binary:
strategy:
matrix:
include:
- repo: Fedora_39
spec: librashader.spec
- repo: xUbuntu_23.10
spec: librashader.spec
- repo: Arch
spec: PKGBUILD
runs-on: ubuntu-latest
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
run: |
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
mkdir -p ~/.config/osc
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
- name: Checkout Open Build Service repository
run: |
osc co home:chyyran:librashader/librashader
- name: Copy spec from repository
run: |
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
- name: Vendor sources for Open Build Service
run: |
cd home:chyyran:librashader/librashader/
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 }}

80
.github/workflows/publish-obs.yml vendored Normal file
View file

@ -0,0 +1,80 @@
name: publish packages using Open Build Service
on:
push:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build-obs-binary:
strategy:
matrix:
include:
- repo: Fedora_39
spec: librashader.spec
- repo: xUbuntu_23.10
spec: librashader.spec
- repo: Arch
spec: PKGBUILD
runs-on: ubuntu-latest
container:
image: fedora:39
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
run: |
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
mkdir -p ~/.config/osc
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
- name: Checkout Open Build Service repository
run: |
osc co home:chyyran:librashader/librashader
- name: Copy spec from repository
run: |
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
- name: Vendor sources for Open Build Service
run: |
cd home:chyyran:librashader/librashader/
rm *.obscpio
osc service mr
- name: Build package
run: |
cd home:chyyran:librashader/librashader/
osc build --no-verify --trust-all-projects ${{ matrix.repo }} x86_64 ${{ matrix.spec }}
publish-obs:
needs: build-obs-binary
runs-on: ubuntu-latest
container: fedora:39
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install OSC and dependencies
env:
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
run: |
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
mkdir -p ~/.config/osc
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
- name: Checkout Open Build Service repository
run: |
osc co home:chyyran:librashader/librashader
- name: Copy spec from repository
run: |
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
- name: Vendor sources for Open Build Service
run: |
cd home:chyyran:librashader/librashader/
rm *.obscpio
osc service mr
- name: Commit source artifacts to Open Build Service
run: |
cd home:chyyran:librashader/librashader/
osc ar
osc commit -f -m "git-rev ${{ github.sha }}"

1
.gitignore vendored
View file

@ -5,6 +5,7 @@
*.rdc *.rdc
*.cap *.cap
/.vs/ /.vs/
.idea/
librashader_runtime_*.exe librashader_runtime_*.exe
/test/capi-tests/librashader-capi-tests/.vs/ /test/capi-tests/librashader-capi-tests/.vs/
/test/capi-tests/librashader-capi-tests/x64/ /test/capi-tests/librashader-capi-tests/x64/

8
.idea/.gitignore vendored
View file

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
</modules>
</component>
</project>

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/test/shaders_slang" vcs="Git" />
</component>
</project>

2061
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ members = [
"librashader-cache", "librashader-cache",
"librashader-capi", "librashader-capi",
"librashader-build-script" "librashader-build-script"
] , "librashader-runtime-wgpu"]
resolver = "2" resolver = "2"
[workspace.metadata.release] [workspace.metadata.release]

View file

@ -6,8 +6,12 @@
librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for RetroArch 'slang' shaders, rewritten in pure Rust. librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for RetroArch 'slang' shaders, rewritten in pure Rust.
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader) [![Docs](https://docs.rs/librashader/badge.svg)](https://docs.rs/librashader) ![License](https://img.shields.io/crates/l/librashader) [![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)
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg) ![License](https://img.shields.io/crates/l/librashader)
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
## Installation
For end-users, librashader is available from the [Open Build Service](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader) for a variety of Linux distributions and platforms. Windows users can grab the latest DLL from [GitHub Releases](https://github.com/SnowflakePowered/librashader/releases).
## Supported Render APIs ## Supported Render APIs
librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU
@ -208,6 +212,8 @@ will check to ensure that `LIBRASHADER_CURRENT_ABI` matches that of the loaded l
librashader will not load. A value of `0` for `LIBRASHADER_CURRENT_ABI` indicates the "null" instance where every operation librashader will not load. A value of `0` for `LIBRASHADER_CURRENT_ABI` indicates the "null" instance where every operation
is a no-op, which occurs if no compatible librashader implementation could be found. is a no-op, which occurs if no compatible librashader implementation could be found.
The `SONAME` of `librashader.so` when installed via package manager is set to `LIBRASHADER_CURRENT_ABI`.
The above does not apply to releases of librashader prior to `0.1.0`, which were allowed to break API and ABI compatibility 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`. in both the Rust and C API without an increase to either `LIBRASHADER_CURRENT_VERSION` or `LIBRASHADER_CURRENT_ABI`.

View file

@ -6,7 +6,7 @@ publish = false
[dependencies] [dependencies]
cbindgen = { git = "https://github.com/eqrion/cbindgen" } cbindgen = "0.26.0"
clap = { version = "4.1.0", features = ["derive"] } clap = { version = "4.1.0", features = ["derive"] }

View file

@ -36,10 +36,7 @@ pub fn main() {
)); ));
if let Some(target) = &args.target { if let Some(target) = &args.target {
cmd.arg(format!( cmd.arg(format!("--target={}", &target));
"--target={}",
&target
));
} }
Some(cmd.status().expect("Failed to build librashader-capi")); Some(cmd.status().expect("Failed to build librashader-capi"));
@ -53,7 +50,6 @@ pub fn main() {
.canonicalize() .canonicalize()
.expect("Could not find output directory."); .expect("Could not find output directory.");
println!("Generating C headers..."); println!("Generating C headers...");
// Create headers. // Create headers.
@ -86,11 +82,20 @@ pub fn main() {
"librashader_capi.d", "librashader_capi.d",
"librashader_capi.dll.exp", "librashader_capi.dll.exp",
"librashader_capi.dll.lib", "librashader_capi.dll.lib",
"librashader_capi.pdb",
]; ];
for artifact in artifacts { for artifact in artifacts {
let ext = artifact.replace("_capi", ""); let ext = artifact.replace("_capi", "");
println!("Renaming {artifact} to {ext}");
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap(); 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();
}
} }
} }

View file

@ -2,7 +2,7 @@
name = "librashader-cache" name = "librashader-cache"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -12,8 +12,8 @@ description = "RetroArch shaders for all."
[dependencies] [dependencies]
serde = { version = "1.0" } serde = { version = "1.0" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["serialize"] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["serialize"] }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
platform-dirs = "0.3.0" platform-dirs = "0.3.0"
blake3 = { version = "1.3.3" } blake3 = { version = "1.3.3" }
thiserror = "1.0.38" thiserror = "1.0.38"

View file

@ -1,6 +1,9 @@
//! Cache helpers for `ShaderCompilation` objects to cache compiled SPIRV. //! Cache helpers for `ShaderCompilation` objects to cache compiled SPIRV.
use librashader_preprocess::ShaderSource; use librashader_preprocess::ShaderSource;
use librashader_reflect::back::targets::{DXIL, GLSL, HLSL, SPIRV}; #[cfg(all(target_os = "windows", feature = "d3d"))]
use librashader_reflect::back::targets::DXIL;
use librashader_reflect::back::targets::{GLSL, HLSL, SPIRV};
use librashader_reflect::back::{CompilerBackend, FromCompilation}; use librashader_reflect::back::{CompilerBackend, FromCompilation};
use librashader_reflect::error::{ShaderCompileError, ShaderReflectError}; use librashader_reflect::error::{ShaderCompileError, ShaderReflectError};
use librashader_reflect::front::{GlslangCompilation, ShaderCompilation}; use librashader_reflect::front::{GlslangCompilation, ShaderCompilation};

View file

@ -3,7 +3,7 @@ name = "librashader-capi"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -23,7 +23,7 @@ runtime-d3d12 = ["windows", "librashader/runtime-d3d12", "windows/Win32_Graphics
runtime-vulkan = ["ash", "librashader/runtime-vk"] runtime-vulkan = ["ash", "librashader/runtime-vk"]
[dependencies] [dependencies]
librashader = { path = "../librashader", version = "0.2.0-beta.2", features = ["internal"] } librashader = { path = "../librashader", version = "0.2.0-beta.5", features = ["internal"] }
thiserror = "1.0.37" thiserror = "1.0.37"
paste = "1.0.9" paste = "1.0.9"
gl = { version = "0.14.0", optional = true } gl = { version = "0.14.0", optional = true }

View file

@ -3,7 +3,7 @@ name = "librashader-common"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -18,10 +18,12 @@ d3d11 = ["windows", "dxgi"]
d3d12 = ["windows", "dxgi"] d3d12 = ["windows", "dxgi"]
dxgi = ["windows"] dxgi = ["windows"]
vulkan = ["ash"] vulkan = ["ash"]
wgpu = ["wgpu-types"]
[dependencies] [dependencies]
gl = { version = "0.14.0", optional = true } gl = { version = "0.14.0", optional = true }
ash = { version = "0.37", optional = true } ash = { version = "0.37", optional = true }
wgpu-types = { version = "0.19.0", optional = true }
num-traits = "0.2.15" num-traits = "0.2.15"

View file

@ -8,6 +8,10 @@ pub mod gl;
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
pub mod vk; pub mod vk;
/// WGPU common conversions.
#[cfg(feature = "wgpu")]
pub mod wgpu;
/// DXGI common conversions. /// DXGI common conversions.
#[cfg(all(target_os = "windows", feature = "dxgi"))] #[cfg(all(target_os = "windows", feature = "dxgi"))]
pub mod dxgi; pub mod dxgi;
@ -21,12 +25,19 @@ pub mod d3d11;
pub mod d3d12; pub mod d3d12;
mod viewport; mod viewport;
pub use viewport::Viewport; pub use viewport::Viewport;
use num_traits::AsPrimitive; use num_traits::AsPrimitive;
use std::convert::Infallible; use std::convert::Infallible;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum ShaderStorage {
Path(std::path::PathBuf),
String(String),
}
#[repr(u32)] #[repr(u32)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
/// Supported image formats for textures. /// Supported image formats for textures.

View file

@ -0,0 +1,177 @@
use crate::{FilterMode, ImageFormat, Size, WrapMode};
impl From<ImageFormat> for Option<wgpu_types::TextureFormat> {
fn from(format: ImageFormat) -> Self {
match format {
ImageFormat::Unknown => None,
ImageFormat::R8Unorm => Some(wgpu_types::TextureFormat::R8Unorm),
ImageFormat::R8Uint => Some(wgpu_types::TextureFormat::R8Uint),
ImageFormat::R8Sint => Some(wgpu_types::TextureFormat::R8Sint),
ImageFormat::R8G8Unorm => Some(wgpu_types::TextureFormat::Rg8Unorm),
ImageFormat::R8G8Uint => Some(wgpu_types::TextureFormat::Rg8Uint),
ImageFormat::R8G8Sint => Some(wgpu_types::TextureFormat::Rg8Sint),
ImageFormat::R8G8B8A8Unorm => Some(wgpu_types::TextureFormat::Rgba8Unorm),
ImageFormat::R8G8B8A8Uint => Some(wgpu_types::TextureFormat::Rgba8Uint),
ImageFormat::R8G8B8A8Sint => Some(wgpu_types::TextureFormat::Rgba8Sint),
ImageFormat::R8G8B8A8Srgb => Some(wgpu_types::TextureFormat::Rgba8UnormSrgb),
ImageFormat::A2B10G10R10UnormPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Unorm),
ImageFormat::A2B10G10R10UintPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Uint),
ImageFormat::R16Uint => Some(wgpu_types::TextureFormat::R16Uint),
ImageFormat::R16Sint => Some(wgpu_types::TextureFormat::R16Sint),
ImageFormat::R16Sfloat => Some(wgpu_types::TextureFormat::R16Float),
ImageFormat::R16G16Uint => Some(wgpu_types::TextureFormat::Rg16Uint),
ImageFormat::R16G16Sint => Some(wgpu_types::TextureFormat::Rg16Sint),
ImageFormat::R16G16Sfloat => Some(wgpu_types::TextureFormat::Rg16Float),
ImageFormat::R16G16B16A16Uint => Some(wgpu_types::TextureFormat::Rgba16Uint),
ImageFormat::R16G16B16A16Sint => Some(wgpu_types::TextureFormat::Rgba16Sint),
ImageFormat::R16G16B16A16Sfloat => Some(wgpu_types::TextureFormat::Rgba16Float),
ImageFormat::R32Uint => Some(wgpu_types::TextureFormat::R32Uint),
ImageFormat::R32Sint => Some(wgpu_types::TextureFormat::R32Sint),
ImageFormat::R32Sfloat => Some(wgpu_types::TextureFormat::R32Float),
ImageFormat::R32G32Uint => Some(wgpu_types::TextureFormat::Rg32Uint),
ImageFormat::R32G32Sint => Some(wgpu_types::TextureFormat::Rg32Sint),
ImageFormat::R32G32Sfloat => Some(wgpu_types::TextureFormat::Rg32Float),
ImageFormat::R32G32B32A32Uint => Some(wgpu_types::TextureFormat::Rgba32Uint),
ImageFormat::R32G32B32A32Sint => Some(wgpu_types::TextureFormat::Rgba32Sint),
ImageFormat::R32G32B32A32Sfloat => Some(wgpu_types::TextureFormat::Rgba32Float),
}
}
}
impl From<wgpu_types::TextureFormat> for ImageFormat {
fn from(format: wgpu_types::TextureFormat) -> Self {
match format {
wgpu_types::TextureFormat::R8Unorm => ImageFormat::R8Unorm,
wgpu_types::TextureFormat::R8Uint => ImageFormat::R8Uint,
wgpu_types::TextureFormat::R8Sint => ImageFormat::R8Sint,
wgpu_types::TextureFormat::Rg8Unorm => ImageFormat::R8G8Unorm,
wgpu_types::TextureFormat::Rg8Uint => ImageFormat::R8G8Uint,
wgpu_types::TextureFormat::Rg8Sint => ImageFormat::R8G8Sint,
wgpu_types::TextureFormat::Rgba8Unorm => ImageFormat::R8G8B8A8Unorm,
wgpu_types::TextureFormat::Rgba8Uint => ImageFormat::R8G8B8A8Uint,
wgpu_types::TextureFormat::Rgba8Sint => ImageFormat::R8G8B8A8Sint,
wgpu_types::TextureFormat::Rgba8UnormSrgb => ImageFormat::R8G8B8A8Srgb,
wgpu_types::TextureFormat::Rgb10a2Unorm => ImageFormat::A2B10G10R10UnormPack32,
wgpu_types::TextureFormat::Rgb10a2Uint => ImageFormat::A2B10G10R10UintPack32,
wgpu_types::TextureFormat::R16Uint => ImageFormat::R16Uint,
wgpu_types::TextureFormat::R16Sint => ImageFormat::R16Sint,
wgpu_types::TextureFormat::R16Float => ImageFormat::R16Sfloat,
wgpu_types::TextureFormat::Rg16Uint => ImageFormat::R16G16Uint,
wgpu_types::TextureFormat::Rg16Sint => ImageFormat::R16G16Sint,
wgpu_types::TextureFormat::Rg16Float => ImageFormat::R16G16Sfloat,
wgpu_types::TextureFormat::Rgba16Uint => ImageFormat::R16G16B16A16Uint,
wgpu_types::TextureFormat::Rgba16Sint => ImageFormat::R16G16B16A16Sint,
wgpu_types::TextureFormat::Rgba16Float => ImageFormat::R16G16B16A16Sfloat,
wgpu_types::TextureFormat::R32Uint => ImageFormat::R32Uint,
wgpu_types::TextureFormat::R32Sint => ImageFormat::R32Sint,
wgpu_types::TextureFormat::R32Float => ImageFormat::R32Sfloat,
wgpu_types::TextureFormat::Rg32Uint => ImageFormat::R32G32Uint,
wgpu_types::TextureFormat::Rg32Sint => ImageFormat::R32G32Sint,
wgpu_types::TextureFormat::Rg32Float => ImageFormat::R32G32Sfloat,
wgpu_types::TextureFormat::Rgba32Uint => ImageFormat::R32G32B32A32Uint,
wgpu_types::TextureFormat::Rgba32Sint => ImageFormat::R32G32B32A32Sint,
wgpu_types::TextureFormat::Rgba32Float => ImageFormat::R32G32B32A32Sfloat,
_ => ImageFormat::Unknown,
}
}
}
impl From<Option<wgpu_types::TextureFormat>> for ImageFormat {
fn from(format: Option<wgpu_types::TextureFormat>) -> Self {
let Some(format) = format else {
return ImageFormat::Unknown;
};
ImageFormat::from(format)
}
}
impl From<wgpu_types::Extent3d> for Size<u32> {
fn from(value: wgpu_types::Extent3d) -> Self {
Size {
width: value.width,
height: value.height,
}
}
}
//
// impl From<Size<u32>> for vk::Extent2D {
// fn from(value: Size<u32>) -> Self {
// vk::Extent2D {
// width: value.width,
// height: value.height,
// }
// }
// }
//
//
// impl From<vk::Extent2D> for Size<u32> {
// fn from(value: vk::Extent2D) -> Self {
// Size {
// width: value.width,
// height: value.height,
// }
// }
// }
//
// impl From<vk::Viewport> for Size<u32> {
// fn from(value: vk::Viewport) -> Self {
// Size {
// width: value.width as u32,
// height: value.height as u32,
// }
// }
// }
//
// impl From<&vk::Viewport> for Size<u32> {
// fn from(value: &vk::Viewport) -> Self {
// Size {
// width: value.width as u32,
// height: value.height as u32,
// }
// }
// }
//
// impl From<Size<u32>> for vk::Viewport {
// fn from(value: Size<u32>) -> Self {
// vk::Viewport {
// x: 0.0,
// y: 0.0,
// width: value.width as f32,
// height: value.height as f32,
// min_depth: 0.0,
// max_depth: 1.0,
// }
// }
// }
impl From<FilterMode> for wgpu_types::FilterMode {
fn from(value: FilterMode) -> Self {
match value {
FilterMode::Linear => wgpu_types::FilterMode::Linear,
FilterMode::Nearest => wgpu_types::FilterMode::Nearest,
}
}
}
impl From<WrapMode> for wgpu_types::AddressMode {
fn from(value: WrapMode) -> Self {
match value {
WrapMode::ClampToBorder => wgpu_types::AddressMode::ClampToBorder,
WrapMode::ClampToEdge => wgpu_types::AddressMode::ClampToEdge,
WrapMode::Repeat => wgpu_types::AddressMode::Repeat,
WrapMode::MirroredRepeat => wgpu_types::AddressMode::MirrorRepeat,
}
}
}
impl From<Size<u32>> for wgpu_types::Extent3d {
fn from(value: Size<u32>) -> Self {
wgpu_types::Extent3d {
width: value.width,
height: value.height,
depth_or_array_layers: 1,
}
}
}

View file

@ -3,7 +3,7 @@ name = "librashader-preprocess"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -14,7 +14,7 @@ description = "RetroArch shaders for all."
[dependencies] [dependencies]
thiserror = "1.0.37" thiserror = "1.0.37"
nom = "7.1.1" nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
encoding_rs = "0.8.31" encoding_rs = "0.8.31"

View file

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

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -15,7 +15,7 @@ description = "RetroArch shaders for all."
thiserror = "1.0.37" thiserror = "1.0.37"
nom = "7.1.1" nom = "7.1.1"
nom_locate = "4.0.0" nom_locate = "4.0.0"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
num-traits = "0.2" num-traits = "0.2"
[features] [features]

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::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

@ -7,10 +7,13 @@
//! as input to create a filter chain. //! as input to create a filter chain.
//! //!
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html). //! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
#![feature(extract_if)]
#![allow(unstable_name_collisions)]
mod error; mod error;
mod extract_if;
mod parse; mod parse;
mod preset; mod preset;
pub use error::*; pub use error::*;
pub use preset::*; pub use preset::*;

View file

@ -1,3 +1,4 @@
use crate::extract_if::MakeExtractIf;
use crate::parse::remove_if; use crate::parse::remove_if;
use crate::parse::value::Value; use crate::parse::value::Value;
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig}; use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
@ -114,7 +115,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
let shader = ShaderPassConfig { let shader = ShaderPassConfig {
id, id,
name, name: librashader_common::ShaderStorage::Path(name),
alias: shader_values.iter().find_map(|f| match f { alias: shader_values.iter().find_map(|f| match f {
Value::Alias(_, value) => Some(value.to_string()), Value::Alias(_, value) => Some(value.to_string()),
_ => None, _ => None,

View file

@ -16,6 +16,8 @@ use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use crate::extract_if::MakeExtractIf;
#[derive(Debug)] #[derive(Debug)]
pub enum Value { pub enum Value {
ShaderCount(i32), ShaderCount(i32),

View file

@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
/// The index of the shader pass relative to its parent preset. /// The index of the shader pass relative to its parent preset.
pub id: i32, pub id: i32,
/// The fully qualified path to the shader pass source file. /// The fully qualified path to the shader pass source file.
pub name: PathBuf, pub name: librashader_common::ShaderStorage,
/// The alias of the shader pass if available. /// The alias of the shader pass if available.
pub alias: Option<String>, pub alias: Option<String>,
/// The filtering mode that this shader pass should expect. /// The filtering mode that this shader pass should expect.

View file

@ -3,7 +3,7 @@ name = "librashader-reflect"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -19,25 +19,29 @@ thiserror = "1.0.37"
bitflags = "1.3.2" bitflags = "1.3.2"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23", optional = true } spirv_cross = { package = "librashader-spirv-cross", version = "0.23", optional = true }
naga = { version = "0.11.0", features = ["glsl-in", "spv-in", "spv-out", "glsl-out", "wgsl-out"], optional = true }
rspirv = { version = "0.11.0+1.5.4", optional = true } naga = { version = "0.19.0", features = ["spv-in", "wgsl-out"], optional = true }
rspirv = { version = "0.12.0+sdk-1.3.268.0", optional = true }
spirv = { version = "0.3.0+sdk-1.3.268.0", optional = true}
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
indexmap = { version = "2.1.0", features = [] }
matches = { version = "0.1.10", features = [] }
[target.'cfg(windows)'.dependencies.spirv-to-dxil] [target.'cfg(windows)'.dependencies.spirv-to-dxil]
version = "0.4" version = "0.4"
optional = true optional = true
[features] [features]
default = ["cross", "serialize"] default = ["cross", "wgsl", "serialize"]
unstable-naga = [ "naga", "rspirv" ]
standalone = ["shaderc/build-from-source", "shaderc/prefer-static-linking"] standalone = ["shaderc/build-from-source", "shaderc/prefer-static-linking"]
dxil = ["cross", "spirv-to-dxil"] dxil = ["cross", "spirv-to-dxil"]
wgsl = ["cross", "naga", "spirv", "rspirv"]
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl" ] cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl" ]
serialize = [ "serde" ] serialize = [ "serde" ]

Binary file not shown.

View file

@ -3,6 +3,7 @@ pub mod cross;
pub mod dxil; pub mod dxil;
mod spirv; mod spirv;
pub mod targets; pub mod targets;
pub mod wgsl;
use crate::back::targets::OutputTarget; use crate::back::targets::OutputTarget;
use crate::error::{ShaderCompileError, ShaderReflectError}; use crate::error::{ShaderCompileError, ShaderReflectError};
@ -114,3 +115,14 @@ where
self.backend.reflect(pass_number, semantics) self.backend.reflect(pass_number, semantics)
} }
} }
#[cfg(test)]
mod test {
use crate::front::GlslangCompilation;
use librashader_preprocess::ShaderSource;
pub fn test() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let _cross = GlslangCompilation::compile(&result).unwrap();
}
}

View file

@ -18,16 +18,27 @@ pub struct MSL;
/// must be validated using platform APIs before usage. /// must be validated using platform APIs before usage.
pub struct DXIL; pub struct DXIL;
/// Shader compiler target for WGSL.
///
/// The resulting WGSL will split sampler2Ds into
/// split textures and shaders. Shaders for each texture binding
/// will be in descriptor set 1.
#[derive(Debug)]
pub struct WGSL;
impl OutputTarget for GLSL { impl OutputTarget for GLSL {
type Output = String; type Output = String;
} }
impl OutputTarget for HLSL { impl OutputTarget for HLSL {
type Output = String; type Output = String;
} }
impl OutputTarget for WGSL {
type Output = String;
}
impl OutputTarget for SPIRV { impl OutputTarget for SPIRV {
type Output = Vec<u32>; type Output = Vec<u32>;
} }
#[cfg(test)]
mod test { mod test {
use crate::back::targets::GLSL; use crate::back::targets::GLSL;
use crate::back::FromCompilation; use crate::back::FromCompilation;

File diff suppressed because it is too large Load diff

View file

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

View file

@ -26,6 +26,15 @@ pub enum ShaderCompileError {
#[cfg(all(target_os = "windows", feature = "dxil"))] #[cfg(all(target_os = "windows", feature = "dxil"))]
#[error("spirv-to-dxil")] #[error("spirv-to-dxil")]
SpirvToDxilCompileError(#[from] spirv_to_dxil::SpirvToDxilError), SpirvToDxilCompileError(#[from] spirv_to_dxil::SpirvToDxilError),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-wgsl")]
NagaWgslError(#[from] naga::back::wgsl::Error),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-wgsl")]
NagaValidationError(#[from] naga::WithSpan<naga::valid::ValidationError>),
} }
/// The error kind encountered when reflecting shader semantics. /// The error kind encountered when reflecting shader semantics.
@ -34,6 +43,8 @@ pub enum SemanticsErrorKind {
/// The number of uniform buffers was invalid. Only one UBO is permitted. /// The number of uniform buffers was invalid. Only one UBO is permitted.
InvalidUniformBufferCount(usize), InvalidUniformBufferCount(usize),
/// The number of push constant blocks was invalid. Only one push constant block is permitted. /// The number of push constant blocks was invalid. Only one push constant block is permitted.
InvalidPushBufferCount(usize),
/// The size of the push constant block was invalid.
InvalidPushBufferSize(u32), InvalidPushBufferSize(u32),
/// The location of a varying was invalid. /// The location of a varying was invalid.
InvalidLocation(u32), InvalidLocation(u32),
@ -43,12 +54,16 @@ pub enum SemanticsErrorKind {
InvalidInputCount(usize), InvalidInputCount(usize),
/// The number of outputs declared was invalid. /// The number of outputs declared was invalid.
InvalidOutputCount(usize), InvalidOutputCount(usize),
/// Expected a binding but there was none.
MissingBinding,
/// The declared binding point was invalid. /// The declared binding point was invalid.
InvalidBinding(u32), InvalidBinding(u32),
/// The declared resource type was invalid. /// The declared resource type was invalid.
InvalidResourceType, InvalidResourceType,
/// The range of a struct member was invalid. /// The range of a struct member was invalid.
InvalidRange(u32), InvalidRange(u32),
/// The number of entry points in the shader was invalid.
InvalidEntryPointCount(usize),
/// The requested uniform or texture name was not provided semantics. /// The requested uniform or texture name was not provided semantics.
UnknownSemantics(String), UnknownSemantics(String),
/// The type of the requested uniform was not compatible with the provided semantics. /// The type of the requested uniform was not compatible with the provided semantics.
@ -59,11 +74,6 @@ pub enum SemanticsErrorKind {
#[non_exhaustive] #[non_exhaustive]
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ShaderReflectError { pub enum ShaderReflectError {
/// Compile error from naga.
#[cfg(feature = "unstable-naga")]
#[error("shader")]
NagaCompileError(#[from] naga::front::spv::Error),
/// Reflection error from spirv-cross. /// Reflection error from spirv-cross.
#[error("spirv")] #[error("spirv")]
SpirvCrossError(#[from] spirv_cross::ErrorCode), SpirvCrossError(#[from] spirv_cross::ErrorCode),
@ -100,6 +110,10 @@ pub enum ShaderReflectError {
/// The binding number is already in use. /// The binding number is already in use.
#[error("the binding is already in use")] #[error("the binding is already in use")]
BindingInUse(u32), BindingInUse(u32),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-spv")]
NagaInputError(#[from] naga::front::spv::Error),
} }
#[cfg(feature = "unstable-naga")] #[cfg(feature = "unstable-naga")]

View file

@ -1,16 +1,10 @@
use crate::error::ShaderCompileError; use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource; use librashader_preprocess::ShaderSource;
#[cfg(feature = "unstable-naga")]
mod naga;
mod shaderc; mod shaderc;
pub use crate::front::shaderc::GlslangCompilation; pub use crate::front::shaderc::GlslangCompilation;
#[cfg(feature = "unstable-naga")]
pub use crate::front::naga::NagaCompilation;
/// Trait for types that can compile shader sources into a compilation unit. /// Trait for types that can compile shader sources into a compilation unit.
pub trait ShaderCompilation: Sized { pub trait ShaderCompilation: Sized {
/// Compile the input shader source file into a compilation unit. /// Compile the input shader source file into a compilation unit.

View file

@ -1,152 +0,0 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use naga::front::glsl::{Options, Parser};
use naga::{Module, ShaderStage};
/// A reflectable shader compilation via naga.
#[derive(Debug)]
pub struct NagaCompilation {
pub(crate) vertex: Module,
pub(crate) fragment: Module,
}
impl TryFrom<&ShaderSource> for NagaCompilation {
type Error = ShaderCompileError;
/// Tries to compile SPIR-V from the provided shader source.
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
compile_spirv(source)
}
}
fn compile_spirv(source: &ShaderSource) -> Result<NagaCompilation, ShaderCompileError> {
let mut parser = Parser::default();
let vertex = parser.parse(&Options::from(ShaderStage::Vertex), &source.vertex)?;
let fragment = parser.parse(&Options::from(ShaderStage::Fragment), &source.fragment)?;
Ok(NagaCompilation { vertex, fragment })
}
#[cfg(test)]
mod test {
use crate::front::naga::compile_spirv;
use crate::front::shaderc::GlslangCompilation;
use librashader_preprocess::ShaderSource;
use naga::back::glsl::{PipelineOptions, Version};
use naga::back::spv::{Capability, WriterFlags};
use naga::front::glsl::{Options, Parser};
use naga::front::spv::Options as SpvOptions;
use naga::valid::{Capabilities, ValidationFlags};
use naga::{FastHashSet, ShaderStage};
use rspirv::binary::Disassemble;
#[test]
pub fn compile_naga_test() {
let result = ShaderSource::load(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
.unwrap();
let fragment_source = result.fragment;
let mut parser = Parser::default();
println!("{fragment_source}");
let _fragment = parser
.parse(&Options::from(ShaderStage::Fragment), &fragment_source)
.unwrap();
}
#[test]
pub fn compile_shader() {
let result = ShaderSource::load(
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
)
.unwrap();
let spirv = compile_spirv(&result).unwrap();
eprintln!("{spirv:?}")
}
#[test]
pub fn compile_shader_roundtrip() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let cross = GlslangCompilation::compile(&result).unwrap();
let naga_fragment =
naga::front::spv::parse_u8_slice(cross.fragment.as_binary_u8(), &SpvOptions::default())
.unwrap();
println!("{:#?}", naga_fragment.constants);
println!("{:#?}", naga_fragment.global_variables);
println!("{:#?}", naga_fragment.types);
}
#[test]
pub fn naga_playground() {
let result = ShaderSource::load("../test/basic.slang").unwrap();
let spirv = GlslangCompilation::compile(&result).unwrap();
let module =
naga::front::spv::parse_u8_slice(spirv.fragment.as_binary_u8(), &SpvOptions::default())
.unwrap();
let capability = FastHashSet::from_iter([Capability::Shader]);
let mut writer = naga::back::spv::Writer::new(&naga::back::spv::Options {
lang_version: (1, 0),
flags: WriterFlags::all(),
binding_map: Default::default(),
capabilities: Some(capability),
bounds_check_policies: Default::default(),
})
.unwrap();
let mut validator =
naga::valid::Validator::new(ValidationFlags::empty(), Capabilities::all());
let info = validator.validate(&module).unwrap();
let mut spv_out = Vec::new();
let pipe = naga::back::spv::PipelineOptions {
shader_stage: ShaderStage::Fragment,
entry_point: "main".to_string(),
};
writer
.write(&module, &info, Some(&pipe), &mut spv_out)
.unwrap();
let mut glsl_out = String::new();
let opts = naga::back::glsl::Options {
version: Version::Desktop(330),
writer_flags: naga::back::glsl::WriterFlags::all(),
binding_map: Default::default(),
};
let pipe = PipelineOptions {
shader_stage: ShaderStage::Fragment,
entry_point: "main".to_string(),
multiview: None,
};
let mut glsl_naga = naga::back::glsl::Writer::new(
&mut glsl_out,
&module,
&info,
&opts,
&pipe,
Default::default(),
)
.unwrap();
glsl_naga.write().unwrap();
let wgsl =
naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::all())
.unwrap();
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(&spv_out, &mut loader).unwrap();
let module = loader.module();
println!("--- spirv --");
println!("{:#}", module.disassemble());
println!("--- cross glsl --");
let loaded = spirv_cross::spirv::Module::from_words(&spv_out);
let mut ast = spirv_cross::spirv::Ast::<spirv_cross::glsl::Target>::parse(&loaded).unwrap();
println!("{:#}", ast.compile().unwrap());
println!("--- naga glsl---");
println!("{glsl_out:#}");
println!("--- naga wgsl---");
println!("{wgsl:#}")
}
}

View file

@ -1,8 +1,8 @@
use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError}; use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError};
use crate::front::GlslangCompilation; use crate::front::GlslangCompilation;
use crate::reflect::semantics::{ use crate::reflect::semantics::{
BindingMeta, BindingStage, MemberOffset, PushReflection, ShaderReflection, ShaderSemantics, BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UboReflection, TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
UniformMemberBlock, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta, UniformMemberBlock, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta,
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE, MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
}; };
@ -208,6 +208,7 @@ where
)); ));
} }
// Ensure that vertex attributes use location 0 and 1
let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| { let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| {
Ok::<u32, ErrorCode>( Ok::<u32, ErrorCode>(
mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?, mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?,
@ -227,9 +228,7 @@ where
if vertex_res.push_constant_buffers.len() > 1 { if vertex_res.push_constant_buffers.len() > 1 {
return Err(ShaderReflectError::VertexSemanticError( return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount( SemanticsErrorKind::InvalidPushBufferCount(vertex_res.push_constant_buffers.len()),
vertex_res.push_constant_buffers.len(),
),
)); ));
} }
@ -241,7 +240,7 @@ where
if fragment_res.push_constant_buffers.len() > 1 { if fragment_res.push_constant_buffers.len() > 1 {
return Err(ShaderReflectError::FragmentSemanticError( return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount( SemanticsErrorKind::InvalidPushBufferCount(
fragment_res.push_constant_buffers.len(), fragment_res.push_constant_buffers.len(),
), ),
)); ));
@ -332,7 +331,7 @@ where
expected, expected,
received: offset, received: offset,
ty: offset_type, ty: offset_type,
pass: pass_number pass: pass_number,
}); });
} }
if meta.size != typeinfo.size { if meta.size != typeinfo.size {
@ -367,7 +366,7 @@ where
expected, expected,
received: offset, received: offset,
ty: offset_type, ty: offset_type,
pass: pass_number pass: pass_number,
}); });
} }
if meta.size != typeinfo.size * typeinfo.columns { if meta.size != typeinfo.size * typeinfo.columns {
@ -416,7 +415,7 @@ where
expected, expected,
received: offset, received: offset,
ty: offset_type, ty: offset_type,
pass: pass_number pass: pass_number,
}); });
} }
@ -450,7 +449,7 @@ where
&mut self, &mut self,
vertex_ubo: Option<&Resource>, vertex_ubo: Option<&Resource>,
fragment_ubo: Option<&Resource>, fragment_ubo: Option<&Resource>,
) -> Result<Option<UboReflection>, ShaderReflectError> { ) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
if let Some(vertex_ubo) = vertex_ubo { if let Some(vertex_ubo) = vertex_ubo {
self.vertex self.vertex
.set_decoration(vertex_ubo.id, Decoration::Binding, 0)?; .set_decoration(vertex_ubo.id, Decoration::Binding, 0)?;
@ -476,7 +475,7 @@ where
} }
let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size); let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size);
Ok(Some(UboReflection { Ok(Some(BufferReflection {
binding: vertex_ubo.binding, binding: vertex_ubo.binding,
size: align_uniform_size(size), size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT, stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
@ -485,7 +484,7 @@ where
(Some(vertex_ubo), None) => { (Some(vertex_ubo), None) => {
let vertex_ubo = let vertex_ubo =
Self::get_ubo_data(&self.vertex, vertex_ubo, SemanticErrorBlame::Vertex)?; Self::get_ubo_data(&self.vertex, vertex_ubo, SemanticErrorBlame::Vertex)?;
Ok(Some(UboReflection { Ok(Some(BufferReflection {
binding: vertex_ubo.binding, binding: vertex_ubo.binding,
size: align_uniform_size(vertex_ubo.size), size: align_uniform_size(vertex_ubo.size),
stage_mask: BindingStage::VERTEX, stage_mask: BindingStage::VERTEX,
@ -494,7 +493,7 @@ where
(None, Some(fragment_ubo)) => { (None, Some(fragment_ubo)) => {
let fragment_ubo = let fragment_ubo =
Self::get_ubo_data(&self.fragment, fragment_ubo, SemanticErrorBlame::Fragment)?; Self::get_ubo_data(&self.fragment, fragment_ubo, SemanticErrorBlame::Fragment)?;
Ok(Some(UboReflection { Ok(Some(BufferReflection {
binding: fragment_ubo.binding, binding: fragment_ubo.binding,
size: align_uniform_size(fragment_ubo.size), size: align_uniform_size(fragment_ubo.size),
stage_mask: BindingStage::FRAGMENT, stage_mask: BindingStage::FRAGMENT,
@ -570,7 +569,7 @@ where
&mut self, &mut self,
vertex_pcb: Option<&Resource>, vertex_pcb: Option<&Resource>,
fragment_pcb: Option<&Resource>, fragment_pcb: Option<&Resource>,
) -> Result<Option<PushReflection>, ShaderReflectError> { ) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
if let Some(vertex_pcb) = vertex_pcb { if let Some(vertex_pcb) = vertex_pcb {
self.vertex self.vertex
.set_decoration(vertex_pcb.id, Decoration::Binding, 1)?; .set_decoration(vertex_pcb.id, Decoration::Binding, 1)?;
@ -594,7 +593,8 @@ where
let size = std::cmp::max(vertex_size, fragment_size); let size = std::cmp::max(vertex_size, fragment_size);
Ok(Some(PushReflection { Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(size), size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT, stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
})) }))
@ -602,7 +602,8 @@ where
(Some(vertex_push), None) => { (Some(vertex_push), None) => {
let vertex_size = let vertex_size =
Self::get_push_size(&self.vertex, vertex_push, SemanticErrorBlame::Vertex)?; Self::get_push_size(&self.vertex, vertex_push, SemanticErrorBlame::Vertex)?;
Ok(Some(PushReflection { Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(vertex_size), size: align_uniform_size(vertex_size),
stage_mask: BindingStage::VERTEX, stage_mask: BindingStage::VERTEX,
})) }))
@ -613,7 +614,8 @@ where
fragment_push, fragment_push,
SemanticErrorBlame::Fragment, SemanticErrorBlame::Fragment,
)?; )?;
Ok(Some(PushReflection { Ok(Some(BufferReflection {
binding: None,
size: align_uniform_size(fragment_size), size: align_uniform_size(fragment_size),
stage_mask: BindingStage::FRAGMENT, stage_mask: BindingStage::FRAGMENT,
})) }))
@ -922,9 +924,9 @@ mod test {
let mut opts = CompilerOptions::default(); let mut opts = CompilerOptions::default();
opts.version = Version::V4_60; opts.version = Version::V4_60;
opts.enable_420_pack_extension = false; opts.enable_420_pack_extension = false;
let compiled = reflect.compile(Version::V3_30).unwrap(); // let compiled: ShaderCompilerOutput<String, CrossWgslContext> = <CrossReflect<glsl::Target> as CompileShader<WGSL>>::compile(reflect, Version::V3_30).unwrap();
// eprintln!("{shader_reflection:#?}"); // // eprintln!("{shader_reflection:#?}");
eprintln!("{:#}", compiled.fragment) // eprintln!("{}", compiled.fragment)
// let mut loader = rspirv::dr::Loader::new(); // let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap(); // rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
// let module = loader.module(); // let module = loader.module();

View file

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

View file

@ -1,126 +1,858 @@
use crate::error::{SemanticsErrorKind, ShaderReflectError}; use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::front::NagaCompilation;
use naga::front::spv::Options; use naga::{
use naga::{GlobalVariable, Module, StructMember, Type, TypeInner}; AddressSpace, Binding, GlobalVariable, Handle, ImageClass, Module, ResourceBinding, Scalar,
use std::convert::Infallible; ScalarKind, TypeInner, VectorSize,
};
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
use crate::reflect::semantics::{
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderSemantics, TextureBinding,
TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UniformMemberBlock,
UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta, MAX_BINDINGS_COUNT,
MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader, ShaderReflection};
#[derive(Debug)] #[derive(Debug)]
pub struct NagaReflect { pub(crate) struct NagaReflect {
vertex: Module, pub(crate) vertex: Module,
fragment: Module, pub(crate) fragment: Module,
} }
impl TryFrom<NagaCompilation> for NagaReflect { impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
type Error = ShaderReflectError; fn validate_type(&self, ty: &&TypeInner) -> Option<TypeInfo> {
let (TypeInner::Vector { .. } | TypeInner::Scalar { .. } | TypeInner::Matrix { .. }) = *ty
else {
return None;
};
fn try_from(value: NagaCompilation) -> Result<Self, Self::Error> { match self {
Ok(NagaReflect { UniqueSemantics::MVP => {
vertex: value.vertex, if matches!(ty, TypeInner::Matrix { columns, rows, scalar: Scalar { width, .. } } if *columns == VectorSize::Quad
fragment: value.fragment, && *rows == VectorSize::Quad && *width == 4)
}) {
return Some(TypeInfo {
size: 4,
columns: 4,
});
}
}
UniqueSemantics::FrameCount => {
// Uint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, width }) if *kind == ScalarKind::Uint && *width == 4)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
UniqueSemantics::FrameDirection => {
// Uint32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, width }) if *kind == ScalarKind::Sint && *width == 4)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
UniqueSemantics::FloatParameter => {
// Float32 == width 4
if matches!(ty, TypeInner::Scalar( Scalar { kind, width }) if *kind == ScalarKind::Float && *width == 4)
{
return Some(TypeInfo {
size: 1,
columns: 1,
});
}
}
_ => {
if matches!(ty, TypeInner::Vector { scalar: Scalar { width, kind }, size } if *kind == ScalarKind::Float && *width == 4 && *size == VectorSize::Quad)
{
return Some(TypeInfo {
size: 4,
columns: 1,
});
}
}
};
return None;
} }
} }
impl TryFrom<&GlslangCompilation> for NagaReflect { impl ValidateTypeSemantics<&TypeInner> for TextureSemantics {
type Error = ShaderReflectError; fn validate_type(&self, ty: &&TypeInner) -> Option<TypeInfo> {
let TypeInner::Vector {
scalar: Scalar { width, kind },
size,
} = ty
else {
return None;
};
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> { if *kind == ScalarKind::Float && *width == 4 && *size == VectorSize::Quad {
let ops = Options::default(); return Some(TypeInfo {
let vertex = size: 4,
naga::front::spv::Parser::new(value.vertex.clone().into_iter(), &ops).parse()?; columns: 1,
let fragment = });
naga::front::spv::Parser::new(value.fragment.clone().into_iter(), &ops).parse()?;
Ok(NagaReflect { vertex, fragment })
}
}
struct UboData {
// id: u32,
// descriptor_set: u32,
binding: u32,
size: u32,
}
struct Ubo {
members: Vec<StructMember>,
span: u32,
}
impl TryFrom<naga::Type> for Ubo {
type Error = Infallible;
fn try_from(value: Type) -> Result<Self, Infallible> {
match value.inner {
TypeInner::Struct { members, span } => Ok(Ubo { members, span }),
// todo: make this programmer error
_ => panic!(),
} }
None
} }
} }
impl NagaReflect { impl NagaReflect {
// pub fn get_ubo_data(arena: Arena, variable: GlobalVariable, blame: SemanticErrorBlame) -> Result<UboData, ShaderReflectError> { fn reflect_ubos(
// let binding = match variable.binding { &mut self,
// Some(ResourceBinding { group: 0, binding }) => binding, vertex_ubo: Option<Handle<GlobalVariable>>,
// Some(ResourceBinding { group, .. }) => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(group))), fragment_ubo: Option<Handle<GlobalVariable>>,
// None => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(u32::MAX))), ) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
// }; // todo: merge this with the spirv-cross code
// match (vertex_ubo, fragment_ubo) {
// if binding >= MAX_BINDINGS_COUNT { (None, None) => Ok(None),
// return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding))); (Some(vertex_ubo), Some(fragment_ubo)) => {
// } let vertex_ubo = Self::get_ubo_data(
// &self.vertex,
// match variable.ty.as { &self.vertex.global_variables[vertex_ubo],
// Handle { .. } => {} SemanticErrorBlame::Vertex,
// } )?;
// Ok(UboData { let fragment_ubo = Self::get_ubo_data(
// binding, &self.fragment,
// &self.fragment.global_variables[fragment_ubo],
// }) SemanticErrorBlame::Fragment,
// } )?;
pub fn reflect_ubos( if vertex_ubo.binding != fragment_ubo.binding {
vertex: GlobalVariable, return Err(ShaderReflectError::MismatchedUniformBuffer {
fragment: GlobalVariable, vertex: vertex_ubo.binding,
) -> Result<(), ShaderReflectError> { fragment: fragment_ubo.binding,
match (vertex.binding, fragment.binding) { });
// todo: should emit for both but whatever }
(None, None) | (Some(_), None) | (None, Some(_)) => {
ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidDescriptorSet( let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size);
u32::MAX, Ok(Some(BufferReflection {
)) binding: vertex_ubo.binding,
size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
}))
} }
(Some(_vert), Some(_frag)) => { (Some(vertex_ubo), None) => {
todo!(); let vertex_ubo = Self::get_ubo_data(
&self.vertex,
&self.vertex.global_variables[vertex_ubo],
SemanticErrorBlame::Vertex,
)?;
Ok(Some(BufferReflection {
binding: vertex_ubo.binding,
size: align_uniform_size(vertex_ubo.size),
stage_mask: BindingStage::VERTEX,
}))
} }
(None, Some(fragment_ubo)) => {
let fragment_ubo = Self::get_ubo_data(
&self.fragment,
&self.fragment.global_variables[fragment_ubo],
SemanticErrorBlame::Fragment,
)?;
Ok(Some(BufferReflection {
binding: fragment_ubo.binding,
size: align_uniform_size(fragment_ubo.size),
stage_mask: BindingStage::FRAGMENT,
}))
}
}
}
fn get_ubo_data(
module: &Module,
ubo: &GlobalVariable,
blame: SemanticErrorBlame,
) -> Result<UboData, ShaderReflectError> {
let Some(binding) = &ubo.binding else {
return Err(blame.error(SemanticsErrorKind::MissingBinding));
}; };
todo!(); if binding.binding >= MAX_BINDINGS_COUNT {
return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding.binding)));
}
if binding.group != 0 {
return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(binding.group)));
}
let ty = &module.types[ubo.ty];
let size = ty.inner.size(module.to_ctx());
Ok(UboData {
// descriptor_set,
// id: ubo.id,
binding: binding.binding,
size,
})
}
fn get_next_binding(&self, bind_group: u32) -> u32 {
let mut max_bind = 0;
for (_, gv) in self.vertex.global_variables.iter() {
let Some(binding) = &gv.binding else {
continue;
};
if binding.group != bind_group {
continue;
}
max_bind = std::cmp::max(max_bind, binding.binding);
}
for (_, gv) in self.fragment.global_variables.iter() {
let Some(binding) = &gv.binding else {
continue;
};
if binding.group != bind_group {
continue;
}
max_bind = std::cmp::max(max_bind, binding.binding);
}
max_bind + 1
}
fn get_push_size(
module: &Module,
push: &GlobalVariable,
blame: SemanticErrorBlame,
) -> Result<u32, ShaderReflectError> {
let ty = &module.types[push.ty];
let size = ty.inner.size(module.to_ctx());
if size > MAX_PUSH_BUFFER_SIZE {
return Err(blame.error(SemanticsErrorKind::InvalidPushBufferSize(size)));
}
Ok(size)
}
fn reflect_push_constant_buffer(
&mut self,
vertex_pcb: Option<Handle<GlobalVariable>>,
fragment_pcb: Option<Handle<GlobalVariable>>,
) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
let binding = self.get_next_binding(0);
// Reassign to UBO later if we want during compilation.
if let Some(vertex_pcb) = vertex_pcb {
let ubo = &mut self.vertex.global_variables[vertex_pcb];
ubo.binding = Some(ResourceBinding { group: 0, binding });
}
if let Some(fragment_pcb) = fragment_pcb {
let ubo = &mut self.fragment.global_variables[fragment_pcb];
ubo.binding = Some(ResourceBinding { group: 0, binding });
};
match (vertex_pcb, fragment_pcb) {
(None, None) => Ok(None),
(Some(vertex_push), Some(fragment_push)) => {
let vertex_size = Self::get_push_size(
&self.vertex,
&self.vertex.global_variables[vertex_push],
SemanticErrorBlame::Vertex,
)?;
let fragment_size = Self::get_push_size(
&self.fragment,
&self.fragment.global_variables[fragment_push],
SemanticErrorBlame::Fragment,
)?;
let size = std::cmp::max(vertex_size, fragment_size);
Ok(Some(BufferReflection {
binding: Some(binding),
size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
}))
}
(Some(vertex_push), None) => {
let vertex_size = Self::get_push_size(
&self.vertex,
&self.vertex.global_variables[vertex_push],
SemanticErrorBlame::Vertex,
)?;
Ok(Some(BufferReflection {
binding: Some(binding),
size: align_uniform_size(vertex_size),
stage_mask: BindingStage::VERTEX,
}))
}
(None, Some(fragment_push)) => {
let fragment_size = Self::get_push_size(
&self.fragment,
&self.fragment.global_variables[fragment_push],
SemanticErrorBlame::Fragment,
)?;
Ok(Some(BufferReflection {
binding: Some(binding),
size: align_uniform_size(fragment_size),
stage_mask: BindingStage::FRAGMENT,
}))
}
}
}
fn validate(&self) -> Result<(), ShaderReflectError> {
// Verify types
if self.vertex.global_variables.iter().any(|(_, gv)| {
let ty = &self.vertex.types[gv.ty];
match ty.inner {
TypeInner::Scalar { .. }
| TypeInner::Vector { .. }
| TypeInner::Matrix { .. }
| TypeInner::Struct { .. } => false,
_ => true,
}
}) {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidResourceType,
));
}
if self.fragment.global_variables.iter().any(|(_, gv)| {
let ty = &self.fragment.types[gv.ty];
match ty.inner {
TypeInner::Scalar { .. }
| TypeInner::Vector { .. }
| TypeInner::Matrix { .. }
| TypeInner::Struct { .. }
| TypeInner::Image { .. }
| TypeInner::Sampler { .. } => false,
TypeInner::BindingArray { base, .. } => {
let ty = &self.fragment.types[base];
match ty.inner {
TypeInner::Image { class, .. }
if !matches!(class, ImageClass::Storage { .. }) =>
{
false
}
TypeInner::Sampler { .. } => false,
_ => true,
}
}
_ => true,
}
}) {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidResourceType,
));
}
// Verify Vertex inputs
'vertex: {
if self.vertex.entry_points.len() != 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidEntryPointCount(self.vertex.entry_points.len()),
));
}
let vertex_entry_point = &self.vertex.entry_points[0];
let vert_inputs = vertex_entry_point.function.arguments.len();
if vert_inputs != 2 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidInputCount(vert_inputs),
));
}
for input in &vertex_entry_point.function.arguments {
let &Some(Binding::Location { location, .. }) = &input.binding else {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if location == 0 {
let pos_type = &self.vertex.types[input.ty];
if !matches!(pos_type.inner, TypeInner::Vector { size, ..} if size == VectorSize::Quad)
{
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
if location == 1 {
let coord_type = &self.vertex.types[input.ty];
if !matches!(coord_type.inner, TypeInner::Vector { size, ..} if size == VectorSize::Bi)
{
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
break 'vertex;
}
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
let uniform_buffer_count = self
.vertex
.global_variables
.iter()
.filter(|(_, gv)| gv.space == AddressSpace::Uniform)
.count();
if uniform_buffer_count > 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount(uniform_buffer_count),
));
}
let push_buffer_count = self
.vertex
.global_variables
.iter()
.filter(|(_, gv)| gv.space == AddressSpace::PushConstant)
.count();
if push_buffer_count > 1 {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::InvalidPushBufferCount(push_buffer_count),
));
}
}
{
if self.fragment.entry_points.len() != 1 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidEntryPointCount(self.vertex.entry_points.len()),
));
}
let frag_entry_point = &self.fragment.entry_points[0];
let Some(frag_output) = &frag_entry_point.function.result else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidOutputCount(0),
));
};
let &Some(Binding::Location { location, .. }) = &frag_output.binding else {
return Err(ShaderReflectError::VertexSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
if location != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidLocation(location),
));
}
let uniform_buffer_count = self
.fragment
.global_variables
.iter()
.filter(|(_, gv)| gv.space == AddressSpace::Uniform)
.count();
if uniform_buffer_count > 1 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidUniformBufferCount(uniform_buffer_count),
));
}
let push_buffer_count = self
.fragment
.global_variables
.iter()
.filter(|(_, gv)| gv.space == AddressSpace::PushConstant)
.count();
if push_buffer_count > 1 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidPushBufferCount(push_buffer_count),
));
}
}
Ok(())
}
fn reflect_buffer_struct_members(
module: &Module,
resource: Handle<GlobalVariable>,
pass_number: usize,
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
offset_type: UniformMemberBlock,
blame: SemanticErrorBlame,
) -> Result<(), ShaderReflectError> {
let resource = &module.global_variables[resource];
let TypeInner::Struct { members, .. } = &module.types[resource.ty].inner else {
return Err(blame.error(SemanticsErrorKind::InvalidResourceType));
};
for member in members {
let Some(name) = member.name.clone() else {
return Err(blame.error(SemanticsErrorKind::InvalidRange(member.offset)));
};
let member_type = &module.types[member.ty].inner;
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)));
};
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = member.offset;
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,
expected,
received: offset as usize,
ty: offset_type,
pass: pass_number,
});
}
if meta.size != typeinfo.size {
return Err(ShaderReflectError::MismatchedSize {
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
});
}
*meta.offset.offset_mut(offset_type) = Some(offset as usize);
} else {
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
id: name,
offset: MemberOffset::new(offset as usize, offset_type),
size: typeinfo.size,
},
);
}
}
semantics => {
let offset = member.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
expected,
received: offset as usize,
ty: offset_type,
pass: pass_number,
});
}
if meta.size != typeinfo.size * typeinfo.columns {
return Err(ShaderReflectError::MismatchedSize {
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number,
});
}
*meta.offset.offset_mut(offset_type) = Some(offset as usize);
} else {
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: name,
offset: MemberOffset::new(offset as usize, offset_type),
size: typeinfo.size * typeinfo.columns,
},
);
}
}
}
} 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)));
};
if let TextureSemantics::PassOutput = texture.semantics {
if texture.index >= pass_number {
return Err(ShaderReflectError::NonCausalFilterChain {
pass: pass_number,
target: texture.index,
});
}
}
let offset = member.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset as usize
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
expected,
received: offset as usize,
ty: offset_type,
pass: pass_number,
});
}
meta.stage_mask.insert(match blame {
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
});
*meta.offset.offset_mut(offset_type) = Some(offset as usize);
} else {
meta.texture_size_meta.insert(
texture,
TextureSizeMeta {
offset: MemberOffset::new(offset as usize, offset_type),
stage_mask: match blame {
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
},
id: name,
},
);
}
} else {
return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name)));
}
}
Ok(())
}
fn reflect_texture<'a>(
&'a self,
texture: &'a GlobalVariable,
) -> Result<TextureData<'a>, ShaderReflectError> {
let Some(binding) = &texture.binding else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::MissingBinding,
));
};
let Some(name) = texture.name.as_ref() else {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidBinding(binding.binding),
));
};
if binding.group != 0 {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidDescriptorSet(binding.group),
));
}
if binding.binding >= MAX_BINDINGS_COUNT {
return Err(ShaderReflectError::FragmentSemanticError(
SemanticsErrorKind::InvalidBinding(binding.binding),
));
}
Ok(TextureData {
// id: texture.id,
// descriptor_set,
name: &name,
binding: binding.binding,
})
}
// todo: share this with cross
fn reflect_texture_metas(
&self,
texture: TextureData,
pass_number: usize,
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
) -> Result<(), ShaderReflectError> {
let Some(semantic) = semantics
.texture_semantics
.get_texture_semantic(texture.name)
else {
return Err(
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
texture.name.to_string(),
)),
);
};
if semantic.semantics == TextureSemantics::PassOutput && semantic.index >= pass_number {
return Err(ShaderReflectError::NonCausalFilterChain {
pass: pass_number,
target: semantic.index,
});
}
meta.texture_meta.insert(
semantic,
TextureBinding {
binding: texture.binding,
},
);
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod test {
use librashader_preprocess::ShaderSource;
use rspirv::dr::Instruction;
use rspirv::spirv::Op;
#[test] impl ReflectShader for NagaReflect {
pub fn test_into() { fn reflect(
let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap(); &mut self,
let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap(); pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
self.validate()?;
let mut loader = rspirv::dr::Loader::new(); // Validate verifies that there's only one uniform block.
rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap(); let vertex_ubo = self
let module = loader.module(); .vertex
.global_variables
let outputs: Vec<&Instruction> = module
.types_global_values
.iter() .iter()
.filter(|i| i.class.opcode == Op::Variable) .find_map(|(handle, gv)| {
.collect(); if gv.space == AddressSpace::Uniform {
Some(handle)
} else {
None
}
});
println!("{outputs:#?}"); let fragment_ubo = self
.fragment
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::Uniform {
Some(handle)
} else {
None
}
});
let ubo = self.reflect_ubos(vertex_ubo, fragment_ubo)?;
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
}
});
let push_constant = self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
let mut meta = BindingMeta::default();
if let Some(ubo) = vertex_ubo {
Self::reflect_buffer_struct_members(
&self.vertex,
ubo,
pass_number,
semantics,
&mut meta,
UniformMemberBlock::Ubo,
SemanticErrorBlame::Vertex,
)?;
}
if let Some(ubo) = fragment_ubo {
Self::reflect_buffer_struct_members(
&self.fragment,
ubo,
pass_number,
semantics,
&mut meta,
UniformMemberBlock::Ubo,
SemanticErrorBlame::Fragment,
)?;
}
if let Some(push) = vertex_push {
Self::reflect_buffer_struct_members(
&self.vertex,
push,
pass_number,
semantics,
&mut meta,
UniformMemberBlock::PushConstant,
SemanticErrorBlame::Vertex,
)?;
}
if let Some(push) = fragment_push {
Self::reflect_buffer_struct_members(
&self.fragment,
push,
pass_number,
semantics,
&mut meta,
UniformMemberBlock::PushConstant,
SemanticErrorBlame::Fragment,
)?;
}
let mut ubo_bindings = 0u16;
if vertex_ubo.is_some() || fragment_ubo.is_some() {
ubo_bindings = 1 << ubo.as_ref().expect("UBOs should be present").binding;
}
let textures = self.fragment.global_variables.iter().filter(|(_, gv)| {
let ty = &self.fragment.types[gv.ty];
matches!(ty.inner, TypeInner::Image { .. })
});
for (_, texture) in textures {
let texture_data = self.reflect_texture(texture)?;
if ubo_bindings & (1 << texture_data.binding) != 0 {
return Err(ShaderReflectError::BindingInUse(texture_data.binding));
}
ubo_bindings |= 1 << texture_data.binding;
self.reflect_texture_metas(texture_data, pass_number, semantics, &mut meta)?;
}
Ok(ShaderReflection {
ubo,
push_constant,
meta,
})
} }
} }
#[cfg(test)]
mod test {
// #[test]
// pub fn test_into() {
// let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
// let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
//
// let mut loader = rspirv::dr::Loader::new();
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
// let module = loader.module();
//
// let outputs: Vec<&Instruction> = module
// .types_global_values
// .iter()
// .filter(|i| i.class.opcode == Op::Variable)
// .collect();
//
// println!("{outputs:#?}");
// }
}

View file

@ -169,26 +169,17 @@ bitflags! {
} }
} }
/// Reflection information for the Uniform Buffer /// Reflection information for the Uniform Buffer or Push Constant Block
#[derive(Debug)] #[derive(Debug)]
pub struct UboReflection { pub struct BufferReflection<T> {
/// The binding point for this UBO. /// The binding point for this buffer, if applicable
pub binding: u32, pub binding: T,
/// The size of the UBO buffer. UBO sizes returned by reflection is always aligned to a 16 byte boundary. /// The size of the buffer. Buffer sizes returned by reflection is always aligned to a 16 byte boundary.
pub size: u32, pub size: u32,
/// The mask indicating for which stages the UBO should be bound. /// The mask indicating for which stages the UBO should be bound.
pub stage_mask: BindingStage, pub stage_mask: BindingStage,
} }
/// Reflection information for the Push Constant Block
#[derive(Debug)]
pub struct PushReflection {
/// The size of the Push Constant range. The size returned by reflection is always aligned to a 16 byte boundary.
pub size: u32,
/// The mask indicating for which stages the Push Constant range should be bound.
pub stage_mask: BindingStage,
}
/// The offset of a uniform member. /// The offset of a uniform member.
/// ///
/// A uniform can be bound to both the UBO, or as a Push Constant. /// A uniform can be bound to both the UBO, or as a Push Constant.
@ -279,9 +270,9 @@ pub struct TextureBinding {
#[derive(Debug)] #[derive(Debug)]
pub struct ShaderReflection { pub struct ShaderReflection {
/// Reflection information about the UBO for this shader. /// Reflection information about the UBO for this shader.
pub ubo: Option<UboReflection>, pub ubo: Option<BufferReflection<u32>>,
/// Reflection information about the Push Constant range for this shader. /// Reflection information about the Push Constant range for this shader.
pub push_constant: Option<PushReflection>, pub push_constant: Option<BufferReflection<Option<u32>>>,
/// Metadata about the bindings required for this shader. /// Metadata about the bindings required for this shader.
pub meta: BindingMeta, pub meta: BindingMeta,
} }

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d11"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -12,13 +12,13 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all." description = "RetroArch shaders for all."
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", features = ["d3d11"], version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["standalone"] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.2" } librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.5" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" } spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2", features = ["d3d"] } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5", features = ["d3d"] }
thiserror = "1.0.37" thiserror = "1.0.37"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"

View file

@ -265,7 +265,9 @@ impl FilterChainD3D11 {
disable_cache, disable_cache,
)?; )?;
let ubo_cbuffer = if let Some(ubo) = &reflection.ubo && ubo.size != 0 { let ubo_cbuffer = if let Some(ubo) = &reflection.ubo
&& ubo.size != 0
{
let buffer = FilterChainD3D11::create_constant_buffer(device, ubo.size)?; let buffer = FilterChainD3D11::create_constant_buffer(device, ubo.size)?;
Some(ConstantBufferBinding { Some(ConstantBufferBinding {
binding: ubo.binding, binding: ubo.binding,
@ -277,7 +279,9 @@ impl FilterChainD3D11 {
None None
}; };
let push_cbuffer = if let Some(push) = &reflection.push_constant && push.size != 0 { let push_cbuffer = if let Some(push) = &reflection.push_constant
&& push.size != 0
{
let buffer = FilterChainD3D11::create_constant_buffer(device, push.size)?; let buffer = FilterChainD3D11::create_constant_buffer(device, push.size)?;
Some(ConstantBufferBinding { Some(ConstantBufferBinding {
binding: if ubo_cbuffer.is_some() { 1 } else { 0 }, binding: if ubo_cbuffer.is_some() { 1 } else { 0 },

View file

@ -97,7 +97,8 @@ pub fn d3d11_get_closest_format(
for supported in format_support_list { for supported in format_support_list {
unsafe { unsafe {
if let Ok(supported_format) = device.CheckFormatSupport(*supported) if let Ok(supported_format) = device.CheckFormatSupport(*supported)
&& (supported_format & format_support_mask) == format_support_mask { && (supported_format & format_support_mask) == format_support_mask
{
return *supported; return *supported;
} }
} }

View file

@ -13,7 +13,7 @@ use librashader_runtime_d3d11::options::FilterChainOptionsD3D11;
// "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID].slangp"; // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID].slangp";
const FILTER_PATH: &str = const FILTER_PATH: &str =
"../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp"; "../test/shaders-slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
// const FILTER_PATH: &str = "../test/slang-shaders/test/history.slangp"; // const FILTER_PATH: &str = "../test/slang-shaders/test/history.slangp";
// const FILTER_PATH: &str = "../test/slang-shaders/test/feedback.slangp"; // const FILTER_PATH: &str = "../test/slang-shaders/test/feedback.slangp";

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d12"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -12,12 +12,12 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all." description = "RetroArch shaders for all."
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d12"], version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", features = ["d3d12"], version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["dxil", "standalone"] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["dxil", "standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.2" } librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.5" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2", features = ["d3d"] } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5", features = ["d3d"] }
thiserror = "1.0.37" thiserror = "1.0.37"
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" } spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }

View file

@ -15,7 +15,6 @@ use windows::Win32::Graphics::Direct3D12::{
D3D12_GPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE,
}; };
#[const_trait]
pub trait D3D12HeapType { pub trait D3D12HeapType {
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC; fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC;
} }
@ -36,7 +35,7 @@ pub struct ResourceWorkHeap;
#[derive(Clone)] #[derive(Clone)]
pub struct SamplerWorkHeap; pub struct SamplerWorkHeap;
impl const D3D12HeapType for SamplerPaletteHeap { impl D3D12HeapType for SamplerPaletteHeap {
// sampler palettes just get set directly // sampler palettes just get set directly
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC { fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC { D3D12_DESCRIPTOR_HEAP_DESC {
@ -48,7 +47,7 @@ impl const D3D12HeapType for SamplerPaletteHeap {
} }
} }
impl const D3D12HeapType for CpuStagingHeap { impl D3D12HeapType for CpuStagingHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader. // Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC { fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC { D3D12_DESCRIPTOR_HEAP_DESC {
@ -60,7 +59,7 @@ impl const D3D12HeapType for CpuStagingHeap {
} }
} }
impl const D3D12HeapType for RenderTargetHeap { impl D3D12HeapType for RenderTargetHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader. // Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC { fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC { D3D12_DESCRIPTOR_HEAP_DESC {
@ -73,7 +72,7 @@ impl const D3D12HeapType for RenderTargetHeap {
} }
impl D3D12ShaderVisibleHeapType for ResourceWorkHeap {} impl D3D12ShaderVisibleHeapType for ResourceWorkHeap {}
impl const D3D12HeapType for ResourceWorkHeap { impl D3D12HeapType for ResourceWorkHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader. // Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC { fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC { D3D12_DESCRIPTOR_HEAP_DESC {
@ -86,7 +85,7 @@ impl const D3D12HeapType for ResourceWorkHeap {
} }
impl D3D12ShaderVisibleHeapType for SamplerWorkHeap {} impl D3D12ShaderVisibleHeapType for SamplerWorkHeap {}
impl const D3D12HeapType for SamplerWorkHeap { impl D3D12HeapType for SamplerWorkHeap {
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader. // Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC { fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC { D3D12_DESCRIPTOR_HEAP_DESC {

View file

@ -477,33 +477,32 @@ impl FilterChainD3D12 {
.into(); .into();
// incredibly cursed. // incredibly cursed.
let (reflection, graphics_pipeline) = if !force_hlsl && let (reflection, graphics_pipeline) = if !force_hlsl
let Ok(graphics_pipeline) = && let Ok(graphics_pipeline) = D3D12GraphicsPipeline::new_from_dxil(
D3D12GraphicsPipeline::new_from_dxil(
device, device,
library, library,
validator, validator,
&dxil, &dxil,
root_signature, root_signature,
render_format, render_format,
disable_cache disable_cache,
) { ) {
(dxil_reflection, graphics_pipeline) (dxil_reflection, graphics_pipeline)
} else { } else {
let hlsl_reflection = hlsl.reflect(index, semantics)?; let hlsl_reflection = hlsl.reflect(index, semantics)?;
let hlsl = hlsl.compile(Some(ShaderModel::V6_0))?; let hlsl = hlsl.compile(Some(ShaderModel::V6_0))?;
let graphics_pipeline = D3D12GraphicsPipeline::new_from_hlsl( let graphics_pipeline = D3D12GraphicsPipeline::new_from_hlsl(
device, device,
library, library,
compiler, compiler,
&hlsl, &hlsl,
root_signature, root_signature,
render_format, render_format,
disable_cache disable_cache,
)?; )?;
(hlsl_reflection, graphics_pipeline) (hlsl_reflection, graphics_pipeline)
}; };
// minimum size here has to be 1 byte. // minimum size here has to be 1 byte.
let ubo_size = reflection.ubo.as_ref().map_or(1, |ubo| ubo.size as usize); let ubo_size = reflection.ubo.as_ref().map_or(1, |ubo| ubo.size as usize);

View file

@ -1,9 +1,7 @@
#![cfg(target_os = "windows")] #![cfg(target_os = "windows")]
#![feature(const_trait_impl)] #![deny(unsafe_op_in_unsafe_fn)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(int_roundings)]
#![deny(unsafe_op_in_unsafe_fn)]
mod buffer; mod buffer;
mod descriptor_heap; mod descriptor_heap;

View file

@ -5,9 +5,9 @@ use crate::hello_triangle::{DXSample, SampleCommandLine};
#[test] #[test]
fn triangle_d3d12() { fn triangle_d3d12() {
let sample = hello_triangle::d3d12_hello_triangle::Sample::new( let sample = hello_triangle::d3d12_hello_triangle::Sample::new(
//"../test/shaders_slang/crt/crt-lottes.slangp", // "../test/shaders_slang/crt/crt-lottes.slangp",
// "../test/basic.slangp", // "../test/basic.slangp",
"../test/shaders_slang/handheld/console-border/gbc-lcd-grid-v2.slangp", // "../test/shaders_slang/handheld/console-border/gbc-lcd-grid-v2.slangp",
// "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp",
// "../test/slang-shaders/test/feedback.slangp", // "../test/slang-shaders/test/feedback.slangp",
// "../test/slang-shaders/test/history.slangp", // "../test/slang-shaders/test/history.slangp",

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-gl"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -12,12 +12,12 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all." description = "RetroArch shaders for all."
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", features = ["opengl"], version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", features = ["opengl"], version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["standalone"] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["standalone"] }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.2" } librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.5" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2" } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" } spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
@ -26,11 +26,10 @@ bytemuck = "1.12.3"
thiserror = "1.0.37" thiserror = "1.0.37"
rayon = "1.6.1" rayon = "1.6.1"
sptr = "0.3"
[dev-dependencies] [dev-dependencies]
glfw = "0.47.0" glfw = "0.47.0"
[[test]]
name = "triangle"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["librashader-cache/docsrs"] features = ["librashader-cache/docsrs"]

View file

@ -68,10 +68,14 @@ where
&& location.bindable() && location.bindable()
{ {
if location.is_valid(BindingStage::VERTEX) { if location.is_valid(BindingStage::VERTEX) {
unsafe { T::FACTORY(location.vertex, value); } unsafe {
T::FACTORY(location.vertex, value);
}
} }
if location.is_valid(BindingStage::FRAGMENT) { if location.is_valid(BindingStage::FRAGMENT) {
unsafe { T::FACTORY(location.fragment, value); } unsafe {
T::FACTORY(location.fragment, value);
}
} }
Some(()) Some(())
} else { } else {

View file

@ -27,12 +27,14 @@ impl FilterChainGL {
options: Option<&FilterChainOptionsGL>, options: Option<&FilterChainOptionsGL>,
) -> Result<Self> { ) -> Result<Self> {
let result = catch_unwind(|| { let result = catch_unwind(|| {
if let Some(options) = options && options.use_dsa { if let Some(options) = options
&& options.use_dsa
{
return Ok(Self { return Ok(Self {
filter: FilterChainDispatch::DirectStateAccess(unsafe { filter: FilterChainDispatch::DirectStateAccess(unsafe {
FilterChainImpl::load_from_preset(preset, Some(options))? FilterChainImpl::load_from_preset(preset, Some(options))?
}) }),
}) });
} }
Ok(Self { Ok(Self {
filter: FilterChainDispatch::Compatibility(unsafe { filter: FilterChainDispatch::Compatibility(unsafe {

View file

@ -52,7 +52,7 @@ impl DrawQuad for Gl3DrawQuad {
gl::FLOAT, gl::FLOAT,
gl::FALSE, gl::FALSE,
(4 * std::mem::size_of::<f32>()) as GLsizei, (4 * std::mem::size_of::<f32>()) as GLsizei,
std::ptr::invalid(0), sptr::invalid(0),
); );
gl::VertexAttribPointer( gl::VertexAttribPointer(
1, 1,
@ -60,7 +60,7 @@ impl DrawQuad for Gl3DrawQuad {
gl::FLOAT, gl::FLOAT,
gl::FALSE, gl::FALSE,
(4 * std::mem::size_of::<f32>()) as GLsizei, (4 * std::mem::size_of::<f32>()) as GLsizei,
std::ptr::invalid(2 * std::mem::size_of::<f32>()), sptr::invalid(2 * std::mem::size_of::<f32>()),
); );
} }
} }

View file

@ -1,7 +1,7 @@
use crate::binding::UniformLocation; use crate::binding::UniformLocation;
use crate::gl::UboRing; use crate::gl::UboRing;
use gl::types::{GLsizei, GLsizeiptr, GLuint}; use gl::types::{GLsizei, GLsizeiptr, GLuint};
use librashader_reflect::reflect::semantics::UboReflection; use librashader_reflect::reflect::semantics::BufferReflection;
use librashader_runtime::ringbuffer::InlineRingBuffer; use librashader_runtime::ringbuffer::InlineRingBuffer;
use librashader_runtime::ringbuffer::RingBuffer; use librashader_runtime::ringbuffer::RingBuffer;
use librashader_runtime::uniforms::UniformStorageAccess; use librashader_runtime::uniforms::UniformStorageAccess;
@ -31,7 +31,7 @@ impl<const SIZE: usize> UboRing<SIZE> for Gl3UboRing<SIZE> {
fn bind_for_frame( fn bind_for_frame(
&mut self, &mut self,
ubo: &UboReflection, ubo: &BufferReflection<u32>,
ubo_location: &UniformLocation<GLuint>, ubo_location: &UniformLocation<GLuint>,
storage: &impl UniformStorageAccess, storage: &impl UniformStorageAccess,
) { ) {

View file

@ -1,7 +1,7 @@
use crate::binding::UniformLocation; use crate::binding::UniformLocation;
use crate::gl::UboRing; use crate::gl::UboRing;
use gl::types::{GLsizei, GLsizeiptr, GLuint}; use gl::types::{GLsizei, GLsizeiptr, GLuint};
use librashader_reflect::reflect::semantics::UboReflection; use librashader_reflect::reflect::semantics::BufferReflection;
use librashader_runtime::ringbuffer::InlineRingBuffer; use librashader_runtime::ringbuffer::InlineRingBuffer;
use librashader_runtime::ringbuffer::RingBuffer; use librashader_runtime::ringbuffer::RingBuffer;
use librashader_runtime::uniforms::UniformStorageAccess; use librashader_runtime::uniforms::UniformStorageAccess;
@ -30,7 +30,7 @@ impl<const SIZE: usize> UboRing<SIZE> for Gl46UboRing<SIZE> {
fn bind_for_frame( fn bind_for_frame(
&mut self, &mut self,
ubo: &UboReflection, ubo: &BufferReflection<u32>,
ubo_location: &UniformLocation<GLuint>, ubo_location: &UniformLocation<GLuint>,
storage: &impl UniformStorageAccess, storage: &impl UniformStorageAccess,
) { ) {

View file

@ -13,7 +13,7 @@ use librashader_common::{ImageFormat, Size};
use librashader_presets::{Scale2D, TextureConfig}; use librashader_presets::{Scale2D, TextureConfig};
use librashader_reflect::back::cross::CrossGlslContext; use librashader_reflect::back::cross::CrossGlslContext;
use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection}; use librashader_reflect::reflect::semantics::{BufferReflection, TextureBinding};
use librashader_runtime::uniforms::UniformStorageAccess; use librashader_runtime::uniforms::UniformStorageAccess;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -38,7 +38,7 @@ pub(crate) trait UboRing<const SIZE: usize> {
fn new(buffer_size: u32) -> Self; fn new(buffer_size: u32) -> Self;
fn bind_for_frame( fn bind_for_frame(
&mut self, &mut self,
ubo: &UboReflection, ubo: &BufferReflection<u32>,
ubo_location: &UniformLocation<GLuint>, ubo_location: &UniformLocation<GLuint>,
storage: &impl UniformStorageAccess, storage: &impl UniformStorageAccess,
); );

View file

@ -3,10 +3,8 @@
//! This crate should not be used directly. //! This crate should not be used directly.
//! See [`librashader::runtime::gl`](https://docs.rs/librashader/latest/librashader/runtime/gl/index.html) instead. //! See [`librashader::runtime::gl`](https://docs.rs/librashader/latest/librashader/runtime/gl/index.html) instead.
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![feature(strict_provenance)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)]
mod binding; mod binding;
mod filter_chain; mod filter_chain;

View file

@ -9,7 +9,7 @@ fn triangle_gl() {
unsafe { unsafe {
let mut filter = FilterChainGL::load_from_path( let mut filter = FilterChainGL::load_from_path(
"../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
Some(&FilterChainOptionsGL { Some(&FilterChainOptionsGL {
glsl_version: 0, glsl_version: 0,
use_dsa: false, use_dsa: false,
@ -30,8 +30,8 @@ fn triangle_gl46() {
let mut filter = FilterChainGL::load_from_path( let mut filter = FilterChainGL::load_from_path(
// "../test/slang-shaders/vhs/VHSPro.slangp", // "../test/slang-shaders/vhs/VHSPro.slangp",
// "../test/slang-shaders/test/history.slangp", // "../test/slang-shaders/test/history.slangp",
// "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
"../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", // "../test/shadersslang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
Some(&FilterChainOptionsGL { Some(&FilterChainOptionsGL {
glsl_version: 0, glsl_version: 0,
use_dsa: true, use_dsa: true,

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-vk"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -14,12 +14,12 @@ description = "RetroArch shaders for all."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", features = ["vulkan"], version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", features = ["vulkan"], version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = [] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = [] }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.2" } librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.5" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2" } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" } spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
@ -37,9 +37,5 @@ winit = "0.27.5"
raw-window-handle = "0.5" raw-window-handle = "0.5"
ash-window = "0.12.0" ash-window = "0.12.0"
[[test]]
name = "triangle"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["librashader-cache/docsrs"] features = ["librashader-cache/docsrs"]

View file

@ -188,7 +188,8 @@ impl FrameResiduals {
} }
for framebuffer in self.framebuffers.drain(0..) { for framebuffer in self.framebuffers.drain(0..) {
if let Some(framebuffer) = framebuffer if let Some(framebuffer) = framebuffer
&& framebuffer != vk::Framebuffer::null() { && framebuffer != vk::Framebuffer::null()
{
unsafe { unsafe {
self.device.destroy_framebuffer(framebuffer, None); self.device.destroy_framebuffer(framebuffer, None);
} }
@ -697,8 +698,13 @@ impl FilterChainVulkan {
// try to hint the optimizer // try to hint the optimizer
assert_eq!(last.len(), 1); assert_eq!(last.len(), 1);
if let Some(pass) = last.iter_mut().next() { if let Some(pass) = last.iter_mut().next() {
if let Some(format) = pass.graphics_pipeline.render_pass.as_ref().map(|r| r.format) if let Some(format) = pass
&& format != viewport.output.format { .graphics_pipeline
.render_pass
.as_ref()
.map(|r| r.format)
&& format != viewport.output.format
{
// need to recompile // need to recompile
pass.graphics_pipeline.recompile(viewport.output.format)?; pass.graphics_pipeline.recompile(viewport.output.format)?;
} }

View file

@ -124,9 +124,7 @@ impl FilterPass {
output.output.begin_pass(cmd); output.output.begin_pass(cmd);
let residual = self let residual = self.graphics_pipeline.begin_rendering(output, cmd)?;
.graphics_pipeline
.begin_rendering(&parent.device, output, cmd)?;
unsafe { unsafe {
parent.device.cmd_bind_pipeline( parent.device.cmd_bind_pipeline(
@ -178,7 +176,7 @@ impl FilterPass {
.device .device
.cmd_set_viewport(cmd, 0, &[output.output.size.into()]); .cmd_set_viewport(cmd, 0, &[output.output.size.into()]);
parent.draw_quad.draw_quad(cmd, vbo_type); parent.draw_quad.draw_quad(cmd, vbo_type);
self.graphics_pipeline.end_rendering(&parent.device, cmd); self.graphics_pipeline.end_rendering(cmd);
} }
Ok(residual) Ok(residual)
} }

View file

@ -7,7 +7,7 @@ use crate::render_pass::VulkanRenderPass;
use ash::vk::PushConstantRange; use ash::vk::PushConstantRange;
use librashader_cache::cache_pipeline; use librashader_cache::cache_pipeline;
use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection}; use librashader_reflect::reflect::semantics::{BufferReflection, TextureBinding};
use librashader_reflect::reflect::ShaderReflection; use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::render_target::RenderTarget; use librashader_runtime::render_target::RenderTarget;
use std::ffi::CStr; use std::ffi::CStr;
@ -30,8 +30,10 @@ impl PipelineDescriptors {
} }
} }
pub fn add_ubo_binding(&mut self, ubo_meta: Option<&UboReflection>) { pub fn add_ubo_binding(&mut self, ubo_meta: Option<&BufferReflection<u32>>) {
if let Some(ubo_meta) = ubo_meta && !ubo_meta.stage_mask.is_empty() { if let Some(ubo_meta) = ubo_meta
&& !ubo_meta.stage_mask.is_empty()
{
let ubo_mask = util::binding_stage_to_vulkan_stage(ubo_meta.stage_mask); let ubo_mask = util::binding_stage_to_vulkan_stage(ubo_meta.stage_mask);
self.layout_bindings.push(vk::DescriptorSetLayoutBinding { self.layout_bindings.push(vk::DescriptorSetLayoutBinding {
@ -119,18 +121,6 @@ impl PipelineLayoutObjects {
let pipeline_create_info = pipeline_create_info.push_constant_ranges(push_constant_range); let pipeline_create_info = pipeline_create_info.push_constant_ranges(push_constant_range);
// let pipeline_create_info = if let Some(push_constant) = &reflection.push_constant {
// let stage_mask = util::binding_stage_to_vulkan_stage(push_constant.stage_mask);
// let push_constant_range = vk::PushConstantRange::builder()
// .stage_flags(stage_mask)
// .size(push_constant.size);
// let push_constant_range = [*push_constant_range];
// pipeline_create_info
// .push_constant_ranges(&push_constant_range)
// } else {
// pipeline_create_info
// };
let layout = unsafe { device.create_pipeline_layout(&pipeline_create_info, None)? }; let layout = unsafe { device.create_pipeline_layout(&pipeline_create_info, None)? };
let pool_info = vk::DescriptorPoolCreateInfo::builder() let pool_info = vk::DescriptorPoolCreateInfo::builder()
@ -396,14 +386,13 @@ impl VulkanGraphicsPipeline {
#[inline(always)] #[inline(always)]
pub(crate) fn begin_rendering( pub(crate) fn begin_rendering(
&self, &self,
device: &ash::Device,
output: &RenderTarget<OutputImage>, output: &RenderTarget<OutputImage>,
cmd: vk::CommandBuffer, cmd: vk::CommandBuffer,
) -> error::Result<Option<vk::Framebuffer>> { ) -> error::Result<Option<vk::Framebuffer>> {
if let Some(render_pass) = &self.render_pass { if let Some(render_pass) = &self.render_pass {
let attachments = [output.output.image_view]; let attachments = [output.output.image_view];
let framebuffer = unsafe { let framebuffer = unsafe {
device.create_framebuffer( self.device.create_framebuffer(
&vk::FramebufferCreateInfo::builder() &vk::FramebufferCreateInfo::builder()
.render_pass(render_pass.handle) .render_pass(render_pass.handle)
.attachments(&attachments) .attachments(&attachments)
@ -430,7 +419,11 @@ impl VulkanGraphicsPipeline {
extent: output.output.size.into(), extent: output.output.size.into(),
}); });
unsafe { unsafe {
device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE); self.device.cmd_begin_render_pass(
cmd,
&render_pass_info,
vk::SubpassContents::INLINE,
);
} }
Ok(Some(framebuffer)) Ok(Some(framebuffer))
} else { } else {
@ -450,18 +443,18 @@ impl VulkanGraphicsPipeline {
.color_attachments(&attachments); .color_attachments(&attachments);
unsafe { unsafe {
device.cmd_begin_rendering(cmd, &rendering_info); self.device.cmd_begin_rendering(cmd, &rendering_info);
} }
Ok(None) Ok(None)
} }
} }
pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) { pub(crate) fn end_rendering(&self, cmd: vk::CommandBuffer) {
unsafe { unsafe {
if self.render_pass.is_none() { if self.render_pass.is_none() {
device.cmd_end_rendering(cmd); self.device.cmd_end_rendering(cmd);
} else { } else {
device.cmd_end_render_pass(cmd) self.device.cmd_end_render_pass(cmd)
} }
} }
} }

View file

@ -5,7 +5,6 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(strict_provenance)]
mod draw_quad; mod draw_quad;
mod filter_chain; mod filter_chain;

View file

@ -186,9 +186,6 @@ impl Drop for RawVulkanBuffer {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
ManuallyDrop::drop(&mut self.buffer); ManuallyDrop::drop(&mut self.buffer);
if self.buffer.handle != vk::Buffer::null() {
self.buffer.device.destroy_buffer(self.buffer.handle, None);
}
} }
} }
} }

View file

@ -11,8 +11,8 @@ fn triangle_vk() {
unsafe { unsafe {
let filter = FilterChainVulkan::load_from_path( let filter = FilterChainVulkan::load_from_path(
// "../test/slang-shaders/crt/crt-royale.slangp", "../test/slang-shaders/crt/crt-royale.slangp",
"../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp",
&base, &base,
// "../test/slang-shaders/test/feedback.slancargogp", // "../test/slang-shaders/test/feedback.slancargogp",
// "../test/basic.slangp", // "../test/basic.slangp",
@ -23,7 +23,7 @@ fn triangle_vk() {
disable_cache: false, disable_cache: false,
}), }),
) )
.unwrap(); .unwrap();
hello_triangle::main(base, filter) hello_triangle::main(base, filter)
} }

View file

@ -0,0 +1,38 @@
[package]
name = "librashader-runtime-wgpu"
edition = "2021"
version = "0.2.0-beta.5"
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."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
librashader-common = { path = "../librashader-common", features = ["wgpu"], version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["wgsl"], default-features = false }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.5" }
wgpu = { version = "0.19.0", features = ["spirv"] }
rustc-hash = "1.1.0"
image = "0.24.7"
thiserror = "1.0.50"
rayon = "1.8.0"
bytemuck = { version = "1.14.0", features = ["derive"] }
array-concat = "0.5.2"
[dev-dependencies]
config = { version = "0.13.4", features = [] }
env_logger = "0.10.1"
raw-window-handle = "0.6.0"
winit = "0.29.10"
pollster = "0.3"
log = "0.4.20"

View file

@ -0,0 +1,52 @@
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
};
// meant to be called with 3 vertex indices: 0, 1, 2
// draws one large triangle over the clip space like this:
// (the asterisks represent the clip space bounds)
//-1,1 1,1
// ---------------------------------
// | * .
// | * .
// | * .
// | * .
// | * .
// | * .
// |***************
// | . 1,-1
// | .
// | .
// | .
// | .
// |.
@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
var result: VertexOutput;
let x = i32(vertex_index) / 2;
let y = i32(vertex_index) & 1;
let tc = vec2<f32>(
f32(x) * 2.0,
f32(y) * 2.0
);
result.position = vec4<f32>(
tc.x * 2.0 - 1.0,
1.0 - tc.y * 2.0,
0.0, 1.0
);
result.tex_coords = tc;
return result;
}
@group(0)
@binding(0)
var r_color: texture_2d<f32>;
@group(0)
@binding(1)
var r_sampler: sampler;
@fragment
fn fs_main(vertex: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(r_color, r_sampler, vertex.tex_coords);
}

View file

@ -0,0 +1,24 @@
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec3<f32>,
};
@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.color = model.color;
out.clip_position = vec4(model.position, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4(in.color, 1.0);
}

View file

@ -0,0 +1,51 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
pub struct WgpuStagedBuffer {
buffer: wgpu::Buffer,
shadow: Box<[u8]>,
}
impl WgpuStagedBuffer {
pub fn new(
device: &Arc<wgpu::Device>,
usage: wgpu::BufferUsages,
size: wgpu::BufferAddress,
label: wgpu::Label<'static>,
) -> WgpuStagedBuffer {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label,
size,
usage: usage | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
WgpuStagedBuffer {
buffer,
shadow: vec![0u8; size as usize].into_boxed_slice(),
}
}
pub fn buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
/// Write the contents of the backing buffer to the device buffer.
pub fn flush(&self, queue: &wgpu::Queue) {
queue.write_buffer(&self.buffer, 0, &self.shadow);
}
}
impl Deref for WgpuStagedBuffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.shadow.deref()
}
}
impl DerefMut for WgpuStagedBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
self.shadow.deref_mut()
}
}

View file

@ -0,0 +1,51 @@
use array_concat::concat_arrays;
use librashader_runtime::quad::QuadType;
use wgpu::util::{BufferInitDescriptor, DeviceExt};
use wgpu::{Buffer, Device, RenderPass};
#[rustfmt::skip]
const VBO_OFFSCREEN: [f32; 16] = [
// Offscreen
-1.0f32, -1.0, 0.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
1.0, -1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0,
];
#[rustfmt::skip]
const VBO_DEFAULT_FINAL: [f32; 16] = [
// Final
0.0f32, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 1.0,
1.0, 0.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0,
];
const VBO_DATA: [f32; 32] = concat_arrays!(VBO_OFFSCREEN, VBO_DEFAULT_FINAL);
pub struct DrawQuad {
buffer: Buffer,
}
impl DrawQuad {
pub fn new(device: &Device) -> DrawQuad {
let buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("librashader vbo"),
contents: bytemuck::cast_slice(&VBO_DATA),
usage: wgpu::BufferUsages::VERTEX,
});
DrawQuad { buffer }
}
pub fn draw_quad<'a, 'b: 'a>(&'b self, cmd: &mut RenderPass<'a>, vbo: QuadType) {
cmd.set_vertex_buffer(0, self.buffer.slice(0..));
let offset = match vbo {
QuadType::Offscreen => 0..4,
QuadType::Final => 4..8,
};
cmd.draw(offset, 0..1)
}
}

View file

@ -0,0 +1,24 @@
//! wgpu shader runtime errors.
use librashader_preprocess::PreprocessError;
use librashader_presets::ParsePresetError;
use librashader_reflect::error::{ShaderCompileError, ShaderReflectError};
use librashader_runtime::image::ImageError;
use thiserror::Error;
/// Cumulative error type for wgpu filter chains.
#[derive(Error, Debug)]
pub enum FilterChainError {
#[error("shader preset parse error")]
ShaderPresetError(#[from] ParsePresetError),
#[error("shader preprocess error")]
ShaderPreprocessError(#[from] PreprocessError),
#[error("shader compile error")]
ShaderCompileError(#[from] ShaderCompileError),
#[error("shader reflect error")]
ShaderReflectError(#[from] ShaderReflectError),
#[error("lut loading error")]
LutLoadError(#[from] ImageError),
}
/// Result type for wgpu filter chains.
pub type Result<T> = std::result::Result<T, FilterChainError>;

View file

@ -0,0 +1,465 @@
use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig};
use librashader_reflect::back::targets::WGSL;
use librashader_reflect::back::{CompileReflectShader, CompileShader};
use librashader_reflect::front::GlslangCompilation;
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
use librashader_reflect::reflect::semantics::ShaderSemantics;
use librashader_reflect::reflect::ReflectShader;
use librashader_runtime::binding::BindingUtil;
use librashader_runtime::image::{Image, ImageError, UVDirection};
use librashader_runtime::quad::QuadType;
use librashader_runtime::uniforms::UniformStorage;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::path::Path;
use std::sync::Arc;
use crate::buffer::WgpuStagedBuffer;
use crate::draw_quad::DrawQuad;
use librashader_common::{FilterMode, ImageFormat, Size, Viewport, WrapMode};
use librashader_reflect::back::wgsl::WgslCompileOptions;
use librashader_runtime::framebuffer::FramebufferInit;
use librashader_runtime::render_target::RenderTarget;
use librashader_runtime::scaling::ScaleFramebuffer;
use rayon::prelude::*;
use wgpu::{Device, TextureFormat};
use crate::error;
use crate::error::FilterChainError;
use crate::filter_pass::FilterPass;
use crate::framebuffer::WgpuOutputView;
use crate::graphics_pipeline::WgpuGraphicsPipeline;
use crate::luts::LutTexture;
use crate::mipmap::MipmapGen;
use crate::options::{FilterChainOptionsWgpu, FrameOptionsWgpu};
use crate::samplers::SamplerSet;
use crate::texture::{InputImage, OwnedImage};
type ShaderPassMeta =
ShaderPassArtifact<impl CompileReflectShader<WGSL, GlslangCompilation> + Send>;
fn compile_passes(
shaders: Vec<ShaderPassConfig>,
textures: &[TextureConfig],
) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), FilterChainError> {
let (passes, semantics) =
WGSL::compile_preset_passes::<GlslangCompilation, FilterChainError>(shaders, &textures)?;
Ok((passes, semantics))
}
/// A wgpu filter chain.
pub struct FilterChainWgpu {
pub(crate) common: FilterCommon,
passes: Box<[FilterPass]>,
output_framebuffers: Box<[OwnedImage]>,
feedback_framebuffers: Box<[OwnedImage]>,
history_framebuffers: VecDeque<OwnedImage>,
disable_mipmaps: bool,
mipmapper: MipmapGen,
}
pub struct FilterMutable {
pub passes_enabled: usize,
pub(crate) parameters: FxHashMap<String, f32>,
}
pub(crate) struct FilterCommon {
pub output_textures: Box<[Option<InputImage>]>,
pub feedback_textures: Box<[Option<InputImage>]>,
pub history_textures: Box<[Option<InputImage>]>,
pub luts: FxHashMap<usize, LutTexture>,
pub samplers: SamplerSet,
pub config: FilterMutable,
pub internal_frame_count: i32,
pub(crate) draw_quad: DrawQuad,
device: Arc<Device>,
pub(crate) queue: Arc<wgpu::Queue>,
}
impl FilterChainWgpu {
/// Load the shader preset at the given path into a filter chain.
pub fn load_from_path(
path: impl AsRef<Path>,
device: Arc<Device>,
queue: Arc<wgpu::Queue>,
options: Option<&FilterChainOptionsWgpu>,
) -> error::Result<FilterChainWgpu> {
// load passes from preset
let preset = ShaderPreset::try_parse(path)?;
Self::load_from_preset(preset, device, queue, options)
}
/// Load a filter chain from a pre-parsed `ShaderPreset`.
pub fn load_from_preset(
preset: ShaderPreset,
device: Arc<Device>,
queue: Arc<wgpu::Queue>,
options: Option<&FilterChainOptionsWgpu>,
) -> error::Result<FilterChainWgpu> {
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("librashader load cmd"),
});
let filter_chain = Self::load_from_preset_deferred(
preset,
Arc::clone(&device),
Arc::clone(&queue),
&mut cmd,
options,
)?;
let cmd = cmd.finish();
// Wait for device
let index = queue.submit([cmd]);
device.poll(wgpu::Maintain::WaitForSubmissionIndex(index));
Ok(filter_chain)
}
/// Load a filter chain from a pre-parsed `ShaderPreset`, deferring and GPU-side initialization
/// to the caller. This function therefore requires no external synchronization of the device queue.
///
/// ## Safety
/// The provided command buffer must be ready for recording and contain no prior commands.
/// The caller is responsible for ending the command buffer and immediately submitting it to a
/// graphics queue. The command buffer must be completely executed before calling [`frame`](Self::frame).
pub fn load_from_preset_deferred(
preset: ShaderPreset,
device: Arc<Device>,
queue: Arc<wgpu::Queue>,
cmd: &mut wgpu::CommandEncoder,
options: Option<&FilterChainOptionsWgpu>,
) -> error::Result<FilterChainWgpu> {
let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?;
// // initialize passes
let filters = Self::init_passes(Arc::clone(&device), passes, &semantics)?;
let samplers = SamplerSet::new(&device);
let mut mipmapper = MipmapGen::new(Arc::clone(&device));
let luts = FilterChainWgpu::load_luts(
&device,
&queue,
cmd,
&mut mipmapper,
&samplers,
&preset.textures,
)?;
//
let framebuffer_gen = || {
Ok::<_, error::FilterChainError>(OwnedImage::new(
Arc::clone(&device),
Size::new(1, 1),
1,
ImageFormat::R8G8B8A8Unorm,
))
};
let input_gen = || None;
let framebuffer_init = FramebufferInit::new(
filters.iter().map(|f| &f.reflection.meta),
&framebuffer_gen,
&input_gen,
);
//
// // initialize output framebuffers
let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?;
//
// initialize feedback framebuffers
let (feedback_framebuffers, feedback_textures) =
framebuffer_init.init_output_framebuffers()?;
//
// initialize history
let (history_framebuffers, history_textures) = framebuffer_init.init_history()?;
let draw_quad = DrawQuad::new(&device);
Ok(FilterChainWgpu {
common: FilterCommon {
luts,
samplers,
config: FilterMutable {
passes_enabled: preset.shader_count as usize,
parameters: preset
.parameters
.into_iter()
.map(|param| (param.name, param.value))
.collect(),
},
draw_quad,
device,
queue,
output_textures,
feedback_textures,
history_textures,
internal_frame_count: 0,
},
passes: filters,
output_framebuffers,
feedback_framebuffers,
history_framebuffers,
disable_mipmaps: options.map(|f| f.force_no_mipmaps).unwrap_or(false),
mipmapper,
})
}
fn load_luts(
device: &wgpu::Device,
queue: &wgpu::Queue,
cmd: &mut wgpu::CommandEncoder,
mipmapper: &mut MipmapGen,
sampler_set: &SamplerSet,
textures: &[TextureConfig],
) -> error::Result<FxHashMap<usize, LutTexture>> {
let mut luts = FxHashMap::default();
let images = textures
.par_iter()
.map(|texture| Image::load(&texture.path, UVDirection::TopLeft))
.collect::<Result<Vec<Image>, ImageError>>()?;
for (index, (texture, image)) in textures.iter().zip(images).enumerate() {
let texture =
LutTexture::new(device, queue, cmd, image, texture, mipmapper, sampler_set);
luts.insert(index, texture);
}
Ok(luts)
}
fn push_history(&mut self, input: &wgpu::Texture, cmd: &mut wgpu::CommandEncoder) {
if let Some(mut back) = self.history_framebuffers.pop_back() {
if back.image.size() != input.size() || input.format() != back.image.format() {
// old back will get dropped.. do we need to defer?
let _old_back = std::mem::replace(
&mut back,
OwnedImage::new(
Arc::clone(&self.common.device),
input.size().into(),
1,
input.format().into(),
),
);
}
back.copy_from(cmd, input);
self.history_framebuffers.push_front(back)
}
}
fn init_passes(
device: Arc<Device>,
passes: Vec<ShaderPassMeta>,
semantics: &ShaderSemantics,
) -> error::Result<Box<[FilterPass]>> {
let filters: Vec<error::Result<FilterPass>> = passes
.into_par_iter()
.enumerate()
.map(|(index, (config, source, mut reflect))| {
let reflection = reflect.reflect(index, semantics)?;
let wgsl = reflect.compile(WgslCompileOptions {
write_pcb_as_ubo: true,
sampler_bind_group: 1,
})?;
let ubo_size = reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize);
let push_size = reflection
.push_constant
.as_ref()
.map_or(0, |push| push.size as wgpu::BufferAddress);
let uniform_storage = UniformStorage::new_with_storage(
WgpuStagedBuffer::new(
&device,
wgpu::BufferUsages::UNIFORM,
ubo_size as wgpu::BufferAddress,
Some("ubo"),
),
WgpuStagedBuffer::new(
&device,
wgpu::BufferUsages::UNIFORM,
push_size as wgpu::BufferAddress,
Some("push"),
),
);
let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset());
let render_pass_format: Option<TextureFormat> =
if let Some(format) = config.get_format_override() {
format.into()
} else {
source.format.into()
};
let graphics_pipeline = WgpuGraphicsPipeline::new(
Arc::clone(&device),
&wgsl,
&reflection,
render_pass_format.unwrap_or(TextureFormat::Rgba8Unorm),
);
Ok(FilterPass {
device: Arc::clone(&device),
reflection,
uniform_storage,
uniform_bindings,
source,
config,
graphics_pipeline,
})
})
.collect();
//
let filters: error::Result<Vec<FilterPass>> = filters.into_iter().collect();
let filters = filters?;
Ok(filters.into_boxed_slice())
}
/// Records shader rendering commands to the provided command encoder.
pub fn frame<'a>(
&mut self,
input: Arc<wgpu::Texture>,
viewport: &Viewport<WgpuOutputView<'a>>,
cmd: &mut wgpu::CommandEncoder,
frame_count: usize,
options: Option<&FrameOptionsWgpu>,
) -> error::Result<()> {
let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
let passes = &mut self.passes[0..max];
if let Some(options) = &options {
if options.clear_history {
for history in &mut self.history_framebuffers {
history.clear(cmd);
}
}
}
if passes.is_empty() {
return Ok(());
}
let original_image_view = input.create_view(&wgpu::TextureViewDescriptor::default());
let filter = passes[0].config.filter;
let wrap_mode = passes[0].config.wrap_mode;
// update history
for (texture, image) in self
.common
.history_textures
.iter_mut()
.zip(self.history_framebuffers.iter())
{
*texture = Some(image.as_input(filter, wrap_mode));
}
let original = InputImage {
image: Arc::clone(&input),
view: Arc::new(original_image_view),
wrap_mode,
filter_mode: filter,
mip_filter: filter,
};
let mut source = original.clone();
// swap output and feedback **before** recording command buffers
std::mem::swap(
&mut self.output_framebuffers,
&mut self.feedback_framebuffers,
);
// rescale render buffers to ensure all bindings are valid.
OwnedImage::scale_framebuffers_with_context(
source.image.size().into(),
viewport.output.size,
&mut self.output_framebuffers,
&mut self.feedback_framebuffers,
passes,
&(),
Some(&mut |index: usize,
pass: &FilterPass,
output: &OwnedImage,
feedback: &OwnedImage| {
// refresh inputs
self.common.feedback_textures[index] =
Some(feedback.as_input(pass.config.filter, pass.config.wrap_mode));
self.common.output_textures[index] =
Some(output.as_input(pass.config.filter, pass.config.wrap_mode));
Ok(())
}),
)?;
let passes_len = passes.len();
let (pass, last) = passes.split_at_mut(passes_len - 1);
let frame_direction = options.map_or(1, |f| f.frame_direction);
for (index, pass) in pass.iter_mut().enumerate() {
let target = &self.output_framebuffers[index];
source.filter_mode = pass.config.filter;
source.wrap_mode = pass.config.wrap_mode;
source.mip_filter = pass.config.filter;
let output_image = WgpuOutputView::from(target);
let out = RenderTarget::identity(&output_image);
pass.draw(
cmd,
index,
&self.common,
pass.config.get_frame_count(frame_count),
frame_direction,
viewport,
&original,
&source,
&out,
QuadType::Offscreen,
)?;
if target.max_miplevels > 1 && !self.disable_mipmaps {
let sampler = self.common.samplers.get(
WrapMode::ClampToEdge,
FilterMode::Linear,
FilterMode::Nearest,
);
target.generate_mipmaps(cmd, &mut self.mipmapper, &sampler);
}
source = self.common.output_textures[index].clone().unwrap();
}
// try to hint the optimizer
assert_eq!(last.len(), 1);
if let Some(pass) = last.iter_mut().next() {
if pass.graphics_pipeline.format != viewport.output.format {
// need to recompile
pass.graphics_pipeline.recompile(viewport.output.format);
}
source.filter_mode = pass.config.filter;
source.wrap_mode = pass.config.wrap_mode;
source.mip_filter = pass.config.filter;
let output_image = &viewport.output;
let out = RenderTarget::viewport_with_output(output_image, viewport);
pass.draw(
cmd,
passes_len - 1,
&self.common,
pass.config.get_frame_count(frame_count),
frame_direction,
viewport,
&original,
&source,
&out,
QuadType::Final,
)?;
}
self.push_history(&input, cmd);
self.common.internal_frame_count = self.common.internal_frame_count.wrapping_add(1);
Ok(())
}
}

View file

@ -0,0 +1,247 @@
use crate::buffer::WgpuStagedBuffer;
use crate::error;
use crate::filter_chain::FilterCommon;
use crate::framebuffer::WgpuOutputView;
use crate::graphics_pipeline::WgpuGraphicsPipeline;
use crate::samplers::SamplerSet;
use crate::texture::InputImage;
use librashader_common::{ImageFormat, Size, Viewport};
use librashader_preprocess::ShaderSource;
use librashader_presets::ShaderPassConfig;
use librashader_reflect::reflect::semantics::{
BindingStage, MemberOffset, TextureBinding, UniformBinding,
};
use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::binding::{BindSemantics, TextureInput};
use librashader_runtime::filter_pass::FilterPassMeta;
use librashader_runtime::quad::QuadType;
use librashader_runtime::render_target::RenderTarget;
use librashader_runtime::uniforms::{NoUniformBinder, UniformStorage, UniformStorageAccess};
use rustc_hash::FxHashMap;
use std::sync::Arc;
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource, BufferBinding, ShaderStages};
pub struct FilterPass {
pub device: Arc<wgpu::Device>,
pub reflection: ShaderReflection,
pub(crate) uniform_storage:
UniformStorage<NoUniformBinder, Option<()>, WgpuStagedBuffer, WgpuStagedBuffer>,
pub uniform_bindings: FxHashMap<UniformBinding, MemberOffset>,
pub source: ShaderSource,
pub config: ShaderPassConfig,
pub graphics_pipeline: WgpuGraphicsPipeline,
}
impl TextureInput for InputImage {
fn size(&self) -> Size<u32> {
self.image.size().into()
}
}
pub struct WgpuArcBinding<T> {
binding: u32,
resource: Arc<T>,
}
impl BindSemantics<NoUniformBinder, Option<()>, WgpuStagedBuffer, WgpuStagedBuffer> for FilterPass {
type InputTexture = InputImage;
type SamplerSet = SamplerSet;
type DescriptorSet<'a> = (
&'a mut FxHashMap<u32, WgpuArcBinding<wgpu::TextureView>>,
&'a mut FxHashMap<u32, WgpuArcBinding<wgpu::Sampler>>,
);
type DeviceContext = Arc<wgpu::Device>;
type UniformOffset = MemberOffset;
#[inline(always)]
fn bind_texture<'a>(
descriptors: &mut Self::DescriptorSet<'a>,
samplers: &Self::SamplerSet,
binding: &TextureBinding,
texture: &Self::InputTexture,
_device: &Self::DeviceContext,
) {
let sampler = samplers.get(texture.wrap_mode, texture.filter_mode, texture.mip_filter);
let (texture_binding, sampler_binding) = descriptors;
texture_binding.insert(
binding.binding,
WgpuArcBinding {
binding: binding.binding,
resource: Arc::clone(&texture.view),
},
);
sampler_binding.insert(
binding.binding,
WgpuArcBinding {
binding: binding.binding,
resource: sampler,
},
);
}
}
impl FilterPass {
pub(crate) fn draw(
&mut self,
cmd: &mut wgpu::CommandEncoder,
pass_index: usize,
parent: &FilterCommon,
frame_count: u32,
frame_direction: i32,
viewport: &Viewport<WgpuOutputView>,
original: &InputImage,
source: &InputImage,
output: &RenderTarget<WgpuOutputView>,
vbo_type: QuadType,
) -> error::Result<()> {
let mut main_heap = FxHashMap::default();
let mut sampler_heap = FxHashMap::default();
self.build_semantics(
pass_index,
parent,
output.mvp,
frame_count,
frame_direction,
output.output.size,
viewport.output.size,
original,
source,
&mut main_heap,
&mut sampler_heap,
);
let mut main_heap_array = Vec::with_capacity(main_heap.len() + 1);
let mut sampler_heap_array = Vec::with_capacity(sampler_heap.len() + 1);
for binding in main_heap.values() {
main_heap_array.push(BindGroupEntry {
binding: binding.binding,
resource: BindingResource::TextureView(&binding.resource),
})
}
for binding in sampler_heap.values() {
sampler_heap_array.push(BindGroupEntry {
binding: binding.binding,
resource: BindingResource::Sampler(&binding.resource),
})
}
if let Some(ubo) = &self.reflection.ubo {
main_heap_array.push(BindGroupEntry {
binding: ubo.binding,
resource: BindingResource::Buffer(BufferBinding {
buffer: self.uniform_storage.inner_ubo().buffer(),
offset: 0,
size: None,
}),
});
}
let mut has_pcb_buffer = false;
if let Some(pcb) = &self.reflection.push_constant {
if let Some(binding) = pcb.binding {
main_heap_array.push(BindGroupEntry {
binding,
resource: BindingResource::Buffer(BufferBinding {
buffer: self.uniform_storage.inner_push().buffer(),
offset: 0,
size: None,
}),
});
has_pcb_buffer = true;
}
}
let main_bind_group = self.device.create_bind_group(&BindGroupDescriptor {
label: Some("main bind group"),
layout: &self.graphics_pipeline.layout.main_bind_group_layout,
entries: &main_heap_array,
});
let sampler_bind_group = self.device.create_bind_group(&BindGroupDescriptor {
label: Some("sampler bind group"),
layout: &self.graphics_pipeline.layout.sampler_bind_group_layout,
entries: &sampler_heap_array,
});
let mut render_pass = self.graphics_pipeline.begin_rendering(output, cmd);
render_pass.set_bind_group(0, &main_bind_group, &[]);
render_pass.set_bind_group(1, &sampler_bind_group, &[]);
if let Some(push) = &self.reflection.push_constant
&& !has_pcb_buffer
{
let mut stage_mask = ShaderStages::empty();
if push.stage_mask.contains(BindingStage::FRAGMENT) {
stage_mask |= ShaderStages::FRAGMENT;
}
if push.stage_mask.contains(BindingStage::VERTEX) {
stage_mask |= ShaderStages::VERTEX;
}
render_pass.set_push_constants(stage_mask, 0, self.uniform_storage.push_slice())
}
parent.draw_quad.draw_quad(&mut render_pass, vbo_type);
Ok(())
}
fn build_semantics<'a>(
&mut self,
pass_index: usize,
parent: &FilterCommon,
mvp: &[f32; 16],
frame_count: u32,
frame_direction: i32,
fb_size: Size<u32>,
viewport_size: Size<u32>,
original: &InputImage,
source: &InputImage,
main_heap: &'a mut FxHashMap<u32, WgpuArcBinding<wgpu::TextureView>>,
sampler_heap: &'a mut FxHashMap<u32, WgpuArcBinding<wgpu::Sampler>>,
) {
Self::bind_semantics(
&self.device,
&parent.samplers,
&mut self.uniform_storage,
&mut (main_heap, sampler_heap),
mvp,
frame_count,
frame_direction,
fb_size,
viewport_size,
original,
source,
&self.uniform_bindings,
&self.reflection.meta.texture_meta,
parent.output_textures[0..pass_index]
.iter()
.map(|o| o.as_ref()),
parent.feedback_textures.iter().map(|o| o.as_ref()),
parent.history_textures.iter().map(|o| o.as_ref()),
parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
&self.source.parameters,
&parent.config.parameters,
);
// flush to buffers
self.uniform_storage.inner_ubo().flush(&parent.queue);
self.uniform_storage.inner_push().flush(&parent.queue);
}
}
impl FilterPassMeta for FilterPass {
fn framebuffer_format(&self) -> ImageFormat {
self.source.format
}
fn config(&self) -> &ShaderPassConfig {
&self.config
}
}

View file

@ -0,0 +1,47 @@
use crate::handle::Handle;
use crate::texture::OwnedImage;
use librashader_common::Size;
use wgpu::TextureViewDescriptor;
/// A wgpu `TextureView` with size and texture information to output.
pub struct WgpuOutputView<'a> {
pub(crate) size: Size<u32>,
pub(crate) view: Handle<'a, wgpu::TextureView>,
pub(crate) format: wgpu::TextureFormat,
}
impl<'a> WgpuOutputView<'a> {
/// Create an output view from an existing texture view, size, and format.
pub fn new_from_raw(
view: &'a wgpu::TextureView,
size: Size<u32>,
format: wgpu::TextureFormat,
) -> Self {
Self {
size,
view: Handle::Borrowed(&view),
format,
}
}
}
#[doc(hidden)]
impl<'a> From<&'a OwnedImage> for WgpuOutputView<'a> {
fn from(image: &'a OwnedImage) -> Self {
Self {
size: image.size,
view: Handle::Borrowed(&image.view),
format: image.image.format(),
}
}
}
impl From<&wgpu::Texture> for WgpuOutputView<'static> {
fn from(image: &wgpu::Texture) -> Self {
Self {
size: image.size().into(),
view: Handle::Owned(image.create_view(&TextureViewDescriptor::default())),
format: image.format(),
}
}
}

View file

@ -0,0 +1,265 @@
use crate::framebuffer::WgpuOutputView;
use crate::util;
use librashader_reflect::back::wgsl::NagaWgslContext;
use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::render_target::RenderTarget;
use std::borrow::Cow;
use std::sync::Arc;
use wgpu::{
BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType,
BufferBindingType, BufferSize, CommandEncoder, Operations, PipelineLayout, PushConstantRange,
RenderPass, RenderPassColorAttachment, RenderPassDescriptor, SamplerBindingType, ShaderModule,
ShaderSource, ShaderStages, TextureFormat, TextureSampleType, TextureViewDimension,
VertexBufferLayout,
};
pub struct WgpuGraphicsPipeline {
pub layout: PipelineLayoutObjects,
render_pipeline: wgpu::RenderPipeline,
pub format: wgpu::TextureFormat,
}
pub struct PipelineLayoutObjects {
layout: PipelineLayout,
pub main_bind_group_layout: BindGroupLayout,
pub sampler_bind_group_layout: BindGroupLayout,
fragment_entry_name: String,
vertex_entry_name: String,
vertex: ShaderModule,
fragment: ShaderModule,
device: Arc<wgpu::Device>,
}
impl PipelineLayoutObjects {
pub fn new(
reflection: &ShaderReflection,
shader_assembly: &ShaderCompilerOutput<String, NagaWgslContext>,
device: Arc<wgpu::Device>,
) -> Self {
let vertex = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("vertex"),
source: ShaderSource::Wgsl(Cow::from(&shader_assembly.vertex)),
});
let fragment = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("fragment"),
source: ShaderSource::Wgsl(Cow::from(&shader_assembly.fragment)),
});
let mut main_bindings = Vec::new();
let mut sampler_bindings = Vec::new();
let mut push_constant_range = Vec::new();
if let Some(push_meta) = reflection.push_constant.as_ref()
&& !push_meta.stage_mask.is_empty()
{
let push_mask = util::binding_stage_to_wgpu_stage(push_meta.stage_mask);
if let Some(binding) = push_meta.binding {
main_bindings.push(BindGroupLayoutEntry {
binding,
visibility: push_mask,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(push_meta.size as u64),
},
count: None,
});
} else {
push_constant_range.push(PushConstantRange {
stages: push_mask,
range: 0..push_meta.size,
})
}
}
if let Some(ubo_meta) = reflection.ubo.as_ref()
&& !ubo_meta.stage_mask.is_empty()
{
let ubo_mask = util::binding_stage_to_wgpu_stage(ubo_meta.stage_mask);
main_bindings.push(BindGroupLayoutEntry {
binding: ubo_meta.binding,
visibility: ubo_mask,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(ubo_meta.size as u64),
},
count: None,
});
}
for texture in reflection.meta.texture_meta.values() {
main_bindings.push(BindGroupLayoutEntry {
binding: texture.binding,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
});
sampler_bindings.push(BindGroupLayoutEntry {
binding: texture.binding,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
})
}
let main_bind_group = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("bind group 0"),
entries: &main_bindings,
});
let sampler_bind_group = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("bind group 1"),
entries: &sampler_bindings,
});
let bind_group_layout_refs = [&main_bind_group, &sampler_bind_group];
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("shader pipeline layout"),
bind_group_layouts: &bind_group_layout_refs,
push_constant_ranges: &push_constant_range.as_ref(),
});
Self {
layout,
main_bind_group_layout: main_bind_group,
sampler_bind_group_layout: sampler_bind_group,
fragment_entry_name: shader_assembly.context.fragment.entry_points[0]
.name
.clone(),
vertex_entry_name: shader_assembly.context.vertex.entry_points[0].name.clone(),
vertex,
fragment,
device,
}
}
pub fn create_pipeline(&self, framebuffer_format: TextureFormat) -> wgpu::RenderPipeline {
self.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&self.layout),
vertex: wgpu::VertexState {
module: &self.vertex,
entry_point: &self.vertex_entry_name,
buffers: &[VertexBufferLayout {
array_stride: 4 * std::mem::size_of::<f32>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: (2 * std::mem::size_of::<f32>()) as wgpu::BufferAddress,
shader_location: 1,
},
],
}],
},
fragment: Some(wgpu::FragmentState {
module: &self.fragment,
entry_point: &self.fragment_entry_name,
targets: &[Some(wgpu::ColorTargetState {
format: framebuffer_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
})
}
}
impl WgpuGraphicsPipeline {
pub fn new(
device: Arc<wgpu::Device>,
shader_assembly: &ShaderCompilerOutput<String, NagaWgslContext>,
reflection: &ShaderReflection,
render_pass_format: TextureFormat,
) -> Self {
let layout = PipelineLayoutObjects::new(reflection, shader_assembly, device);
let render_pipeline = layout.create_pipeline(render_pass_format);
Self {
layout,
render_pipeline,
format: render_pass_format,
}
}
pub fn recompile(&mut self, format: TextureFormat) {
let render_pipeline = self.layout.create_pipeline(format);
self.render_pipeline = render_pipeline;
}
pub(crate) fn begin_rendering<'pass>(
&'pass self,
output: &RenderTarget<'pass, WgpuOutputView>,
cmd: &'pass mut CommandEncoder,
) -> RenderPass<'pass> {
let mut render_pass = cmd.begin_render_pass(&RenderPassDescriptor {
label: Some("librashader"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &output.output.view,
resolve_target: None,
ops: Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_scissor_rect(
output.x as u32,
output.y as u32,
output.output.size.width,
output.output.size.height,
);
render_pass.set_viewport(
output.x,
output.y,
output.output.size.width as f32,
output.output.size.height as f32,
1.0,
1.0,
);
render_pass.set_pipeline(&self.render_pipeline);
render_pass
}
}

View file

@ -0,0 +1,17 @@
use std::ops::Deref;
pub enum Handle<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<T> Deref for Handle<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Handle::Borrowed(r) => &r,
Handle::Owned(r) => &r,
}
}
}

View file

@ -0,0 +1,28 @@
//! librashader WGPU runtime
//!
//! This crate should not be used directly.
//! See [`librashader::runtime::wgpu`](https://docs.rs/librashader/latest/librashader/runtime/wgpu/index.html) instead.
#![deny(unsafe_op_in_unsafe_fn)]
#![feature(type_alias_impl_trait)]
#![feature(let_chains)]
#![feature(strict_provenance)]
mod buffer;
mod draw_quad;
mod filter_chain;
mod filter_pass;
mod framebuffer;
mod graphics_pipeline;
mod handle;
mod luts;
mod mipmap;
mod samplers;
mod texture;
mod util;
pub use filter_chain::FilterChainWgpu;
pub use framebuffer::WgpuOutputView;
pub mod error;
pub mod options;

View file

@ -0,0 +1,87 @@
use crate::mipmap::MipmapGen;
use crate::samplers::SamplerSet;
use crate::texture::InputImage;
use librashader_common::{Size, WrapMode};
use librashader_presets::TextureConfig;
use librashader_runtime::image::Image;
use librashader_runtime::scaling::MipmapSize;
use std::sync::Arc;
use wgpu::TextureDescriptor;
pub(crate) struct LutTexture(InputImage);
impl AsRef<InputImage> for LutTexture {
fn as_ref(&self) -> &InputImage {
&self.0
}
}
impl LutTexture {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
cmd: &mut wgpu::CommandEncoder,
image: Image,
config: &TextureConfig,
mipmapper: &mut MipmapGen,
sampler_set: &SamplerSet,
) -> LutTexture {
let texture = device.create_texture(&TextureDescriptor {
label: Some(&config.name),
size: image.size.into(),
mip_level_count: if config.mipmap {
image.size.calculate_miplevels()
} else {
1
},
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
// need render attachment for mipmaps...
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
});
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(),
);
if config.mipmap {
mipmapper.generate_mipmaps(
cmd,
&texture,
&*sampler_set.get(
WrapMode::ClampToEdge,
config.filter_mode,
config.filter_mode,
),
Size::<u32>::from(texture.size()).calculate_miplevels(),
);
}
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let image = InputImage {
image: Arc::new(texture),
view: Arc::new(view),
wrap_mode: config.wrap_mode,
filter_mode: config.filter_mode,
mip_filter: config.filter_mode,
};
Self(image)
}
}

View file

@ -0,0 +1,119 @@
use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::sync::Arc;
pub struct MipmapGen {
device: Arc<wgpu::Device>,
shader: wgpu::ShaderModule,
pipeline_cache: FxHashMap<wgpu::TextureFormat, wgpu::RenderPipeline>,
}
impl MipmapGen {
fn create_pipeline(
device: &wgpu::Device,
shader: &wgpu::ShaderModule,
format: wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("blit"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(format.into())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
pipeline
}
pub fn new(device: Arc<wgpu::Device>) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shader/blit.wgsl"))),
});
Self {
device,
shader,
pipeline_cache: Default::default(),
}
}
pub fn generate_mipmaps(
&mut self,
cmd: &mut wgpu::CommandEncoder,
texture: &wgpu::Texture,
sampler: &wgpu::Sampler,
miplevels: u32,
) {
let format = texture.format();
let pipeline = &*self
.pipeline_cache
.entry(format)
.or_insert_with(|| Self::create_pipeline(&self.device, &self.shader, format));
let views = (0..miplevels)
.map(|mip| {
texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("mip"),
format: None,
dimension: None,
aspect: wgpu::TextureAspect::All,
base_mip_level: mip,
mip_level_count: Some(1),
base_array_layer: 0,
array_layer_count: None,
})
})
.collect::<Vec<_>>();
for target_mip in 1..miplevels as usize {
let bind_group_layout = pipeline.get_bind_group_layout(0);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: None,
});
let mut pass = cmd.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &views[target_mip],
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.draw(0..3, 0..1);
}
}
}

View file

@ -0,0 +1,20 @@
//! wgpu shader runtime options.
/// Options for each wgpu shader frame.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct FrameOptionsWgpu {
/// Whether or not to clear the history buffers.
pub clear_history: bool,
/// The direction of rendering.
/// -1 indicates that the frames are played in reverse order.
pub frame_direction: i32,
}
/// Options for filter chain creation.
#[repr(C)]
#[derive(Default, Debug, Clone)]
pub struct FilterChainOptionsWgpu {
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
}

View file

@ -0,0 +1,63 @@
use librashader_common::{FilterMode, WrapMode};
use rustc_hash::FxHashMap;
use std::sync::Arc;
use wgpu::{Sampler, SamplerBorderColor, SamplerDescriptor};
pub struct SamplerSet {
// todo: may need to deal with differences in mip filter.
samplers: FxHashMap<(WrapMode, FilterMode, FilterMode), Arc<Sampler>>,
}
impl SamplerSet {
#[inline(always)]
pub fn get(&self, wrap: WrapMode, filter: FilterMode, mipmap: FilterMode) -> Arc<Sampler> {
// eprintln!("{wrap}, {filter}, {mip}");
// SAFETY: the sampler set is complete for the matrix
// wrap x filter x mipmap
unsafe {
Arc::clone(
&self
.samplers
.get(&(wrap, filter, mipmap))
.unwrap_unchecked(),
)
}
}
pub fn new(device: &wgpu::Device) -> SamplerSet {
let mut samplers = FxHashMap::default();
let wrap_modes = &[
WrapMode::ClampToBorder,
WrapMode::ClampToEdge,
WrapMode::Repeat,
WrapMode::MirroredRepeat,
];
for wrap_mode in wrap_modes {
for filter_mode in &[FilterMode::Linear, FilterMode::Nearest] {
for mipmap_filter in &[FilterMode::Linear, FilterMode::Nearest] {
samplers.insert(
(*wrap_mode, *filter_mode, *mipmap_filter),
Arc::new(device.create_sampler(&SamplerDescriptor {
label: None,
address_mode_u: (*wrap_mode).into(),
address_mode_v: (*wrap_mode).into(),
address_mode_w: (*wrap_mode).into(),
mag_filter: (*filter_mode).into(),
min_filter: (*filter_mode).into(),
mipmap_filter: (*mipmap_filter).into(),
lod_min_clamp: 0.0,
lod_max_clamp: 1000.0,
compare: None,
anisotropy_clamp: 1,
border_color: Some(SamplerBorderColor::TransparentBlack),
})),
);
}
}
}
// assert all samplers were created.
assert_eq!(samplers.len(), wrap_modes.len() * 2 * 2);
SamplerSet { samplers }
}
}

View file

@ -0,0 +1,152 @@
use crate::error::FilterChainError;
use crate::mipmap::MipmapGen;
use librashader_common::{FilterMode, ImageFormat, Size, WrapMode};
use librashader_presets::Scale2D;
use librashader_runtime::scaling::{MipmapSize, ScaleFramebuffer, ViewportSize};
use std::sync::Arc;
pub struct OwnedImage {
device: Arc<wgpu::Device>,
pub image: Arc<wgpu::Texture>,
pub view: Arc<wgpu::TextureView>,
pub max_miplevels: u32,
pub levels: u32,
pub size: Size<u32>,
}
#[derive(Clone)]
pub struct InputImage {
/// A handle to the `VkImage`.
pub image: Arc<wgpu::Texture>,
pub view: Arc<wgpu::TextureView>,
pub wrap_mode: WrapMode,
pub filter_mode: FilterMode,
pub mip_filter: FilterMode,
}
impl AsRef<InputImage> for InputImage {
fn as_ref(&self) -> &InputImage {
&self
}
}
impl OwnedImage {
pub fn new(
device: Arc<wgpu::Device>,
size: Size<u32>,
max_miplevels: u32,
format: ImageFormat,
) -> Self {
let format: Option<wgpu::TextureFormat> = format.into();
let format = format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: size.into(),
mip_level_count: std::cmp::min(max_miplevels, size.calculate_miplevels()),
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[format.into()],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label: None,
format: Some(format),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
});
Self {
device,
image: Arc::new(texture),
view: Arc::new(view),
max_miplevels,
levels: std::cmp::min(max_miplevels, size.calculate_miplevels()),
size,
}
}
pub fn scale(
&mut self,
scaling: Scale2D,
format: ImageFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
mipmap: bool,
) -> Size<u32> {
let size = source_size.scale_viewport(scaling, *viewport_size);
let format: Option<wgpu::TextureFormat> = format.into();
let format = format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
if self.size != size
|| (mipmap && self.max_miplevels == 1)
|| (!mipmap && self.max_miplevels != 1)
|| format != self.image.format()
{
let mut new = OwnedImage::new(
Arc::clone(&self.device),
size,
self.max_miplevels,
format.into(),
);
std::mem::swap(self, &mut new);
}
size
}
pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> InputImage {
InputImage {
image: Arc::clone(&self.image),
view: Arc::clone(&self.view),
wrap_mode,
filter_mode: filter,
mip_filter: filter,
}
}
pub fn copy_from(&self, cmd: &mut wgpu::CommandEncoder, source: &wgpu::Texture) {
cmd.copy_texture_to_texture(
source.as_image_copy(),
self.image.as_image_copy(),
source.size(),
)
}
pub fn clear(&self, cmd: &mut wgpu::CommandEncoder) {
cmd.clear_texture(&self.image, &wgpu::ImageSubresourceRange::default());
}
pub fn generate_mipmaps(
&self,
cmd: &mut wgpu::CommandEncoder,
mipmapper: &mut MipmapGen,
sampler: &wgpu::Sampler,
) {
mipmapper.generate_mipmaps(cmd, &self.image, sampler, self.max_miplevels);
}
}
impl ScaleFramebuffer for OwnedImage {
type Error = FilterChainError;
type Context = ();
fn scale(
&mut self,
scaling: Scale2D,
format: ImageFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
should_mipmap: bool,
_context: &Self::Context,
) -> Result<Size<u32>, Self::Error> {
Ok(self.scale(scaling, format, viewport_size, source_size, should_mipmap))
}
}

View file

@ -0,0 +1,15 @@
use librashader_reflect::reflect::semantics::BindingStage;
use wgpu::ShaderStages;
pub fn binding_stage_to_wgpu_stage(stage_mask: BindingStage) -> ShaderStages {
let mut mask = ShaderStages::empty();
if stage_mask.contains(BindingStage::VERTEX) {
mask |= ShaderStages::VERTEX;
}
if stage_mask.contains(BindingStage::FRAGMENT) {
mask |= ShaderStages::FRAGMENT;
}
mask
}

View file

@ -0,0 +1,357 @@
use std::sync::Arc;
use wgpu::Maintain;
use winit::{
event::*,
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};
use librashader_common::Viewport;
use librashader_presets::ShaderPreset;
use librashader_runtime_wgpu::FilterChainWgpu;
use wgpu::util::DeviceExt;
use winit::event_loop::EventLoopBuilder;
use winit::keyboard::{Key, KeyCode, PhysicalKey};
use winit::platform::windows::EventLoopBuilderExtWindows;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
color: [f32; 3],
}
impl Vertex {
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
],
}
}
}
const VERTICES: &[Vertex] = &[
Vertex {
// top
position: [0.0, 0.5, 0.0],
color: [1.0, 0.0, 0.0],
},
Vertex {
// bottom left
position: [-0.5, -0.5, 0.0],
color: [0.0, 1.0, 0.0],
},
Vertex {
// bottom right
position: [0.5, -0.5, 0.0],
color: [0.0, 0.0, 1.0],
},
];
struct State<'a> {
surface: wgpu::Surface<'a>,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
config: wgpu::SurfaceConfiguration,
size: winit::dpi::PhysicalSize<u32>,
clear_color: wgpu::Color,
render_pipeline: wgpu::RenderPipeline,
vertex_buffer: wgpu::Buffer,
num_vertices: u32,
chain: FilterChainWgpu,
frame_count: usize,
}
impl<'a> State<'a> {
async fn new(window: &'a Window) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window).unwrap();
// NOTE: could be none, see: https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/#state-new
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
required_limits: wgpu::Limits::default(),
label: None,
},
None,
)
.await
.unwrap();
let swapchain_capabilities = surface.get_capabilities(&adapter);
let swapchain_format = swapchain_capabilities.formats[0];
let mut config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
format: swapchain_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
desired_maximum_frame_latency: 2,
alpha_mode: swapchain_capabilities.alpha_modes[0],
view_formats: vec![],
};
let device = Arc::new(device);
let queue = Arc::new(queue);
let preset =
ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap();
let chain = FilterChainWgpu::load_from_preset(
preset,
Arc::clone(&device),
Arc::clone(&queue),
None,
)
.unwrap();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shader/triangle.wgsl").into()),
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("triangle vertices"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let clear_color = wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
};
let num_vertices = VERTICES.len() as u32;
Self {
surface,
device,
queue,
config,
size,
clear_color,
render_pipeline,
vertex_buffer,
num_vertices,
chain,
frame_count: 0,
}
}
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
}
fn input(&mut self, event: &WindowEvent) -> bool {
false
}
fn update(&mut self) {}
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;
let render_output = Arc::new(self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("rendertexture"),
size: output.texture.size(),
mip_level_count: output.texture.mip_level_count(),
sample_count: output.texture.sample_count(),
dimension: output.texture.dimension(),
format: output.texture.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[output.texture.format()],
}));
let filter_output = Arc::new(self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("filteroutput"),
size: output.texture.size(),
mip_level_count: output.texture.mip_level_count(),
sample_count: output.texture.sample_count(),
dimension: output.texture.dimension(),
format: output.texture.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[output.texture.format()],
}));
let view = render_output.create_view(&wgpu::TextureViewDescriptor::default());
let filter_view = filter_output.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
render_pass.draw(0..self.num_vertices, 0..1);
}
self.chain
.frame(
Arc::clone(&render_output),
&Viewport {
x: 0.0,
y: 0.0,
mvp: None,
output: librashader_runtime_wgpu::WgpuOutputView::new_from_raw(
&filter_view,
filter_output.size().into(),
filter_output.format(),
),
},
&mut encoder,
self.frame_count,
None,
)
.expect("failed to draw frame");
encoder.copy_texture_to_texture(
filter_output.as_image_copy(),
output.texture.as_image_copy(),
output.texture.size(),
);
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
self.frame_count += 1;
Ok(())
}
}
pub fn run() {
env_logger::init();
let event_loop = EventLoopBuilder::new()
.with_any_thread(true)
.with_dpi_aware(true)
.build()
.unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap();
pollster::block_on(async {
let mut state = State::new(&window).await;
event_loop
.run(|event, target| {
match event {
Event::WindowEvent {
window_id: _,
event,
} => match event {
WindowEvent::Resized(new_size) => {
state.resize(new_size);
// On macos the window needs to be redrawn manually after resizing
window.request_redraw();
}
WindowEvent::RedrawRequested => {
state.update();
match state.render() {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
Err(wgpu::SurfaceError::OutOfMemory) => target.exit(),
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
WindowEvent::CloseRequested => target.exit(),
_ => {}
},
Event::AboutToWait => window.request_redraw(),
_ => {}
}
})
.unwrap();
});
}

View file

@ -0,0 +1,6 @@
mod hello_triangle;
#[test]
fn triangle_wgpu() {
hello_triangle::run()
}

View file

@ -3,7 +3,7 @@ name = "librashader-runtime"
edition = "2021" edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -12,10 +12,10 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all." description = "RetroArch shaders for all."
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2" } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5" }
bytemuck = "1.12.3" bytemuck = "1.12.3"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
num-traits = "0.2.15" num-traits = "0.2.15"

View file

@ -0,0 +1,87 @@
/// An iterator over a slice in (non-overlapping) mutable chunks (`N` elements
/// at a time), starting at the beginning of the slice.
///
/// When the slice len is not evenly divided by the chunk size, the last
/// up to `N-1` elements will be omitted but can be retrieved from
/// the [`into_remainder`] function from the iterator.
///
/// This struct is created by the [`array_chunks_mut`] method on [slices].
///
///
/// [`array_chunks_mut`]: slice::array_chunks_mut
/// [`into_remainder`]: ../../std/slice/struct.ArrayChunksMut.html#method.into_remainder
/// [slices]: slice
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ArrayChunksMut<'a, T: 'a, const N: usize> {
iter: core::slice::IterMut<'a, [T; N]>,
}
impl<'a, T, const N: usize> ArrayChunksMut<'a, T, N> {
#[inline]
pub(super) fn new(slice: &'a mut [T]) -> Self {
let (array_slice, _rem) = as_chunks_mut(slice);
Self {
iter: array_slice.iter_mut(),
}
}
}
impl<'a, T, const N: usize> Iterator for ArrayChunksMut<'a, T, N> {
type Item = &'a mut [T; N];
#[inline]
fn next(&mut self) -> Option<&'a mut [T; N]> {
self.iter.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
#[inline]
fn count(self) -> usize {
self.iter.count()
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth(n)
}
#[inline]
fn last(self) -> Option<Self::Item> {
self.iter.last()
}
}
/// Splits the slice into a slice of `N`-element arrays,
/// starting at the beginning of the slice,
/// and a remainder slice with length strictly less than `N`.
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
#[inline]
#[must_use]
fn as_chunks_mut<T, const N: usize>(slice: &mut [T]) -> (&mut [[T; N]], &mut [T]) {
unsafe fn as_chunks_unchecked_mut<T, const N: usize>(slice: &mut [T]) -> &mut [[T; N]] {
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
let new_len = slice.len() / N;
// SAFETY: We cast a slice of `new_len * N` elements into
// a slice of `new_len` many `N` elements chunks.
unsafe { core::slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), new_len) }
}
assert!(N != 0, "chunk size must be non-zero");
let len = slice.len() / N;
let (multiple_of_n, remainder) = slice.split_at_mut(len * N);
// SAFETY: We already panicked for zero, and ensured by construction
// that the length of the subslice is a multiple of N.
let array_slice = unsafe { as_chunks_unchecked_mut(multiple_of_n) };
(array_slice, remainder)
}

View file

@ -2,6 +2,7 @@ pub use image::ImageError;
use librashader_common::Size; use librashader_common::Size;
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::array_chunks_mut::ArrayChunksMut;
use std::path::Path; use std::path::Path;
/// An uncompressed raw image ready to upload to GPU buffers. /// An uncompressed raw image ready to upload to GPU buffers.
@ -37,7 +38,8 @@ impl PixelFormat for RGBA8 {
impl PixelFormat for BGRA8 { impl PixelFormat for BGRA8 {
fn convert(pixels: &mut Vec<u8>) { fn convert(pixels: &mut Vec<u8>) {
for [r, _g, b, _a] in pixels.array_chunks_mut::<4>() { assert!(pixels.len() % 4 == 0);
for [r, _g, b, _a] in ArrayChunksMut::new(pixels) {
std::mem::swap(b, r) std::mem::swap(b, r)
} }
} }

View file

@ -6,7 +6,6 @@
//! //!
//! If you are _writing_ a librashader runtime implementation, using these traits and helpers will //! If you are _writing_ a librashader runtime implementation, using these traits and helpers will
//! help in maintaining consistent behaviour in binding semantics and image handling. //! help in maintaining consistent behaviour in binding semantics and image handling.
#![feature(array_chunks)]
/// Scaling helpers. /// Scaling helpers.
pub mod scaling; pub mod scaling;
@ -37,3 +36,6 @@ pub mod render_target;
/// Helpers for handling framebuffers. /// Helpers for handling framebuffers.
pub mod framebuffer; pub mod framebuffer;
/// array_chunks_mut polyfill
mod array_chunks_mut;

51
librashader.copr.spec Normal file
View file

@ -0,0 +1,51 @@
%global commit 1dca2a97d03fc6aa531a03ba7aaa9ca3dbcb5a61
%global shortcommit %(c=%{commit}; echo ${c:0:7})
Name: librashader
%define lname librashader0
%define profile optimized
Version: {{{ git_dir_version }}}
Release: %autorelease
Summary: RetroArch shaders for all
License: MPL-2.0
URL: https://github.com/SnowflakePowered/%{name}
%undefine _disable_source_fetch
Source: {{{ git_dir_pack }}}
BuildRequires: pkgconfig(vulkan)
BuildRequires: pkgconfig(shaderc)
BuildRequires: cmake
BuildRequires: gcc
BuildRequires: git
BuildRequires: g++
BuildRequires: ninja-build
BuildRequires: patchelf
BuildRequires: rustc
BuildRequires: cargo
Requires: vulkan
%description
RetroArch shader runtime
Summary: RetroArch shader runtime
Provides: librashader
%prep
{{{ git_dir_setup_macro }}}
%build
# need to use stable compiler, but enable nightly features
RUSTC_BOOTSTRAP=1 cargo run -p librashader-build-script -- --profile %{profile}
%install
mkdir -p %{buildroot}/%{_libdir}
mkdir -p %{buildroot}/%{_includedir}/librashader
patchelf --set-soname librashader.so.1 target/%{profile}/librashader.so
install -m 0755 target/%{profile}/librashader.so %{buildroot}%{_libdir}/librashader.so
cp target/%{profile}/librashader.h %{buildroot}%{_includedir}/librashader/librashader.h
cp include/librashader_ld.h %{buildroot}%{_includedir}/librashader/librashader_ld.h
%files
%{_libdir}/librashader.so
%{_libdir}/librashader.so.1
%{_includedir}/librashader/

View file

@ -4,7 +4,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
license = "MPL-2.0 OR GPL-3.0-only" license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2" version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"] authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader" repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md" readme = "../README.md"
@ -13,19 +13,21 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all." description = "RetroArch shaders for all."
[dependencies] [dependencies]
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" } librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["standalone"] } librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = ["standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.2" } librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.5" }
librashader-runtime-d3d11 = { path = "../librashader-runtime-d3d11", version = "0.2.0-beta.2", optional = true } librashader-runtime-d3d11 = { path = "../librashader-runtime-d3d11", version = "0.2.0-beta.5", optional = true }
librashader-runtime-d3d12 = { path = "../librashader-runtime-d3d12", version = "0.2.0-beta.2", optional = true } librashader-runtime-d3d12 = { path = "../librashader-runtime-d3d12", version = "0.2.0-beta.5", optional = true }
librashader-runtime-gl = { path = "../librashader-runtime-gl", version = "0.2.0-beta.2", optional = true } librashader-runtime-gl = { path = "../librashader-runtime-gl", version = "0.2.0-beta.5", optional = true }
librashader-runtime-vk = { path = "../librashader-runtime-vk", version = "0.2.0-beta.2", optional = true } librashader-runtime-vk = { path = "../librashader-runtime-vk", version = "0.2.0-beta.5", optional = true }
librashader-runtime-wgpu = { path = "../librashader-runtime-wgpu", version = "0.2.0-beta.5", optional = true }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2" } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5" }
ash = { version = "0.37", optional = true } ash = { version = "0.37", optional = true }
wgpu = { version = "0.19.1", optional = true }
[target.'cfg(windows)'.dependencies.windows] [target.'cfg(windows)'.dependencies.windows]
version = "0.48.0" version = "0.48.0"
@ -39,17 +41,20 @@ preprocess = []
presets = [] presets = []
# runtimes # runtimes
runtime-gl = [ "runtime", "reflect-cross", "librashader-common/opengl", "librashader-runtime-gl" ] runtime-gl = [ "runtime", "reflect-cross", "librashader-common/opengl", "librashader-runtime-gl" ]
runtime-d3d11 = [ "runtime", "reflect-cross","librashader-common/d3d11", "librashader-runtime-d3d11", "windows/Win32_Graphics_Direct3D11" ] runtime-d3d11 = [ "runtime", "reflect-cross","librashader-common/d3d11", "librashader-runtime-d3d11", "windows/Win32_Graphics_Direct3D11" ]
runtime-d3d12 = [ "runtime", "reflect-cross", "reflect-dxil", "librashader-common/d3d12", "librashader-runtime-d3d12", "windows/Win32_Graphics_Direct3D12" ] runtime-d3d12 = [ "runtime", "reflect-cross", "reflect-dxil", "librashader-common/d3d12", "librashader-runtime-d3d12", "windows/Win32_Graphics_Direct3D12" ]
runtime-vk = ["runtime", "reflect-cross", "librashader-common/vulkan", "librashader-runtime-vk", "ash" ] runtime-vk = ["runtime", "reflect-cross", "librashader-common/vulkan", "librashader-runtime-vk", "ash" ]
runtime-wgpu = [ "runtime", "reflect-naga", "librashader-common/wgpu", "librashader-runtime-wgpu", "wgpu" ]
# reflection # reflection
reflect-cross = ["reflect", "librashader-reflect/cross"] reflect-cross = ["reflect", "librashader-reflect/cross"]
reflect-dxil = ["reflect", "librashader-reflect/dxil"] reflect-dxil = ["reflect", "librashader-reflect/dxil"]
reflect-naga = ["reflect", "librashader-reflect/naga"]
runtime-all = ["runtime-gl", "runtime-d3d11", "runtime-d3d12", "runtime-vk"] runtime-all = ["runtime-gl", "runtime-d3d11", "runtime-d3d12", "runtime-vk", "runtime-wgpu"]
reflect-all = ["reflect-cross", "reflect-dxil"] reflect-all = ["reflect-cross", "reflect-dxil", "reflect-naga"]
# enable all features by default # enable all features by default
default = [ "full" ] default = [ "full" ]

View file

@ -15,7 +15,7 @@
//! called with appropriate input and output parameters to draw a frame with the shader effect applied. //! called with appropriate input and output parameters to draw a frame with the shader effect applied.
//! //!
//! ## Runtimes //! ## Runtimes
//! Currently available runtimes are Vulkan, OpenGL 3.3+ and 4.6 (with DSA), Direct3D 11, and Direct3D 12. //! Currently available runtimes are wgpu, Vulkan, OpenGL 3.3+ and 4.6 (with DSA), Direct3D 11, and Direct3D 12.
//! //!
//! The Vulkan runtime requires [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html) //! The Vulkan runtime requires [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html)
//! by default, unless [`FilterChainOptions::use_render_pass`](crate::runtime::vk::FilterChainOptions) is explicitly set. Note that dynamic rendering //! by default, unless [`FilterChainOptions::use_render_pass`](crate::runtime::vk::FilterChainOptions) is explicitly set. Note that dynamic rendering
@ -24,6 +24,8 @@
//! The Direct3D 12 runtime requires support for [render passes](https://learn.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-render-passes), which //! The Direct3D 12 runtime requires support for [render passes](https://learn.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-render-passes), which
//! have been available since Windows 10, version 1809. //! have been available since Windows 10, version 1809.
//! //!
//! wgpu support is not available in the librashader C API.
//!
//! | **API** | **Status** | **`librashader` feature** | //! | **API** | **Status** | **`librashader` feature** |
//! |-------------|------------|---------------------------| //! |-------------|------------|---------------------------|
//! | OpenGL 3.3+ | ✔ | `gl` | //! | OpenGL 3.3+ | ✔ | `gl` |
@ -31,8 +33,9 @@
//! | Vulkan | ✔ | `vk` | //! | Vulkan | ✔ | `vk` |
//! | Direct3D 11 | ✔ | `d3d11` | //! | Direct3D 11 | ✔ | `d3d11` |
//! | Direct3D 12 | ✔ | `d3d12` | //! | Direct3D 12 | ✔ | `d3d12` |
//! | wgpu | ✔ | `wgpu` |
//! | Metal | ❌ | | //! | Metal | ❌ | |
//! | WebGPU | ❌ | | //!
//! ## C API //! ## C API
//! For documentation on the librashader C API, see [librashader-capi](https://docs.rs/librashader-capi/latest/librashader_capi/), //! For documentation on the librashader C API, see [librashader-capi](https://docs.rs/librashader-capi/latest/librashader_capi/),
//! or [`librashader.h`](https://github.com/SnowflakePowered/librashader/blob/master/include/librashader.h). //! or [`librashader.h`](https://github.com/SnowflakePowered/librashader/blob/master/include/librashader.h).
@ -135,6 +138,7 @@ pub mod reflect {
pub use librashader_reflect::back::targets::GLSL; pub use librashader_reflect::back::targets::GLSL;
pub use librashader_reflect::back::targets::HLSL; pub use librashader_reflect::back::targets::HLSL;
pub use librashader_reflect::back::targets::SPIRV; pub use librashader_reflect::back::targets::SPIRV;
pub use librashader_reflect::back::targets::WGSL;
} }
pub use librashader_reflect::error::*; pub use librashader_reflect::error::*;
@ -146,12 +150,12 @@ pub mod reflect {
FromCompilation, ShaderCompilerOutput, FromCompilation, ShaderCompilerOutput,
}; };
pub use librashader_reflect::front::GlslangCompilation;
/// Reflection via SPIRV-Cross. /// Reflection via SPIRV-Cross.
#[cfg(feature = "reflect-cross")] #[cfg(feature = "reflect-cross")]
#[doc(cfg(feature = "reflect-cross"))] #[doc(cfg(feature = "reflect-cross"))]
pub mod cross { pub mod cross {
pub use librashader_reflect::front::GlslangCompilation;
/// The version of GLSL to target. /// The version of GLSL to target.
/// ///
pub use librashader_reflect::back::cross::GlslVersion; pub use librashader_reflect::back::cross::GlslVersion;
@ -180,6 +184,14 @@ pub mod reflect {
pub use librashader_reflect::back::dxil::DxilObject; pub use librashader_reflect::back::dxil::DxilObject;
} }
/// Reflection via Naga
#[cfg(feature = "reflect-naga")]
#[doc(cfg(feature = "reflect-naga"))]
pub mod naga {
pub use librashader_reflect::back::wgsl::NagaWgslContext;
pub use librashader_reflect::back::wgsl::WgslCompileOptions;
}
pub use librashader_reflect::reflect::semantics::BindingMeta; pub use librashader_reflect::reflect::semantics::BindingMeta;
pub use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; pub use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
@ -292,6 +304,19 @@ pub mod runtime {
pub use librashader_runtime_vk::*; pub use librashader_runtime_vk::*;
} }
} }
#[cfg(feature = "runtime-wgpu")]
#[doc(cfg(feature = "runtime-wgpu"))]
/// Shader runtime for wgpu
pub mod wgpu {
pub use librashader_runtime_wgpu::{
error,
options::{
FilterChainOptionsWgpu as FilterChainOptions, FrameOptionsWgpu as FrameOptions,
},
FilterChainWgpu as FilterChain, WgpuOutputView,
};
}
} }
pub use librashader_common::{FilterMode, ImageFormat, WrapMode}; pub use librashader_common::{FilterMode, ImageFormat, WrapMode};

10
pkg/librashader.pc Normal file
View file

@ -0,0 +1,10 @@
prefix=/usr/local
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${exec_prefix}/include
Name: librashader
Description: RetroArch shaders for all
Version: 0.2.0
Libs: -L${libdir} -lvulkan -lrashader
Cflags: -I${includedir}

49
pkg/librashader.spec Normal file
View file

@ -0,0 +1,49 @@
%global commit 1dca2a97d03fc6aa531a03ba7aaa9ca3dbcb5a61
%global shortcommit %(c=%{commit}; echo ${c:0:7})
Name: librashader
%define lname librashader0
%define profile optimized
Version: 0.2.0~beta.2
Release: %autorelease
Summary: RetroArch shaders for all
License: MPL-2.0
URL: https://github.com/SnowflakePowered/%{name}
%undefine _disable_source_fetch
Source: https://github.com/SnowflakePowered/%{name}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
BuildRequires: pkgconfig(vulkan)
BuildRequires: pkgconfig(shaderc)
BuildRequires: cmake
BuildRequires: gcc
BuildRequires: g++
BuildRequires: ninja-build
BuildRequires: patchelf
BuildRequires: rustc
BuildRequires: cargo
Requires: vulkan
%description
RetroArch shader runtime
Summary: RetroArch shader runtime
Provides: librashader
%prep
%autosetup -n %{name}-%{commit}
%build
# need to use stable compiler, but enable nightly features
RUSTC_BOOTSTRAP=1 cargo run -p librashader-build-script -- --profile %{profile}
%install
mkdir -p %{buildroot}/%{_libdir}
mkdir -p %{buildroot}/%{_includedir}/librashader
patchelf --set-soname librashader.so.1 target/%{profile}/librashader.so
install -m 0755 target/%{profile}/librashader.so %{buildroot}%{_libdir}/librashader.so
cp target/%{profile}/librashader.h %{buildroot}%{_includedir}/librashader/librashader.h
cp include/librashader_ld.h %{buildroot}%{_includedir}/librashader/librashader_ld.h
%files
%{_libdir}/librashader.so
%{_includedir}/librashader/

33
pkg/obs/PKGBUILD Normal file
View file

@ -0,0 +1,33 @@
pkgname=librashader
pkgver=0.2.0~beta.2
pkgrel=0
pkgdesc="RetroArch shader runtime"
arch=('x86_64' 'aarch64')
url="https://github.com/SnowflakePowered/librashader"
license=('MPL-2.0')
groups=('')
depends=('vulkan-icd-loader' 'shaderc' 'cmake' 'gcc' 'rust' 'patchelf' 'ninja')
provides=("$pkgname=$pkgver" 'librashader.so')
backup=('')
source=("$pkgname-$pkgver.tar.xz" 'vendor.tar.xz' 'cargo_config')
cksums=("SKIP" "SKIP" "SKIP")
profile="optimized"
build() {
cd $pkgname-$pkgver
mkdir .cargo
cp "$srcdir/cargo_config" .cargo/config
cp -r "$srcdir/vendor" "vendor"
cp "$srcdir/Cargo.lock" "Cargo.lock"
RUSTC_BOOTSTRAP=1 cargo run -p librashader-build-script -- --profile ${profile}
}
package() {
mkdir -p $pkgdir/usr/lib
mkdir -p $pkgdir/usr/include/librashader
patchelf --set-soname librashader.so.1 $srcdir/$pkgname-$pkgver/target/$profile/librashader.so
install -m 0755 $srcdir/$pkgname-$pkgver/target/${profile}/librashader.so $pkgdir/usr/lib/librashader.so
cp $srcdir/$pkgname-$pkgver/target/${profile}/librashader.h $pkgdir/usr/include/librashader/librashader.h
cp $srcdir/$pkgname-$pkgver/include/librashader_ld.h $pkgdir/usr/include/librashader/librashader_ld.h
}

17
pkg/obs/_service Normal file
View file

@ -0,0 +1,17 @@
<services>
<service name="obs_scm" mode="manual">
<param name="scm">git</param>
<param name="url">https://github.com/SnowflakePowered/librashader</param>
<param name="revision">master</param>
</service>
<service name="cargo_vendor" mode="manual">
<param name="srcdir">librashader</param>
<param name="compression">xz</param>
</service>
<service name="tar" mode="buildtime"/>
<service name="recompress" mode="buildtime">
<param name="file">*.tar</param>
<param name="compression">xz</param>
</service>
<service name="set_version" mode="buildtime"/>
</services>

51
pkg/obs/librashader.spec Normal file
View file

@ -0,0 +1,51 @@
Name: librashader
%define lname librashader0
%define profile optimized
Summary: RetroArch shaders for all
License: MPL-2.0
Version: 0.2.0~beta.2
Release: 0
URL: https://github.com/SnowflakePowered/%{name}
Source0: librashader-%{version}.tar.xz
Source1: vendor.tar.xz
Source2: cargo_config
BuildRequires: pkgconfig(vulkan)
BuildRequires: pkgconfig(shaderc)
BuildRequires: ninja-build
BuildRequires: patchelf
BuildRequires: cmake
BuildRequires: gcc
BuildRequires: gcc-c++
BuildRequires: cargo
BuildRequires: rust
Requires: vulkan
%description
RetroArch shader runtime
Summary: RetroArch shader runtime
Provides: librashader
%prep
%setup -qa1
mkdir .cargo # cargo automatically uses this dir
cp %{SOURCE2} .cargo/config # and automatically uses this config
%build
RUSTC_BOOTSTRAP=1 cargo run -p librashader-build-script -- --profile %{profile}
%install
mkdir -p %{buildroot}/%{_libdir}
mkdir -p %{buildroot}/%{_includedir}/librashader
patchelf --set-soname librashader.so.1 target/%{profile}/librashader.so
install -m 0755 target/%{profile}/librashader.so %{buildroot}%{_libdir}/librashader.so
cp target/%{profile}/librashader.h %{buildroot}%{_includedir}/librashader/librashader.h
cp include/librashader_ld.h %{buildroot}%{_includedir}/librashader/librashader_ld.h
%files
%{_libdir}/librashader.so
%if !0%{?suse_version}
%{_libdir}/librashader.so.1
%endif
%{_includedir}/librashader/

24
test/extreme_basic.glsl Normal file
View file

@ -0,0 +1,24 @@
#version 450
layout(set = 0, binding = 0, std140) uniform UBO
{
mat4 MVP;
};
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
void main()
{
gl_Position = MVP * Position;
vTexCoord = TexCoord;
}
#pragma stage fragment
layout(location = 0) out vec4 color;
layout(binding = 1) uniform sampler2D tex;
void main() {
color = texture(tex, vec2(0.0));
}

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