Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion node-graph/gcore-shaders/src/blending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl AlphaBlending {
}

#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, bytemuck::NoUninit)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub enum BlendMode {
// Basic group
Expand Down
2 changes: 1 addition & 1 deletion node-graph/gcore/src/raster_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ mod gpu {

#[derive(Clone, Debug, PartialEq, Hash)]
pub struct GPU {
texture: wgpu::Texture,
pub texture: wgpu::Texture,
}

impl Sealed for Raster<GPU> {}
Expand Down
2 changes: 2 additions & 0 deletions node-graph/graster-nodes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ default = ["std"]
shader-nodes = [
"std",
"dep:graphene-raster-nodes-shaders",
"dep:wgpu-executor",
]
std = [
"dep:graphene-core",
Expand All @@ -39,6 +40,7 @@ node-macro = { workspace = true }
# Local std dependencies
dyn-any = { workspace = true, optional = true }
graphene-core = { workspace = true, optional = true }
wgpu-executor = { workspace = true, optional = true }
graphene-raster-nodes-shaders = { path = "./shaders", optional = true }

# Workspace dependencies
Expand Down
4 changes: 2 additions & 2 deletions node-graph/graster-nodes/src/adjustments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use num_traits::float::Float;
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6

#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)]
#[repr(u32)]
Expand Down Expand Up @@ -560,7 +560,7 @@ pub enum RedGreenBlue {
}

/// Color Channel
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
#[repr(u32)]
Expand Down
2 changes: 1 addition & 1 deletion node-graph/graster-nodes/src/blending_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
}
}

#[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))]
#[node_macro::node(category("Raster"), cfg(feature = "std"))]
fn blend<T: Blend<Color> + Send>(
_: impl Ctx,
#[implementations(
Expand Down
29 changes: 29 additions & 0 deletions node-graph/graster-nodes/src/fullscreen_vertex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use glam::{Vec2, Vec4};
use spirv_std::spirv;

/// webgpu NDC is like OpenGL: (-1.0 .. 1.0, -1.0 .. 1.0, 0.0 .. 1.0)
/// https://www.w3.org/TR/webgpu/#coordinate-systems
///
/// So to make a fullscreen triangle around a box at (-1..1):
///
/// ```norun
/// 3 +
/// |\
/// 2 | \
/// | \
/// 1 +-----+
/// | |\
/// 0 | 0 | \
/// | | \
/// -1 +-----+-----+
/// -1 0 1 2 3
/// ```
const FULLSCREEN_VERTICES: [Vec2; 3] = [Vec2::new(-1., -1.), Vec2::new(-1., 3.), Vec2::new(3., -1.)];

#[spirv(vertex)]
pub fn fullscreen_vertex(#[spirv(vertex_index)] vertex_index: u32, #[spirv(position)] gl_position: &mut Vec4) {
// broken on edition 2024 branch
// let vertex = unsafe { *FULLSCREEN_VERTICES.index_unchecked(vertex_index as usize) };
let vertex = FULLSCREEN_VERTICES[vertex_index as usize];
*gl_position = Vec4::from((vertex, 0., 1.));
}
5 changes: 5 additions & 0 deletions node-graph/graster-nodes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ pub mod adjust;
pub mod adjustments;
pub mod blending_nodes;
pub mod cubic_spline;
pub mod fullscreen_vertex;

/// required by shader macro
#[cfg(feature = "shader-nodes")]
pub use graphene_raster_nodes_shaders::WGSL_SHADER;

#[cfg(feature = "std")]
pub mod curve;
Expand Down
7 changes: 5 additions & 2 deletions node-graph/node-macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre

let cfg = crate::shader_nodes::modify_cfg(attributes);
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier, &cfg);
let shader_entry_point = attributes.shader_node.as_ref().map(|n| n.codegen_shader_entry_point(parsed)).unwrap_or(Ok(TokenStream2::new()))?;
let ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(parsed)).unwrap_or(Ok(ShaderTokens::default()))?;

Ok(quote! {
/// Underlying implementation for [#struct_name]
#[inline]
Expand Down Expand Up @@ -387,6 +388,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
}

#shader_entry_point

#gpu_node
})
}

Expand Down Expand Up @@ -589,7 +592,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
})
}

use crate::shader_nodes::CodegenShaderEntryPoint;
use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
use syn::visit_mut::VisitMut;
use syn::{GenericArgument, Lifetime, Type};

Expand Down
6 changes: 3 additions & 3 deletions node-graph/node-macro/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub(crate) struct ParsedNodeFn {
pub(crate) description: String,
}

#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub(crate) struct NodeFnAttributes {
pub(crate) category: Option<LitStr>,
pub(crate) display_name: Option<LitStr>,
Expand Down Expand Up @@ -144,7 +144,7 @@ pub struct NodeParsedField {
pub implementations: Punctuated<Implementation, Comma>,
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub(crate) struct Input {
pub(crate) pat_ident: PatIdent,
pub(crate) ty: Type,
Expand Down Expand Up @@ -663,7 +663,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
}

impl ParsedNodeFn {
fn replace_impl_trait_in_input(&mut self) {
pub fn replace_impl_trait_in_input(&mut self) {
if let Type::ImplTrait(impl_trait) = self.input.ty.clone() {
let ident = Ident::new("_Input", impl_trait.span());
let mut bounds = impl_trait.bounds;
Expand Down
57 changes: 42 additions & 15 deletions node-graph/node-macro/src/shader_nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,76 @@ use crate::shader_nodes::per_pixel_adjust::PerPixelAdjust;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use strum::VariantNames;
use syn::Error;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Error, Token};

pub mod per_pixel_adjust;

pub const STD_FEATURE_GATE: &str = "std";
pub const SHADER_NODES_FEATURE_GATE: &str = "shader-nodes";

pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream {
match (&attributes.cfg, &attributes.shader_node) {
(Some(cfg), Some(_)) => quote!(#[cfg(all(#cfg, feature = #STD_FEATURE_GATE))]),
(Some(cfg), None) => quote!(#[cfg(#cfg)]),
(None, Some(_)) => quote!(#[cfg(feature = #STD_FEATURE_GATE)]),
(None, None) => quote!(),
}
let feature_gate = match &attributes.shader_node {
// shader node cfg is done on the mod
Some(ShaderNodeType::ShaderNode) => quote!(),
Some(_) => quote!(feature = #STD_FEATURE_GATE),
None => quote!(),
};
let cfgs: Punctuated<_, Token![,]> = match &attributes.cfg {
None => [&feature_gate].into_iter().collect(),
Some(cfg) => [cfg, &feature_gate].into_iter().collect(),
};
quote!(#[cfg(all(#cfgs))])
}

#[derive(Debug, VariantNames)]
#[derive(Debug, Clone, VariantNames)]
pub(crate) enum ShaderNodeType {
/// Marker for this node being in a gpu node crate, but not having a gpu implementation. This is distinct from not
/// declaring `shader_node` at all, as it will wrap the CPU node with a `#[cfg(feature = "std")]` feature gate.
None,
/// Marker for this node being a generated gpu node implementation, that should not emit anything to prevent
/// recursively generating more gpu nodes. But it still counts as a gpu node and will get the
/// `#[cfg(feature = "std")]` feature gate around it's impl.
ShaderNode,
PerPixelAdjust(PerPixelAdjust),
}

impl Parse for ShaderNodeType {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
Ok(match ident.to_string().as_str() {
"None" => ShaderNodeType::None,
"PerPixelAdjust" => ShaderNodeType::PerPixelAdjust(PerPixelAdjust::parse(input)?),
_ => return Err(Error::new_spanned(&ident, format!("attr 'shader_node' must be one of {:?}", Self::VARIANTS))),
})
}
}

pub trait CodegenShaderEntryPoint {
fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result<TokenStream>;
pub trait ShaderCodegen {
fn codegen(&self, parsed: &ParsedNodeFn) -> syn::Result<ShaderTokens>;
}

impl CodegenShaderEntryPoint for ShaderNodeType {
fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result<TokenStream> {
if parsed.is_async {
return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async"));
impl ShaderCodegen for ShaderNodeType {
fn codegen(&self, parsed: &ParsedNodeFn) -> syn::Result<ShaderTokens> {
match self {
ShaderNodeType::None | ShaderNodeType::ShaderNode => (),
_ => {
if parsed.is_async {
return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async"));
}
}
}

match self {
ShaderNodeType::PerPixelAdjust(x) => x.codegen_shader_entry_point(parsed),
ShaderNodeType::None | ShaderNodeType::ShaderNode => Ok(ShaderTokens::default()),
ShaderNodeType::PerPixelAdjust(x) => x.codegen(parsed),
}
}
}

#[derive(Clone, Default)]
pub struct ShaderTokens {
pub shader_entry_point: TokenStream,
pub gpu_node: TokenStream,
}
Loading
Loading