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
105 changed files with 7641 additions and 773 deletions

View file

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

View file

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

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
*.cap
/.vs/
.idea/
librashader_runtime_*.exe
/test/capi-tests/librashader-capi-tests/.vs/
/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-capi",
"librashader-build-script"
]
, "librashader-runtime-wgpu"]
resolver = "2"
[workspace.metadata.release]

View file

@ -6,9 +6,13 @@
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)
![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
librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU
are not currently supported (but pull-requests are welcome). librashader does not support legacy render
@ -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
is a no-op, which occurs if no compatible librashader implementation could be found.
The `SONAME` of `librashader.so` when installed via package manager is set to `LIBRASHADER_CURRENT_ABI`.
The above does not apply to releases of librashader prior to `0.1.0`, which were allowed to break API and ABI compatibility
in both the Rust and C API without an increase to either `LIBRASHADER_CURRENT_VERSION` or `LIBRASHADER_CURRENT_ABI`.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,10 @@ pub mod gl;
#[cfg(feature = "vulkan")]
pub mod vk;
/// WGPU common conversions.
#[cfg(feature = "wgpu")]
pub mod wgpu;
/// DXGI common conversions.
#[cfg(all(target_os = "windows", feature = "dxgi"))]
pub mod dxgi;
@ -21,6 +25,7 @@ pub mod d3d11;
pub mod d3d12;
mod viewport;
pub use viewport::Viewport;
use num_traits::AsPrimitive;

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"
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>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -14,7 +14,7 @@ description = "RetroArch shaders for all."
[dependencies]
thiserror = "1.0.37"
nom = "7.1.1"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" }
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
rustc-hash = "1.1.0"
encoding_rs = "0.8.31"

View file

@ -3,7 +3,7 @@ name = "librashader-presets"
edition = "2021"
license = "MPL-2.0 OR GPL-3.0-only"
version = "0.2.0-beta.2"
version = "0.2.0-beta.5"
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -15,7 +15,7 @@ description = "RetroArch shaders for all."
thiserror = "1.0.37"
nom = "7.1.1"
nom_locate = "4.0.0"
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" }
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.5" }
num-traits = "0.2"
[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.
//!
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
#![feature(extract_if)]
#![allow(unstable_name_collisions)]
mod error;
mod extract_if;
mod parse;
mod preset;
pub use error::*;
pub use preset::*;

View file

@ -1,3 +1,4 @@
use crate::extract_if::MakeExtractIf;
use crate::parse::remove_if;
use crate::parse::value::Value;
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};

View file

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

View file

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

Binary file not shown.

View file

@ -3,6 +3,7 @@ pub mod cross;
pub mod dxil;
mod spirv;
pub mod targets;
pub mod wgsl;
use crate::back::targets::OutputTarget;
use crate::error::{ShaderCompileError, ShaderReflectError};
@ -114,3 +115,14 @@ where
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.
pub struct DXIL;
/// Shader compiler target for WGSL.
///
/// The resulting WGSL will split sampler2Ds into
/// split textures and shaders. Shaders for each texture binding
/// will be in descriptor set 1.
#[derive(Debug)]
pub struct WGSL;
impl OutputTarget for GLSL {
type Output = String;
}
impl OutputTarget for HLSL {
type Output = String;
}
impl OutputTarget for WGSL {
type Output = String;
}
impl OutputTarget for SPIRV {
type Output = Vec<u32>;
}
#[cfg(test)]
mod test {
use crate::back::targets::GLSL;
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"))]
#[error("spirv-to-dxil")]
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.
@ -34,6 +43,8 @@ pub enum SemanticsErrorKind {
/// The number of uniform buffers was invalid. Only one UBO is permitted.
InvalidUniformBufferCount(usize),
/// The number of push constant blocks was invalid. Only one push constant block is permitted.
InvalidPushBufferCount(usize),
/// The size of the push constant block was invalid.
InvalidPushBufferSize(u32),
/// The location of a varying was invalid.
InvalidLocation(u32),
@ -43,12 +54,16 @@ pub enum SemanticsErrorKind {
InvalidInputCount(usize),
/// The number of outputs declared was invalid.
InvalidOutputCount(usize),
/// Expected a binding but there was none.
MissingBinding,
/// The declared binding point was invalid.
InvalidBinding(u32),
/// The declared resource type was invalid.
InvalidResourceType,
/// The range of a struct member was invalid.
InvalidRange(u32),
/// The number of entry points in the shader was invalid.
InvalidEntryPointCount(usize),
/// The requested uniform or texture name was not provided semantics.
UnknownSemantics(String),
/// The type of the requested uniform was not compatible with the provided semantics.
@ -59,11 +74,6 @@ pub enum SemanticsErrorKind {
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ShaderReflectError {
/// Compile error from naga.
#[cfg(feature = "unstable-naga")]
#[error("shader")]
NagaCompileError(#[from] naga::front::spv::Error),
/// Reflection error from spirv-cross.
#[error("spirv")]
SpirvCrossError(#[from] spirv_cross::ErrorCode),
@ -100,6 +110,10 @@ pub enum ShaderReflectError {
/// The binding number is already in use.
#[error("the binding is already in use")]
BindingInUse(u32),
/// Error when transpiling from naga
#[cfg(feature = "wgsl")]
#[error("naga-spv")]
NagaInputError(#[from] naga::front::spv::Error),
}
#[cfg(feature = "unstable-naga")]

View file

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

View file

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

View file

@ -1,126 +1,858 @@
use crate::error::{SemanticsErrorKind, ShaderReflectError};
use crate::front::GlslangCompilation;
use crate::front::NagaCompilation;
use naga::front::spv::Options;
use naga::{GlobalVariable, Module, StructMember, Type, TypeInner};
use std::convert::Infallible;
use naga::{
AddressSpace, Binding, GlobalVariable, Handle, ImageClass, Module, ResourceBinding, Scalar,
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)]
pub struct NagaReflect {
vertex: Module,
fragment: Module,
pub(crate) struct NagaReflect {
pub(crate) vertex: Module,
pub(crate) fragment: Module,
}
impl TryFrom<NagaCompilation> for NagaReflect {
type Error = ShaderReflectError;
impl ValidateTypeSemantics<&TypeInner> for UniqueSemantics {
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> {
Ok(NagaReflect {
vertex: value.vertex,
fragment: value.fragment,
})
match self {
UniqueSemantics::MVP => {
if matches!(ty, TypeInner::Matrix { columns, rows, scalar: Scalar { width, .. } } if *columns == VectorSize::Quad
&& *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 {
type Error = ShaderReflectError;
impl ValidateTypeSemantics<&TypeInner> for TextureSemantics {
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> {
let ops = Options::default();
let vertex =
naga::front::spv::Parser::new(value.vertex.clone().into_iter(), &ops).parse()?;
let fragment =
naga::front::spv::Parser::new(value.fragment.clone().into_iter(), &ops).parse()?;
Ok(NagaReflect { vertex, fragment })
}
if *kind == ScalarKind::Float && *width == 4 && *size == VectorSize::Quad {
return Some(TypeInfo {
size: 4,
columns: 1,
});
}
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 {
// pub fn get_ubo_data(arena: Arena, variable: GlobalVariable, blame: SemanticErrorBlame) -> Result<UboData, ShaderReflectError> {
// let binding = match variable.binding {
// Some(ResourceBinding { group: 0, binding }) => binding,
// Some(ResourceBinding { group, .. }) => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(group))),
// None => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(u32::MAX))),
// };
//
// if binding >= MAX_BINDINGS_COUNT {
// return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding)));
// }
//
// match variable.ty.as {
// Handle { .. } => {}
// }
// Ok(UboData {
// binding,
//
// })
// }
pub fn reflect_ubos(
vertex: GlobalVariable,
fragment: GlobalVariable,
) -> Result<(), ShaderReflectError> {
match (vertex.binding, fragment.binding) {
// todo: should emit for both but whatever
(None, None) | (Some(_), None) | (None, Some(_)) => {
ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidDescriptorSet(
u32::MAX,
))
fn reflect_ubos(
&mut self,
vertex_ubo: Option<Handle<GlobalVariable>>,
fragment_ubo: Option<Handle<GlobalVariable>>,
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
// todo: merge this with the spirv-cross code
match (vertex_ubo, fragment_ubo) {
(None, None) => Ok(None),
(Some(vertex_ubo), Some(fragment_ubo)) => {
let vertex_ubo = Self::get_ubo_data(
&self.vertex,
&self.vertex.global_variables[vertex_ubo],
SemanticErrorBlame::Vertex,
)?;
let fragment_ubo = Self::get_ubo_data(
&self.fragment,
&self.fragment.global_variables[fragment_ubo],
SemanticErrorBlame::Fragment,
)?;
if vertex_ubo.binding != fragment_ubo.binding {
return Err(ShaderReflectError::MismatchedUniformBuffer {
vertex: vertex_ubo.binding,
fragment: fragment_ubo.binding,
});
}
(Some(_vert), Some(_frag)) => {
todo!();
let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size);
Ok(Some(BufferReflection {
binding: vertex_ubo.binding,
size: align_uniform_size(size),
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
}))
}
(Some(vertex_ubo), None) => {
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(())
}
}
impl ReflectShader for NagaReflect {
fn reflect(
&mut self,
pass_number: usize,
semantics: &ShaderSemantics,
) -> Result<ShaderReflection, ShaderReflectError> {
self.validate()?;
// Validate verifies that there's only one uniform block.
let vertex_ubo = self
.vertex
.global_variables
.iter()
.find_map(|(handle, gv)| {
if gv.space == AddressSpace::Uniform {
Some(handle)
} else {
None
}
});
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 {
use librashader_preprocess::ShaderSource;
use rspirv::dr::Instruction;
use rspirv::spirv::Op;
#[test]
pub fn test_into() {
let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
let module = loader.module();
let outputs: Vec<&Instruction> = module
.types_global_values
.iter()
.filter(|i| i.class.opcode == Op::Variable)
.collect();
println!("{outputs:#?}");
}
// #[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)]
pub struct UboReflection {
/// The binding point for this UBO.
pub binding: u32,
/// The size of the UBO buffer. UBO sizes returned by reflection is always aligned to a 16 byte boundary.
pub struct BufferReflection<T> {
/// The binding point for this buffer, if applicable
pub binding: T,
/// The size of the buffer. Buffer sizes returned by reflection is always aligned to a 16 byte boundary.
pub size: u32,
/// The mask indicating for which stages the UBO should be bound.
pub stage_mask: BindingStage,
}
/// Reflection information for the Push Constant Block
#[derive(Debug)]
pub struct PushReflection {
/// The size of the Push Constant range. The size returned by reflection is always aligned to a 16 byte boundary.
pub size: u32,
/// The mask indicating for which stages the Push Constant range should be bound.
pub stage_mask: BindingStage,
}
/// The offset of a uniform member.
///
/// A uniform can be bound to both the UBO, or as a Push Constant.
@ -279,9 +270,9 @@ pub struct TextureBinding {
#[derive(Debug)]
pub struct ShaderReflection {
/// Reflection information about the UBO for this shader.
pub ubo: Option<UboReflection>,
pub ubo: Option<BufferReflection<u32>>,
/// Reflection information about the Push Constant range for this shader.
pub push_constant: Option<PushReflection>,
pub push_constant: Option<BufferReflection<Option<u32>>>,
/// Metadata about the bindings required for this shader.
pub meta: BindingMeta,
}

View file

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

View file

@ -265,7 +265,9 @@ impl FilterChainD3D11 {
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)?;
Some(ConstantBufferBinding {
binding: ubo.binding,
@ -277,7 +279,9 @@ impl FilterChainD3D11 {
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)?;
Some(ConstantBufferBinding {
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 {
unsafe {
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;
}
}

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";
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/feedback.slangp";

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-d3d12"
edition = "2021"
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>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,12 +12,12 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", features = ["d3d12"], version = "0.2.0-beta.2" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["dxil", "standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.2" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.2", features = ["d3d"] }
librashader-common = { path = "../librashader-common", features = ["d3d12"], 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 = ["dxil", "standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.5" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5", features = ["d3d"] }
thiserror = "1.0.37"
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }

View file

@ -15,7 +15,6 @@ use windows::Win32::Graphics::Direct3D12::{
D3D12_GPU_DESCRIPTOR_HANDLE,
};
#[const_trait]
pub trait D3D12HeapType {
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC;
}
@ -36,7 +35,7 @@ pub struct ResourceWorkHeap;
#[derive(Clone)]
pub struct SamplerWorkHeap;
impl const D3D12HeapType for SamplerPaletteHeap {
impl D3D12HeapType for SamplerPaletteHeap {
// sampler palettes just get set directly
fn get_desc(size: usize) -> 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.
fn get_desc(size: usize) -> 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.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
@ -73,7 +72,7 @@ impl const D3D12HeapType for RenderTargetHeap {
}
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.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {
@ -86,7 +85,7 @@ impl const D3D12HeapType for ResourceWorkHeap {
}
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.
fn get_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
D3D12_DESCRIPTOR_HEAP_DESC {

View file

@ -477,16 +477,15 @@ impl FilterChainD3D12 {
.into();
// incredibly cursed.
let (reflection, graphics_pipeline) = if !force_hlsl &&
let Ok(graphics_pipeline) =
D3D12GraphicsPipeline::new_from_dxil(
let (reflection, graphics_pipeline) = if !force_hlsl
&& let Ok(graphics_pipeline) = D3D12GraphicsPipeline::new_from_dxil(
device,
library,
validator,
&dxil,
root_signature,
render_format,
disable_cache
disable_cache,
) {
(dxil_reflection, graphics_pipeline)
} else {
@ -500,7 +499,7 @@ impl FilterChainD3D12 {
&hlsl,
root_signature,
render_format,
disable_cache
disable_cache,
)?;
(hlsl_reflection, graphics_pipeline)
};

View file

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

View file

@ -7,7 +7,7 @@ fn triangle_d3d12() {
let sample = hello_triangle::d3d12_hello_triangle::Sample::new(
// "../test/shaders_slang/crt/crt-lottes.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/slang-shaders/test/feedback.slangp",
// "../test/slang-shaders/test/history.slangp",

View file

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

View file

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

View file

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

View file

@ -52,7 +52,7 @@ impl DrawQuad for Gl3DrawQuad {
gl::FLOAT,
gl::FALSE,
(4 * std::mem::size_of::<f32>()) as GLsizei,
std::ptr::invalid(0),
sptr::invalid(0),
);
gl::VertexAttribPointer(
1,
@ -60,7 +60,7 @@ impl DrawQuad for Gl3DrawQuad {
gl::FLOAT,
gl::FALSE,
(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::gl::UboRing;
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::RingBuffer;
use librashader_runtime::uniforms::UniformStorageAccess;
@ -31,7 +31,7 @@ impl<const SIZE: usize> UboRing<SIZE> for Gl3UboRing<SIZE> {
fn bind_for_frame(
&mut self,
ubo: &UboReflection,
ubo: &BufferReflection<u32>,
ubo_location: &UniformLocation<GLuint>,
storage: &impl UniformStorageAccess,
) {

View file

@ -1,7 +1,7 @@
use crate::binding::UniformLocation;
use crate::gl::UboRing;
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::RingBuffer;
use librashader_runtime::uniforms::UniformStorageAccess;
@ -30,7 +30,7 @@ impl<const SIZE: usize> UboRing<SIZE> for Gl46UboRing<SIZE> {
fn bind_for_frame(
&mut self,
ubo: &UboReflection,
ubo: &BufferReflection<u32>,
ubo_location: &UniformLocation<GLuint>,
storage: &impl UniformStorageAccess,
) {

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ name = "librashader-runtime-vk"
edition = "2021"
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>"]
repository = "https://github.com/SnowflakePowered/librashader"
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
[dependencies]
librashader-common = { path = "../librashader-common", features = ["vulkan"], version = "0.2.0-beta.2" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = [] }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.2" }
librashader-cache = { path = "../librashader-cache", 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.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5", features = [] }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.5" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.5" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
rustc-hash = "1.1.0"
@ -37,9 +37,5 @@ winit = "0.27.5"
raw-window-handle = "0.5"
ash-window = "0.12.0"
[[test]]
name = "triangle"
[package.metadata.docs.rs]
features = ["librashader-cache/docsrs"]

View file

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

View file

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

View file

@ -7,7 +7,7 @@ use crate::render_pass::VulkanRenderPass;
use ash::vk::PushConstantRange;
use librashader_cache::cache_pipeline;
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_runtime::render_target::RenderTarget;
use std::ffi::CStr;
@ -30,8 +30,10 @@ impl PipelineDescriptors {
}
}
pub fn add_ubo_binding(&mut self, ubo_meta: Option<&UboReflection>) {
if let Some(ubo_meta) = ubo_meta && !ubo_meta.stage_mask.is_empty() {
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()
{
let ubo_mask = util::binding_stage_to_vulkan_stage(ubo_meta.stage_mask);
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 = 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 pool_info = vk::DescriptorPoolCreateInfo::builder()
@ -396,14 +386,13 @@ impl VulkanGraphicsPipeline {
#[inline(always)]
pub(crate) fn begin_rendering(
&self,
device: &ash::Device,
output: &RenderTarget<OutputImage>,
cmd: vk::CommandBuffer,
) -> error::Result<Option<vk::Framebuffer>> {
if let Some(render_pass) = &self.render_pass {
let attachments = [output.output.image_view];
let framebuffer = unsafe {
device.create_framebuffer(
self.device.create_framebuffer(
&vk::FramebufferCreateInfo::builder()
.render_pass(render_pass.handle)
.attachments(&attachments)
@ -430,7 +419,11 @@ impl VulkanGraphicsPipeline {
extent: output.output.size.into(),
});
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))
} else {
@ -450,18 +443,18 @@ impl VulkanGraphicsPipeline {
.color_attachments(&attachments);
unsafe {
device.cmd_begin_rendering(cmd, &rendering_info);
self.device.cmd_begin_rendering(cmd, &rendering_info);
}
Ok(None)
}
}
pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) {
pub(crate) fn end_rendering(&self, cmd: vk::CommandBuffer) {
unsafe {
if self.render_pass.is_none() {
device.cmd_end_rendering(cmd);
self.device.cmd_end_rendering(cmd);
} 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)]
#![feature(type_alias_impl_trait)]
#![feature(let_chains)]
#![feature(strict_provenance)]
mod draw_quad;
mod filter_chain;

View file

@ -11,8 +11,8 @@ fn triangle_vk() {
unsafe {
let filter = FilterChainVulkan::load_from_path(
// "../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/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",
&base,
// "../test/slang-shaders/test/feedback.slancargogp",
// "../test/basic.slangp",

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"
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>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -12,10 +12,10 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" }
librashader-reflect = { path = "../librashader-reflect", 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.5" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.5" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.5" }
bytemuck = "1.12.3"
rustc-hash = "1.1.0"
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 std::marker::PhantomData;
use crate::array_chunks_mut::ArrayChunksMut;
use std::path::Path;
/// An uncompressed raw image ready to upload to GPU buffers.
@ -37,7 +38,8 @@ impl PixelFormat for RGBA8 {
impl PixelFormat for BGRA8 {
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)
}
}

View file

@ -6,7 +6,6 @@
//!
//! 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.
#![feature(array_chunks)]
/// Scaling helpers.
pub mod scaling;
@ -37,3 +36,6 @@ pub mod render_target;
/// Helpers for handling framebuffers.
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
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>"]
repository = "https://github.com/SnowflakePowered/librashader"
readme = "../README.md"
@ -13,19 +13,21 @@ keywords = ["shader", "retroarch", "SPIR-V"]
description = "RetroArch shaders for all."
[dependencies]
librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.2" }
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.2" }
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.2", features = ["standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.2" }
librashader-runtime-d3d11 = { path = "../librashader-runtime-d3d11", version = "0.2.0-beta.2", optional = true }
librashader-runtime-d3d12 = { path = "../librashader-runtime-d3d12", version = "0.2.0-beta.2", optional = true }
librashader-runtime-gl = { path = "../librashader-runtime-gl", version = "0.2.0-beta.2", optional = true }
librashader-runtime-vk = { path = "../librashader-runtime-vk", version = "0.2.0-beta.2", optional = true }
librashader-common = { path = "../librashader-common", 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 = ["standalone"] }
librashader-runtime = { path = "../librashader-runtime", version = "0.2.0-beta.5" }
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.5", 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.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 }
wgpu = { version = "0.19.1", optional = true }
[target.'cfg(windows)'.dependencies.windows]
version = "0.48.0"
@ -39,17 +41,20 @@ preprocess = []
presets = []
# runtimes
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-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-wgpu = [ "runtime", "reflect-naga", "librashader-common/wgpu", "librashader-runtime-wgpu", "wgpu" ]
# reflection
reflect-cross = ["reflect", "librashader-reflect/cross"]
reflect-dxil = ["reflect", "librashader-reflect/dxil"]
reflect-naga = ["reflect", "librashader-reflect/naga"]
runtime-all = ["runtime-gl", "runtime-d3d11", "runtime-d3d12", "runtime-vk"]
reflect-all = ["reflect-cross", "reflect-dxil"]
runtime-all = ["runtime-gl", "runtime-d3d11", "runtime-d3d12", "runtime-vk", "runtime-wgpu"]
reflect-all = ["reflect-cross", "reflect-dxil", "reflect-naga"]
# enable all features by default
default = [ "full" ]

View file

@ -15,7 +15,7 @@
//! called with appropriate input and output parameters to draw a frame with the shader effect applied.
//!
//! ## 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)
//! 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
//! have been available since Windows 10, version 1809.
//!
//! wgpu support is not available in the librashader C API.
//!
//! | **API** | **Status** | **`librashader` feature** |
//! |-------------|------------|---------------------------|
//! | OpenGL 3.3+ | ✔ | `gl` |
@ -31,8 +33,9 @@
//! | Vulkan | ✔ | `vk` |
//! | Direct3D 11 | ✔ | `d3d11` |
//! | Direct3D 12 | ✔ | `d3d12` |
//! | wgpu | ✔ | `wgpu` |
//! | Metal | ❌ | |
//! | WebGPU | ❌ | |
//!
//! ## C API
//! 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).
@ -135,6 +138,7 @@ pub mod reflect {
pub use librashader_reflect::back::targets::GLSL;
pub use librashader_reflect::back::targets::HLSL;
pub use librashader_reflect::back::targets::SPIRV;
pub use librashader_reflect::back::targets::WGSL;
}
pub use librashader_reflect::error::*;
@ -146,12 +150,12 @@ pub mod reflect {
FromCompilation, ShaderCompilerOutput,
};
pub use librashader_reflect::front::GlslangCompilation;
/// Reflection via SPIRV-Cross.
#[cfg(feature = "reflect-cross")]
#[doc(cfg(feature = "reflect-cross"))]
pub mod cross {
pub use librashader_reflect::front::GlslangCompilation;
/// The version of GLSL to target.
///
pub use librashader_reflect::back::cross::GlslVersion;
@ -180,6 +184,14 @@ pub mod reflect {
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::presets::{CompilePresetTarget, ShaderPassArtifact};
@ -292,6 +304,19 @@ pub mod runtime {
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};

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

0
test/extreme_basic.spv Normal file
View file

View file

@ -0,0 +1,9 @@
#version 450
layout(location = 0) out vec4 color;
layout(set = 0, binding = 1) uniform texture2D tex;
layout(set = 1, binding = 1) uniform sampler tex_sampler;
void main() {
color = texture(sampler2D(tex, tex_sampler), vec2(0.0));
}

View file

@ -0,0 +1,47 @@
module.dialect = spv.Module(version: 1.0, spv.Capability.{Shader, Sampled1D}, spv.MemoryModel.GLSL450)
module.debug_info = spv.Module.DebugInfo(generator: spv.Tool(id: 0, version: 28))
type T0 = spv.OpTypeImage(SampledType: f32, spv.Dim.2D, Depth: 0, Arrayed: 0, MS: 0, Sampled: 1, spv.ImageFormat.Unknown)
#[spv.Decoration.Location(Location: 0)]
global_var GV0(spv.StorageClass.Output): f32×4
#[spv.Decoration.Binding(BindingPoint: 1)]
#[spv.Decoration.DescriptorSet(DescriptorSet: 0)]
global_var GV1(spv.StorageClass.UniformConstant): T0
#[spv.Decoration.Binding(BindingPoint: 1)]
#[spv.Decoration.DescriptorSet(DescriptorSet: 1)]
global_var GV2(spv.StorageClass.UniformConstant): spv.OpTypeSampler
global_var GV3(spv.StorageClass.Private, init: f32×4(0.0, 0.0, 0.0, 0.0)): f32×4
func F0() {
v0 = spv.OpLoad(Pointer: &GV1): T0
v1 = spv.OpLoad(Pointer: &GV2): spv.OpTypeSampler
branch L0
label L0:
v2 = spv.OpSampledImage(Image: v0, Sampler: v1): spv.OpTypeSampledImage(ImageType: T0)
v3 = spv.OpImageSampleImplicitLod(SampledImage: v2, Coordinate: f32×2(0.0, 0.0)): f32×4
spv.OpStore(Pointer: &GV3, Object: v3)
return
}
#[spv.ExecutionMode.OriginUpperLeft]
func F1() {
_ = spv.OpLoad(Pointer: &GV1): T0
_ = spv.OpLoad(Pointer: &GV2): spv.OpTypeSampler
branch L0
label L0:
call F0()
v0 = spv.OpLoad(Pointer: &GV3): f32×4
spv.OpStore(Pointer: &GV0, Object: v0)
return
}
export {
spv.OpEntryPoint(spv.ExecutionModel.Fragment, Name: "main"): F1,
}

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