Compare commits
53 commits
2d0dd5909d
...
446b052657
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | 446b052657 | ||
Alex Janka | 9d926b0051 | ||
fb2bcc5d52 | |||
8015a2a796 | |||
5c8428eac8 | |||
8fb2179ae8 | |||
f6268a621c | |||
bbfd5153da | |||
37397ff216 | |||
754e8da620 | |||
6c50880600 | |||
acc9bfeb53 | |||
54e86e7b06 | |||
2450217c29 | |||
962a81c2e3 | |||
11ab4b7c9a | |||
121dbc4ed6 | |||
31891e414f | |||
e39834547c | |||
d5aa6b2e4a | |||
cc26be486b | |||
10358b4966 | |||
32148cdff4 | |||
555ff6f9fc | |||
34f224cc5d | |||
7586ed4633 | |||
2b995539f2 | |||
8cfe1e9da2 | |||
f9df72a02d | |||
4e052159e7 | |||
171c842c97 | |||
4dfcdf2725 | |||
1a16c4fadf | |||
c05d8ff06a | |||
ec98494202 | |||
ef4e2353ff | |||
a5e978f158 | |||
97ff76276f | |||
797625903a | |||
81ba694ba4 | |||
18ff9cf05a | |||
ae2a427b5e | |||
552be8c34e | |||
7c0190004f | |||
a6c91a07df | |||
d700234c3c | |||
96f937586c | |||
9c5a8f4042 | |||
d5bf7e312c | |||
80325fda9e | |||
60fac06332 | |||
92e8a05f8a | |||
617bfdd93e |
3
.github/workflows/build-linux-arm64.yml
vendored
3
.github/workflows/build-linux-arm64.yml
vendored
|
@ -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
|
||||
|
|
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
|
@ -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
48
.github/workflows/package-obs.yml
vendored
Normal 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
80
.github/workflows/publish-obs.yml
vendored
Normal 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
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
*.rdc
|
||||
*.cap
|
||||
/.vs/
|
||||
.idea/
|
||||
librashader_runtime_*.exe
|
||||
/test/capi-tests/librashader-capi-tests/.vs/
|
||||
/test/capi-tests/librashader-capi-tests/x64/
|
||||
|
|
8
.idea/.gitignore
vendored
8
.idea/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace">
|
||||
<contentRoot DIR="$PROJECT_DIR$" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
|
@ -1,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
2061
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,7 @@ members = [
|
|||
"librashader-cache",
|
||||
"librashader-capi",
|
||||
"librashader-build-script"
|
||||
]
|
||||
, "librashader-runtime-wgpu"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,12 +25,19 @@ pub mod d3d11;
|
|||
pub mod d3d12;
|
||||
|
||||
mod viewport;
|
||||
|
||||
pub use viewport::Viewport;
|
||||
|
||||
use num_traits::AsPrimitive;
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ShaderStorage {
|
||||
Path(std::path::PathBuf),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
/// Supported image formats for textures.
|
||||
|
|
177
librashader-common/src/wgpu.rs
Normal file
177
librashader-common/src/wgpu.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ use crate::include::read_source;
|
|||
pub use error::*;
|
||||
use librashader_common::ImageFormat;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::Path;
|
||||
|
||||
/// The source file for a single shader pass.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -58,8 +57,8 @@ pub struct ShaderParameter {
|
|||
impl ShaderSource {
|
||||
/// Load the source file at the given path, resolving includes relative to the location of the
|
||||
/// source file.
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
|
||||
load_shader_source(path)
|
||||
pub fn load(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
|
||||
load_shader_source(file)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,8 +77,14 @@ impl SourceOutput for String {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
|
||||
let source = read_source(path)?;
|
||||
pub(crate) fn load_shader_source(
|
||||
file: &librashader_common::ShaderStorage,
|
||||
) -> Result<ShaderSource, PreprocessError> {
|
||||
let source = match file {
|
||||
librashader_common::ShaderStorage::Path(path) => read_source(path)?,
|
||||
librashader_common::ShaderStorage::String(s) => s.to_string(),
|
||||
};
|
||||
|
||||
let meta = pragma::parse_pragma_meta(&source)?;
|
||||
let text = stage::process_stages(&source)?;
|
||||
let parameters = FxHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
|
||||
|
|
|
@ -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]
|
||||
|
|
312
librashader-presets/src/extract_if.rs
Normal file
312
librashader-presets/src/extract_if.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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};
|
||||
|
@ -114,7 +115,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
|
||||
let shader = ShaderPassConfig {
|
||||
id,
|
||||
name,
|
||||
name: librashader_common::ShaderStorage::Path(name),
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
|
|||
/// The index of the shader pass relative to its parent preset.
|
||||
pub id: i32,
|
||||
/// The fully qualified path to the shader pass source file.
|
||||
pub name: PathBuf,
|
||||
pub name: librashader_common::ShaderStorage,
|
||||
/// The alias of the shader pass if available.
|
||||
pub alias: Option<String>,
|
||||
/// The filtering mode that this shader pass should expect.
|
||||
|
|
|
@ -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" ]
|
||||
|
|
BIN
librashader-reflect/basic.spv
Normal file
BIN
librashader-reflect/basic.spv
Normal file
Binary file not shown.
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
1077
librashader-reflect/src/back/wgsl/lower_samplers.rs
Normal file
1077
librashader-reflect/src/back/wgsl/lower_samplers.rs
Normal file
File diff suppressed because it is too large
Load diff
227
librashader-reflect/src/back/wgsl/mod.rs
Normal file
227
librashader-reflect/src/back/wgsl/mod.rs
Normal 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:#?}");
|
||||
}
|
||||
}
|
|
@ -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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:#}")
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
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!(),
|
||||
if *kind == ScalarKind::Float && *width == 4 && *size == VectorSize::Quad {
|
||||
return Some(TypeInfo {
|
||||
size: 4,
|
||||
columns: 1,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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(_vert), Some(_frag)) => {
|
||||
todo!();
|
||||
(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 ¶meter.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(())
|
||||
}
|
||||
}
|
||||
#[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();
|
||||
impl ReflectShader for NagaReflect {
|
||||
fn reflect(
|
||||
&mut self,
|
||||
pass_number: usize,
|
||||
semantics: &ShaderSemantics,
|
||||
) -> Result<ShaderReflection, ShaderReflectError> {
|
||||
self.validate()?;
|
||||
|
||||
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
|
||||
// Validate verifies that there's only one uniform block.
|
||||
let vertex_ubo = self
|
||||
.vertex
|
||||
.global_variables
|
||||
.iter()
|
||||
.filter(|i| i.class.opcode == Op::Variable)
|
||||
.collect();
|
||||
.find_map(|(handle, gv)| {
|
||||
if gv.space == AddressSpace::Uniform {
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
println!("{outputs:#?}");
|
||||
let fragment_ubo = self
|
||||
.fragment
|
||||
.global_variables
|
||||
.iter()
|
||||
.find_map(|(handle, gv)| {
|
||||
if gv.space == AddressSpace::Uniform {
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let ubo = self.reflect_ubos(vertex_ubo, fragment_ubo)?;
|
||||
|
||||
let vertex_push = self
|
||||
.vertex
|
||||
.global_variables
|
||||
.iter()
|
||||
.find_map(|(handle, gv)| {
|
||||
if gv.space == AddressSpace::PushConstant {
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let fragment_push = self
|
||||
.fragment
|
||||
.global_variables
|
||||
.iter()
|
||||
.find_map(|(handle, gv)| {
|
||||
if gv.space == AddressSpace::PushConstant {
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let push_constant = self.reflect_push_constant_buffer(vertex_push, fragment_push)?;
|
||||
|
||||
let mut meta = BindingMeta::default();
|
||||
|
||||
if let Some(ubo) = vertex_ubo {
|
||||
Self::reflect_buffer_struct_members(
|
||||
&self.vertex,
|
||||
ubo,
|
||||
pass_number,
|
||||
semantics,
|
||||
&mut meta,
|
||||
UniformMemberBlock::Ubo,
|
||||
SemanticErrorBlame::Vertex,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(ubo) = fragment_ubo {
|
||||
Self::reflect_buffer_struct_members(
|
||||
&self.fragment,
|
||||
ubo,
|
||||
pass_number,
|
||||
semantics,
|
||||
&mut meta,
|
||||
UniformMemberBlock::Ubo,
|
||||
SemanticErrorBlame::Fragment,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(push) = vertex_push {
|
||||
Self::reflect_buffer_struct_members(
|
||||
&self.vertex,
|
||||
push,
|
||||
pass_number,
|
||||
semantics,
|
||||
&mut meta,
|
||||
UniformMemberBlock::PushConstant,
|
||||
SemanticErrorBlame::Vertex,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(push) = fragment_push {
|
||||
Self::reflect_buffer_struct_members(
|
||||
&self.fragment,
|
||||
push,
|
||||
pass_number,
|
||||
semantics,
|
||||
&mut meta,
|
||||
UniformMemberBlock::PushConstant,
|
||||
SemanticErrorBlame::Fragment,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut ubo_bindings = 0u16;
|
||||
if vertex_ubo.is_some() || fragment_ubo.is_some() {
|
||||
ubo_bindings = 1 << ubo.as_ref().expect("UBOs should be present").binding;
|
||||
}
|
||||
|
||||
let textures = self.fragment.global_variables.iter().filter(|(_, gv)| {
|
||||
let ty = &self.fragment.types[gv.ty];
|
||||
matches!(ty.inner, TypeInner::Image { .. })
|
||||
});
|
||||
|
||||
for (_, texture) in textures {
|
||||
let texture_data = self.reflect_texture(texture)?;
|
||||
if ubo_bindings & (1 << texture_data.binding) != 0 {
|
||||
return Err(ShaderReflectError::BindingInUse(texture_data.binding));
|
||||
}
|
||||
ubo_bindings |= 1 << texture_data.binding;
|
||||
|
||||
self.reflect_texture_metas(texture_data, pass_number, semantics, &mut meta)?;
|
||||
}
|
||||
|
||||
Ok(ShaderReflection {
|
||||
ubo,
|
||||
push_constant,
|
||||
meta,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
// #[test]
|
||||
// pub fn test_into() {
|
||||
// let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
// let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
|
||||
//
|
||||
// let mut loader = rspirv::dr::Loader::new();
|
||||
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
|
||||
// let module = loader.module();
|
||||
//
|
||||
// let outputs: Vec<&Instruction> = module
|
||||
// .types_global_values
|
||||
// .iter()
|
||||
// .filter(|i| i.class.opcode == Op::Variable)
|
||||
// .collect();
|
||||
//
|
||||
// println!("{outputs:#?}");
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -477,33 +477,32 @@ 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 {
|
||||
let hlsl_reflection = hlsl.reflect(index, semantics)?;
|
||||
let hlsl = hlsl.compile(Some(ShaderModel::V6_0))?;
|
||||
(dxil_reflection, graphics_pipeline)
|
||||
} else {
|
||||
let hlsl_reflection = hlsl.reflect(index, semantics)?;
|
||||
let hlsl = hlsl.compile(Some(ShaderModel::V6_0))?;
|
||||
|
||||
let graphics_pipeline = D3D12GraphicsPipeline::new_from_hlsl(
|
||||
device,
|
||||
library,
|
||||
compiler,
|
||||
&hlsl,
|
||||
root_signature,
|
||||
render_format,
|
||||
disable_cache
|
||||
)?;
|
||||
(hlsl_reflection, graphics_pipeline)
|
||||
};
|
||||
let graphics_pipeline = D3D12GraphicsPipeline::new_from_hlsl(
|
||||
device,
|
||||
library,
|
||||
compiler,
|
||||
&hlsl,
|
||||
root_signature,
|
||||
render_format,
|
||||
disable_cache,
|
||||
)?;
|
||||
(hlsl_reflection, graphics_pipeline)
|
||||
};
|
||||
|
||||
// minimum size here has to be 1 byte.
|
||||
let ubo_size = reflection.ubo.as_ref().map_or(1, |ubo| ubo.size as usize);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,9 +5,9 @@ use crate::hello_triangle::{DXSample, SampleCommandLine};
|
|||
#[test]
|
||||
fn triangle_d3d12() {
|
||||
let sample = hello_triangle::d3d12_hello_triangle::Sample::new(
|
||||
//"../test/shaders_slang/crt/crt-lottes.slangp",
|
||||
// "../test/shaders_slang/crt/crt-lottes.slangp",
|
||||
// "../test/basic.slangp",
|
||||
"../test/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",
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -186,9 +186,6 @@ impl Drop for RawVulkanBuffer {
|
|||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.buffer);
|
||||
if self.buffer.handle != vk::Buffer::null() {
|
||||
self.buffer.device.destroy_buffer(self.buffer.handle, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -23,7 +23,7 @@ fn triangle_vk() {
|
|||
disable_cache: false,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap();
|
||||
|
||||
hello_triangle::main(base, filter)
|
||||
}
|
||||
|
|
38
librashader-runtime-wgpu/Cargo.toml
Normal file
38
librashader-runtime-wgpu/Cargo.toml
Normal 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"
|
||||
|
52
librashader-runtime-wgpu/shader/blit.wgsl
Normal file
52
librashader-runtime-wgpu/shader/blit.wgsl
Normal 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);
|
||||
}
|
24
librashader-runtime-wgpu/shader/triangle.wgsl
Normal file
24
librashader-runtime-wgpu/shader/triangle.wgsl
Normal 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);
|
||||
}
|
51
librashader-runtime-wgpu/src/buffer.rs
Normal file
51
librashader-runtime-wgpu/src/buffer.rs
Normal 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()
|
||||
}
|
||||
}
|
51
librashader-runtime-wgpu/src/draw_quad.rs
Normal file
51
librashader-runtime-wgpu/src/draw_quad.rs
Normal 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)
|
||||
}
|
||||
}
|
24
librashader-runtime-wgpu/src/error.rs
Normal file
24
librashader-runtime-wgpu/src/error.rs
Normal 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>;
|
465
librashader-runtime-wgpu/src/filter_chain.rs
Normal file
465
librashader-runtime-wgpu/src/filter_chain.rs
Normal 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(())
|
||||
}
|
||||
}
|
247
librashader-runtime-wgpu/src/filter_pass.rs
Normal file
247
librashader-runtime-wgpu/src/filter_pass.rs
Normal 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
|
||||
}
|
||||
}
|
47
librashader-runtime-wgpu/src/framebuffer.rs
Normal file
47
librashader-runtime-wgpu/src/framebuffer.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
265
librashader-runtime-wgpu/src/graphics_pipeline.rs
Normal file
265
librashader-runtime-wgpu/src/graphics_pipeline.rs
Normal 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
|
||||
}
|
||||
}
|
17
librashader-runtime-wgpu/src/handle.rs
Normal file
17
librashader-runtime-wgpu/src/handle.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
28
librashader-runtime-wgpu/src/lib.rs
Normal file
28
librashader-runtime-wgpu/src/lib.rs
Normal 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;
|
87
librashader-runtime-wgpu/src/luts.rs
Normal file
87
librashader-runtime-wgpu/src/luts.rs
Normal 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)
|
||||
}
|
||||
}
|
119
librashader-runtime-wgpu/src/mipmap.rs
Normal file
119
librashader-runtime-wgpu/src/mipmap.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
20
librashader-runtime-wgpu/src/options.rs
Normal file
20
librashader-runtime-wgpu/src/options.rs
Normal 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,
|
||||
}
|
63
librashader-runtime-wgpu/src/samplers.rs
Normal file
63
librashader-runtime-wgpu/src/samplers.rs
Normal 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 }
|
||||
}
|
||||
}
|
152
librashader-runtime-wgpu/src/texture.rs
Normal file
152
librashader-runtime-wgpu/src/texture.rs
Normal 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))
|
||||
}
|
||||
}
|
15
librashader-runtime-wgpu/src/util.rs
Normal file
15
librashader-runtime-wgpu/src/util.rs
Normal 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
|
||||
}
|
357
librashader-runtime-wgpu/tests/hello_triangle.rs
Normal file
357
librashader-runtime-wgpu/tests/hello_triangle.rs
Normal 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();
|
||||
});
|
||||
}
|
6
librashader-runtime-wgpu/tests/triangle.rs
Normal file
6
librashader-runtime-wgpu/tests/triangle.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod hello_triangle;
|
||||
|
||||
#[test]
|
||||
fn triangle_wgpu() {
|
||||
hello_triangle::run()
|
||||
}
|
|
@ -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"
|
||||
|
|
87
librashader-runtime/src/array_chunks_mut.rs
Normal file
87
librashader-runtime/src/array_chunks_mut.rs
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
51
librashader.copr.spec
Normal 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/
|
|
@ -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" ]
|
||||
|
|
|
@ -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
10
pkg/librashader.pc
Normal 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
49
pkg/librashader.spec
Normal 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
33
pkg/obs/PKGBUILD
Normal 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
17
pkg/obs/_service
Normal 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
51
pkg/obs/librashader.spec
Normal 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
24
test/extreme_basic.glsl
Normal file
|
@ -0,0 +1,24 @@
|
|||
#version 450
|
||||
layout(set = 0, binding = 0, std140) uniform UBO
|
||||
{
|
||||
mat4 MVP;
|
||||
};
|
||||
|
||||
#pragma stage vertex
|
||||
layout(location = 0) in vec4 Position;
|
||||
layout(location = 1) in vec2 TexCoord;
|
||||
layout(location = 0) out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
gl_Position = MVP * Position;
|
||||
vTexCoord = TexCoord;
|
||||
}
|
||||
|
||||
#pragma stage fragment
|
||||
|
||||
layout(location = 0) out vec4 color;
|
||||
layout(binding = 1) uniform sampler2D tex;
|
||||
|
||||
void main() {
|
||||
color = texture(tex, vec2(0.0));
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue