mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Shaders: add BufferStruct to support bool and enums (#3109)
* node-macro: modernize `node` macro * node-macro: add `CrateIdent` struct containing resolved crate paths * shaders: add trait `BufferStruct` and derive macro * shaders: `gamma_correction` and `channel_mixer` gpu nodes * shaders: `selective_color` gpu node * shaders: `brightness_contrast_classic` gpu node * shaders: append GPU to display name * node-macro: fixup doc links * shaders: consistently append " GPU" to all shader node names
This commit is contained in:
@@ -22,10 +22,14 @@ std = [
|
||||
"glam/serde",
|
||||
"half/std",
|
||||
"half/serde",
|
||||
"num-traits/std"
|
||||
"num-traits/std",
|
||||
"num_enum/std",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Local std dependencies
|
||||
dyn-any = { workspace = true, optional = true }
|
||||
|
||||
@@ -35,6 +39,8 @@ glam = { workspace = true }
|
||||
half = { workspace = true, default-features = false }
|
||||
num-derive = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
num_enum = { workspace = true }
|
||||
spirv-std = { workspace = true }
|
||||
|
||||
# Workspace std dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use core::fmt::Display;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use node_macro::BufferStruct;
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num_traits::float::Float;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", serde(default))]
|
||||
pub struct AlphaBlending {
|
||||
@@ -66,7 +68,7 @@ impl AlphaBlending {
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, bytemuck::NoUninit)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
pub enum BlendMode {
|
||||
// Basic group
|
||||
|
||||
@@ -5,6 +5,7 @@ use core::fmt::Debug;
|
||||
use core::hash::Hash;
|
||||
use glam::Vec4;
|
||||
use half::f16;
|
||||
use node_macro::BufferStruct;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num_traits::Euclid;
|
||||
#[cfg(not(feature = "std"))]
|
||||
@@ -215,7 +216,7 @@ impl Pixel for Luma {}
|
||||
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
|
||||
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable, BufferStruct)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod choice_type;
|
||||
pub mod color;
|
||||
pub mod context;
|
||||
pub mod registry;
|
||||
pub mod shaders;
|
||||
|
||||
pub use context::Ctx;
|
||||
pub use glam;
|
||||
|
||||
114
node-graph/gcore-shaders/src/shaders/buffer_struct/glam.rs
Normal file
114
node-graph/gcore-shaders/src/shaders/buffer_struct/glam.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use crate::shaders::buffer_struct::BufferStruct;
|
||||
|
||||
macro_rules! glam_array {
|
||||
($t:ty, $a:ty) => {
|
||||
unsafe impl BufferStruct for $t {
|
||||
type Buffer = $a;
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
<$t>::to_array(&from)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
<$t>::from_array(from)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! glam_cols_array {
|
||||
($t:ty, $a:ty) => {
|
||||
unsafe impl BufferStruct for $t {
|
||||
type Buffer = $a;
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
<$t>::to_cols_array(&from)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
<$t>::from_cols_array(&from)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
glam_array!(glam::Vec2, [f32; 2]);
|
||||
glam_array!(glam::Vec3, [f32; 3]);
|
||||
// glam_array!(Vec3A, [f32; 4]);
|
||||
glam_array!(glam::Vec4, [f32; 4]);
|
||||
glam_array!(glam::Quat, [f32; 4]);
|
||||
glam_cols_array!(glam::Mat2, [f32; 4]);
|
||||
glam_cols_array!(glam::Mat3, [f32; 9]);
|
||||
// glam_cols_array!(Mat3A, [f32; 4]);
|
||||
glam_cols_array!(glam::Mat4, [f32; 16]);
|
||||
glam_cols_array!(glam::Affine2, [f32; 6]);
|
||||
glam_cols_array!(glam::Affine3A, [f32; 12]);
|
||||
|
||||
glam_array!(glam::DVec2, [f64; 2]);
|
||||
glam_array!(glam::DVec3, [f64; 3]);
|
||||
glam_array!(glam::DVec4, [f64; 4]);
|
||||
glam_array!(glam::DQuat, [f64; 4]);
|
||||
glam_cols_array!(glam::DMat2, [f64; 4]);
|
||||
glam_cols_array!(glam::DMat3, [f64; 9]);
|
||||
glam_cols_array!(glam::DMat4, [f64; 16]);
|
||||
glam_cols_array!(glam::DAffine2, [f64; 6]);
|
||||
glam_cols_array!(glam::DAffine3, [f64; 12]);
|
||||
|
||||
glam_array!(glam::I16Vec2, [i16; 2]);
|
||||
glam_array!(glam::I16Vec3, [i16; 3]);
|
||||
glam_array!(glam::I16Vec4, [i16; 4]);
|
||||
|
||||
glam_array!(glam::U16Vec2, [u16; 2]);
|
||||
glam_array!(glam::U16Vec3, [u16; 3]);
|
||||
glam_array!(glam::U16Vec4, [u16; 4]);
|
||||
|
||||
glam_array!(glam::IVec2, [i32; 2]);
|
||||
glam_array!(glam::IVec3, [i32; 3]);
|
||||
glam_array!(glam::IVec4, [i32; 4]);
|
||||
|
||||
glam_array!(glam::UVec2, [u32; 2]);
|
||||
glam_array!(glam::UVec3, [u32; 3]);
|
||||
glam_array!(glam::UVec4, [u32; 4]);
|
||||
|
||||
glam_array!(glam::I64Vec2, [i64; 2]);
|
||||
glam_array!(glam::I64Vec3, [i64; 3]);
|
||||
glam_array!(glam::I64Vec4, [i64; 4]);
|
||||
|
||||
glam_array!(glam::U64Vec2, [u64; 2]);
|
||||
glam_array!(glam::U64Vec3, [u64; 3]);
|
||||
glam_array!(glam::U64Vec4, [u64; 4]);
|
||||
|
||||
unsafe impl BufferStruct for glam::Vec3A {
|
||||
type Buffer = [f32; 4];
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
glam::Vec4::to_array(&from.extend(0.))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
glam::Vec3A::from_vec4(glam::Vec4::from_array(from))
|
||||
}
|
||||
}
|
||||
|
||||
/// do NOT use slices, otherwise spirv will fail to compile
|
||||
unsafe impl BufferStruct for glam::Mat3A {
|
||||
type Buffer = [f32; 12];
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
let a = from.to_cols_array();
|
||||
[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], 0., 0., 0.]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
let a = from;
|
||||
glam::Mat3A::from_cols_array(&[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]])
|
||||
}
|
||||
}
|
||||
63
node-graph/gcore-shaders/src/shaders/buffer_struct/mod.rs
Normal file
63
node-graph/gcore-shaders/src/shaders/buffer_struct/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! I (@firestar99) copied this entire mod from one of my projects, as I haven't uploaded that lib to crates. Hopefully
|
||||
//! rust-gpu improves and this entire thing becomes unnecessary in the future.
|
||||
//!
|
||||
//! https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content
|
||||
|
||||
use bytemuck::Pod;
|
||||
|
||||
mod glam;
|
||||
mod primitive;
|
||||
|
||||
/// A BufferStruct is a "parallel representation" of the original struct with some fundamental types remapped. This
|
||||
/// struct hierarchy represents how data is stored in GPU Buffers, where all types must be [`Pod`] to allow
|
||||
/// transmuting them to `&[u8]` with [`bytemuck`].
|
||||
///
|
||||
/// Notable type remappings (original: buffer):
|
||||
/// * bool: u32 of 0 or 1
|
||||
/// * any repr(u32) enum: u32 with remapping via [`num_enum`]
|
||||
///
|
||||
/// By adding `#[derive(ShaderStruct)]` to your struct (or enum), a parallel `{name}Buffer` struct is created with all
|
||||
/// the members of the original struct, but with their types using the associated remapped types as specified by this
|
||||
/// trait.
|
||||
///
|
||||
/// # Origin
|
||||
/// I (@firestar99) copied this entire mod from my [Nanite-at-home] project, specifically the [buffer-content] crate
|
||||
/// and the [buffer_struct] proc macro. The variant here has quite some modifications, to both cleaned up some of the
|
||||
/// mistakes my implementation has and to customize it a bit for graphite.
|
||||
///
|
||||
/// Hopefully rust-gpu improves to the point where this remapping becomes unnecessary.
|
||||
///
|
||||
/// [Nanite-at-home]: https://github.com/Firestar99/nanite-at-home
|
||||
/// [buffer-content]: https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content
|
||||
/// [buffer_struct]: https://github.com/Firestar99/nanite-at-home/blob/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/macros/src/buffer_struct.rs
|
||||
///
|
||||
/// # Safety
|
||||
/// The associated type Transfer must be the same on all targets. Writing followed by reading back a value must result
|
||||
/// in the same value.
|
||||
pub unsafe trait BufferStruct: Copy + Send + Sync + 'static {
|
||||
type Buffer: Pod + Send + Sync;
|
||||
|
||||
fn write(from: Self) -> Self::Buffer;
|
||||
|
||||
fn read(from: Self::Buffer) -> Self;
|
||||
}
|
||||
|
||||
/// Trait marking all [`BufferStruct`] whose read and write methods are identity. While [`BufferStruct`] only
|
||||
/// requires `t == read(write(t))`, this trait additionally requires `t == read(t) == write(t)`. As this removes the
|
||||
/// conversion requirement for writing to or reading from a buffer, one can acquire slices from buffers created of these
|
||||
/// types.
|
||||
///
|
||||
/// Implementing this type is completely safe due to the [`Pod`] requirement.
|
||||
pub trait BufferStructIdentity: Pod + Send + Sync {}
|
||||
|
||||
unsafe impl<T: BufferStructIdentity> BufferStruct for T {
|
||||
type Buffer = Self;
|
||||
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
from
|
||||
}
|
||||
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
from
|
||||
}
|
||||
}
|
||||
135
node-graph/gcore-shaders/src/shaders/buffer_struct/primitive.rs
Normal file
135
node-graph/gcore-shaders/src/shaders/buffer_struct/primitive.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use crate::shaders::buffer_struct::{BufferStruct, BufferStructIdentity};
|
||||
use bytemuck::Pod;
|
||||
use core::marker::PhantomData;
|
||||
use core::num::Wrapping;
|
||||
use spirv_std::arch::IndexUnchecked;
|
||||
|
||||
macro_rules! identity {
|
||||
($t:ty) => {
|
||||
impl BufferStructIdentity for $t {}
|
||||
};
|
||||
}
|
||||
|
||||
identity!(());
|
||||
identity!(u8);
|
||||
identity!(u16);
|
||||
identity!(u32);
|
||||
identity!(u64);
|
||||
identity!(u128);
|
||||
identity!(usize);
|
||||
identity!(i8);
|
||||
identity!(i16);
|
||||
identity!(i32);
|
||||
identity!(i64);
|
||||
identity!(i128);
|
||||
identity!(isize);
|
||||
identity!(f32);
|
||||
identity!(f64);
|
||||
|
||||
identity!(spirv_std::arch::SubgroupMask);
|
||||
identity!(spirv_std::memory::Semantics);
|
||||
identity!(spirv_std::ray_tracing::RayFlags);
|
||||
identity!(spirv_std::indirect_command::DrawIndirectCommand);
|
||||
identity!(spirv_std::indirect_command::DrawIndexedIndirectCommand);
|
||||
identity!(spirv_std::indirect_command::DispatchIndirectCommand);
|
||||
identity!(spirv_std::indirect_command::DrawMeshTasksIndirectCommandEXT);
|
||||
identity!(spirv_std::indirect_command::TraceRaysIndirectCommandKHR);
|
||||
// not pod
|
||||
// identity!(spirv_std::indirect_command::TraceRaysIndirectCommand2KHR);
|
||||
|
||||
unsafe impl BufferStruct for bool {
|
||||
type Buffer = u32;
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
from as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
from != 0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: BufferStruct> BufferStruct for Wrapping<T>
|
||||
where
|
||||
// unfortunately has to be Pod, even though AnyBitPattern would be sufficient,
|
||||
// due to bytemuck doing `impl<T: Pod> AnyBitPattern for T {}`
|
||||
// see https://github.com/Lokathor/bytemuck/issues/164
|
||||
T::Buffer: Pod,
|
||||
{
|
||||
type Buffer = Wrapping<T::Buffer>;
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
Wrapping(T::write(from.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
Wrapping(T::read(from.0))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: BufferStruct + 'static> BufferStruct for PhantomData<T> {
|
||||
type Buffer = PhantomData<T>;
|
||||
|
||||
#[inline]
|
||||
fn write(_: Self) -> Self::Buffer {
|
||||
PhantomData {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(_: Self::Buffer) -> Self {
|
||||
PhantomData {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Potential problem: you can't impl this for an array of BufferStruct, as it'll conflict with this impl due to the
|
||||
/// blanket impl on all BufferStructPlain types.
|
||||
unsafe impl<T: BufferStruct, const N: usize> BufferStruct for [T; N]
|
||||
where
|
||||
// rust-gpu does not like `[T; N].map()` nor `core::array::from_fn()` nor transmuting arrays with a const generic
|
||||
// length, so for now we need to require T: Default and T::Transfer: Default for all arrays.
|
||||
T: Default,
|
||||
// unfortunately has to be Pod, even though AnyBitPattern would be sufficient,
|
||||
// due to bytemuck doing `impl<T: Pod> AnyBitPattern for T {}`
|
||||
// see https://github.com/Lokathor/bytemuck/issues/164
|
||||
T::Buffer: Pod + Default,
|
||||
{
|
||||
type Buffer = [T::Buffer; N];
|
||||
|
||||
#[inline]
|
||||
fn write(from: Self) -> Self::Buffer {
|
||||
unsafe {
|
||||
let mut ret = [T::Buffer::default(); N];
|
||||
for i in 0..N {
|
||||
*ret.index_unchecked_mut(i) = T::write(*from.index_unchecked(i));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read(from: Self::Buffer) -> Self {
|
||||
unsafe {
|
||||
let mut ret = [T::default(); N];
|
||||
for i in 0..N {
|
||||
*ret.index_unchecked_mut(i) = T::read(*from.index_unchecked(i));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_bool() {
|
||||
for x in [false, true] {
|
||||
assert_eq!(x, <bool as BufferStruct>::read(<bool as BufferStruct>::write(x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
10
node-graph/gcore-shaders/src/shaders/mod.rs
Normal file
10
node-graph/gcore-shaders/src/shaders/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! supporting infrastructure for shaders
|
||||
|
||||
pub mod buffer_struct;
|
||||
|
||||
pub mod __private {
|
||||
pub use bytemuck;
|
||||
pub use glam;
|
||||
pub use num_enum;
|
||||
pub use spirv_std;
|
||||
}
|
||||
Reference in New Issue
Block a user