From 57415b948bbeadb08fc955256e7571267307fa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20Ic=C4=83?= Date: Sun, 28 May 2023 11:34:09 +0300 Subject: [PATCH] Add the first basic version of the GPU blend node (#1243) * Implement Gpu Blend node * Remove duplicate shader input * Fix formatting --------- Co-authored-by: Dennis Kobert --- Cargo.lock | 1 + .../document_node_types.rs | 14 ++ node-graph/compilation-client/Cargo.toml | 1 + node-graph/compilation-client/src/main.rs | 77 ++++--- node-graph/gcore/src/raster/adjustments.rs | 45 +++- node-graph/gcore/src/types.rs | 13 ++ .../gpu-compiler-bin-wrapper/src/lib.rs | 2 +- node-graph/gpu-compiler/src/lib.rs | 2 +- .../src/templates/spirv-template.rs | 7 +- node-graph/gpu-executor/src/lib.rs | 32 ++- node-graph/graph-craft/src/document/value.rs | 9 +- node-graph/gstd/src/executor.rs | 202 +++++++++++++++++- .../interpreted-executor/src/node_registry.rs | 24 ++- node-graph/wgpu-executor/src/lib.rs | 7 +- 14 files changed, 369 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e609439e1..61b613a82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,6 +651,7 @@ dependencies = [ "gpu-compiler-bin-wrapper", "gpu-executor", "graph-craft", + "graphene-core", "reqwest", "serde_json", "tempfile", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index e16cbc133..6f4f8ec13 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -723,6 +723,20 @@ fn static_nodes() -> Vec { outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::no_properties, }, + #[cfg(feature = "gpu")] + DocumentNodeType { + name: "Blend (GPU)", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_std::executor::BlendGpuImageNode<_, _, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType::value("Second", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::Normal), false), + DocumentInputType::value("Opacity", TaggedValue::F32(100.0), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::blend_properties, + }, DocumentNodeType { name: "Extract", category: "Macros", diff --git a/node-graph/compilation-client/Cargo.toml b/node-graph/compilation-client/Cargo.toml index d8ed96823..73fa717a9 100644 --- a/node-graph/compilation-client/Cargo.toml +++ b/node-graph/compilation-client/Cargo.toml @@ -12,6 +12,7 @@ serde_json = "1.0" graph-craft = { version = "0.1.0", path = "../graph-craft", features = [ "serde", ] } +graphene-core = { version = "0.1.0", path = "../gcore" } gpu-executor = { version = "0.1.0", path = "../gpu-executor" } gpu-compiler-bin-wrapper = { version = "0.1.0", path = "../gpu-compiler/gpu-compiler-bin-wrapper" } tempfile = "3.3.0" diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index d5e6c9c81..8fa7c0d7c 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -1,8 +1,11 @@ use gpu_compiler_bin_wrapper::CompileRequest; use gpu_executor::{ShaderIO, ShaderInput}; use graph_craft::concrete; +use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graph_craft::*; +use graphene_core::raster::adjustments::{BlendMode, BlendNode}; +use graphene_core::Color; use std::borrow::Cow; use std::time::Duration; @@ -10,33 +13,21 @@ use std::time::Duration; fn main() { let client = reqwest::blocking::Client::new(); - // let network = NodeNetwork { - // inputs: vec![0], - // outputs: vec![NodeOutput::new(0, 0)], - // disabled: vec![], - // previous_outputs: None, - // nodes: [( - // 0, - // DocumentNode { - // name: "Inc".into(), - // inputs: vec![NodeInput::Network(concrete!(u32))], - // implementation: DocumentNodeImplementation::Network(add_network()), - // metadata: DocumentNodeMetadata::default(), - // }, - // )] - // .into_iter() - // .collect(), - // }; let network = add_network(); let compiler = graph_craft::executor::Compiler {}; let proto_network = compiler.compile_single(network, true).unwrap(); let io = ShaderIO { - inputs: vec![ShaderInput::StorageBuffer((), concrete!(u32))], - output: ShaderInput::OutputBuffer((), concrete!(&mut [u32])), + inputs: vec![ + ShaderInput::StorageBuffer((), concrete!(Color)), // background image + ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image + ShaderInput::StorageBuffer((), concrete!(u32)), // width/height of the foreground image + ShaderInput::OutputBuffer((), concrete!(Color)), + ], + output: ShaderInput::OutputBuffer((), concrete!(Color)), }; - let compile_request = CompileRequest::new(vec![proto_network], vec![concrete!(u32)], vec![concrete!(u32)], io); + let compile_request = CompileRequest::new(vec![proto_network], vec![concrete!(Color), concrete!(Color), concrete!(u32)], vec![concrete!(Color)], io); let response = client .post("http://localhost:3000/compile/spirv") .timeout(Duration::from_secs(30)) @@ -52,27 +43,33 @@ fn add_network() -> NodeNetwork { outputs: vec![NodeOutput::new(0, 0)], disabled: vec![], previous_outputs: None, - nodes: [ - ( - 0, - DocumentNode { - name: "Dup".into(), - inputs: vec![NodeInput::value(value::TaggedValue::U32(5u32), false)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), - ..Default::default() - }, - ), - // ( - // 1, - // DocumentNode { - // name: "Add".into(), - // inputs: vec![NodeInput::node(0, 0)], - // metadata: DocumentNodeMetadata::default(), - // implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode")), - // }, - // ), - ] + nodes: [DocumentNode { + name: "Blend Image".into(), + inputs: vec![NodeInput::Inline(InlineRust::new( + format!( + r#"graphene_core::raster::adjustments::BlendNode::new( + graphene_core::value::CopiedNode::new({}), + graphene_core::value::CopiedNode::new({}), + ).eval(( + i1[_global_index.x as usize], + if _global_index.x < i2[2] {{ + i0[_global_index.x as usize] + }} else {{ + Color::from_rgbaf32_unchecked(0.0, 0.0, 0.0, 0.0) + }}, + ))"#, + TaggedValue::BlendMode(BlendMode::Normal).to_primitive_string(), + TaggedValue::F32(1.0).to_primitive_string(), + ), + concrete![Color], + ))], + implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()), + ..Default::default() + }] .into_iter() + .enumerate() + .map(|(i, n)| (i as u64, n)) .collect(), + ..Default::default() } } diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 8c5457d26..ffe2cb7cb 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -84,6 +84,7 @@ impl BlendMode { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "std", derive(specta::Type))] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)] +#[repr(i32)] // TODO: Enable Int8 capability for SPRIV so that we don't need this? pub enum BlendMode { #[default] // Basic group @@ -173,6 +174,46 @@ impl core::fmt::Display for BlendMode { } } +pub fn to_primtive_string(blend_mode: &BlendMode) -> &'static str { + match blend_mode { + BlendMode::Normal => "Normal", + + BlendMode::Multiply => "Multiply", + BlendMode::Darken => "Darken", + BlendMode::ColorBurn => "ColorBurn", + BlendMode::LinearBurn => "LinearBurn", + BlendMode::DarkerColor => "DarkerColor", + + BlendMode::Screen => "Screen", + BlendMode::Lighten => "Lighten", + BlendMode::ColorDodge => "ColorDodge", + BlendMode::LinearDodge => "LinearDodge", + BlendMode::LighterColor => "LighterColor", + + BlendMode::Overlay => "Overlay", + BlendMode::SoftLight => "SoftLight", + BlendMode::HardLight => "HardLight", + BlendMode::VividLight => "VividLight", + BlendMode::LinearLight => "LinearLight", + BlendMode::PinLight => "PinLight", + BlendMode::HardMix => "HardMix", + + BlendMode::Difference => "Difference", + BlendMode::Exclusion => "Exclusion", + BlendMode::Subtract => "Subtract", + BlendMode::Divide => "Divide", + + BlendMode::Hue => "Hue", + BlendMode::Saturation => "Saturation", + BlendMode::Color => "Color", + BlendMode::Luminosity => "Luminosity", + + BlendMode::InsertRed => "InsertRed", + BlendMode::InsertGreen => "InsertGreen", + BlendMode::InsertBlue => "InsertBlue", + } +} + #[derive(Debug, Clone, Copy, Default)] pub struct LuminanceNode { luminance_calc: LuminanceCalculation, @@ -410,7 +451,7 @@ pub struct BlendNode { } #[node_macro::node_fn(BlendNode)] -fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Color { +fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f32) -> Color { let opacity = opacity / 100.; let (foreground, background) = input; @@ -452,7 +493,7 @@ fn blend_node(input: (Color, Color), blend_mode: BlendMode, opacity: f64) -> Col BlendMode::InsertBlue => foreground.with_blue(background.b()), }; - background.alpha_blend(target_color.to_associated_alpha(opacity as f32)) + background.alpha_blend(target_color.to_associated_alpha(opacity)) } #[derive(Debug, Clone, Copy)] diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 1b6902ad1..a29e8f354 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -34,6 +34,19 @@ macro_rules! concrete { }) }; } + +#[macro_export] +macro_rules! concrete_with_name { + ($type:ty, $name:expr) => { + Type::Concrete(TypeDescriptor { + id: Some(core::any::TypeId::of::<$type>()), + name: Cow::Borrowed($name), + size: core::mem::size_of::<$type>(), + align: core::mem::align_of::<$type>(), + }) + }; +} + #[macro_export] macro_rules! generic { ($type:ty) => {{ diff --git a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs index 7690a0829..88e0910ab 100644 --- a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs +++ b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs @@ -17,7 +17,7 @@ pub fn compile_spirv(request: &CompileRequest, compile_dir: Option<&str>, manife println!("calling cargo run!"); let non_cargo_env_vars = std::env::vars().filter(|(k, _)| k.starts_with("PATH")).collect::>(); - let mut cargo_command = std::process::Command::new("/usr/bin/cargo") + let mut cargo_command = std::process::Command::new("cargo") .arg("run") .arg("--release") .arg("--manifest-path") diff --git a/node-graph/gpu-compiler/src/lib.rs b/node-graph/gpu-compiler/src/lib.rs index aa00b4ae6..bf6617a8d 100644 --- a/node-graph/gpu-compiler/src/lib.rs +++ b/node-graph/gpu-compiler/src/lib.rs @@ -71,7 +71,7 @@ pub fn construct_argument(input: &ShaderInput<()>, position: u32, binding_offset let line = match input { ShaderInput::Constant(constant) => format!("#[spirv({})] i{}: {}", constant_attribute(constant), position, constant.ty()), ShaderInput::UniformBuffer(_, ty) => { - format!("#[spirv(uniform, descriptor_set = 0, binding = {})] i{}: &[{}]", position + binding_offset, position, ty,) + format!("#[spirv(uniform, descriptor_set = 0, binding = {})] i{}: &{}", position + binding_offset, position, ty,) } ShaderInput::StorageBuffer(_, ty) | ShaderInput::ReadBackBuffer(_, ty) => { format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] i{}: &[{}]", position + binding_offset, position, ty,) diff --git a/node-graph/gpu-compiler/src/templates/spirv-template.rs b/node-graph/gpu-compiler/src/templates/spirv-template.rs index 006327b45..4562a3252 100644 --- a/node-graph/gpu-compiler/src/templates/spirv-template.rs +++ b/node-graph/gpu-compiler/src/templates/spirv-template.rs @@ -8,7 +8,8 @@ extern crate spirv_std; //pub mod gpu { //use super::*; use spirv_std::spirv; - use spirv_std::glam::UVec3; + use spirv_std::glam; + use spirv_std::glam::{UVec3, Vec2, Mat2, BVec2}; #[allow(unused)] #[spirv(compute(threads({{compute_threads}})))] @@ -19,6 +20,8 @@ extern crate spirv_std; {% endfor %} ) { use graphene_core::Node; + use graphene_core::raster::adjustments::{BlendMode, BlendNode}; + use graphene_core::Color; /* {% for input in input_nodes %} @@ -34,7 +37,7 @@ extern crate spirv_std; {% for output in output_nodes %} let v = {{output}}.eval(()); - o{{loop.index0}}[_global_index.x as usize] = v; + o{{loop.index0}}[(_global_index.y * i0 + _global_index.x) as usize] = v; {% endfor %} // TODO: Write output to buffer } diff --git a/node-graph/gpu-executor/src/lib.rs b/node-graph/gpu-executor/src/lib.rs index 8ec9d65df..9d40673cd 100644 --- a/node-graph/gpu-executor/src/lib.rs +++ b/node-graph/gpu-executor/src/lib.rs @@ -13,6 +13,22 @@ use std::sync::Arc; type ReadBackFuture = Pin>>>>; +pub enum ComputePassDimensions { + X(u32), + XY(u32, u32), + XYZ(u32, u32, u32), +} + +impl ComputePassDimensions { + pub fn get(&self) -> (u32, u32, u32) { + match self { + ComputePassDimensions::X(x) => (*x, 1, 1), + ComputePassDimensions::XY(x, y) => (*x, *y, 1), + ComputePassDimensions::XYZ(x, y, z) => (*x, *y, *z), + } + } +} + pub trait GpuExecutor { type ShaderHandle; type BufferHandle; @@ -22,7 +38,7 @@ pub trait GpuExecutor { fn create_uniform_buffer(&self, data: T) -> Result>; fn create_storage_buffer(&self, data: T, options: StorageBufferOptions) -> Result>; fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result>; - fn create_compute_pass(&self, layout: &PipelineLayout, read_back: Option>>, instances: u32) -> Result; + fn create_compute_pass(&self, layout: &PipelineLayout, read_back: Option>>, instances: ComputePassDimensions) -> Result; fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>; fn read_output_buffer(&self, buffer: Arc>) -> ReadBackFuture; } @@ -129,10 +145,15 @@ pub struct StorageBufferOptions { } pub trait ToUniformBuffer: StaticType { - type UniformBufferHandle; fn to_bytes(&self) -> Cow<[u8]>; } +impl ToUniformBuffer for T { + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(bytemuck::bytes_of(self).into()) + } +} + pub trait ToStorageBuffer: StaticType { fn to_bytes(&self) -> Cow<[u8]>; fn ty(&self) -> Type; @@ -233,7 +254,12 @@ pub struct CreateComputePassNode { } #[node_macro::node_fn(CreateComputePassNode)] -fn create_compute_pass_node(layout: PipelineLayout, executor: &'input E, output: ShaderInput, instances: u32) -> E::CommandBuffer { +fn create_compute_pass_node<'any_input, E: 'any_input + GpuExecutor>( + layout: PipelineLayout, + executor: &'any_input E, + output: ShaderInput, + instances: ComputePassDimensions, +) -> E::CommandBuffer { executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index b9808886e..2a9638617 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -3,7 +3,7 @@ use crate::executor::Any; pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; use crate::proto::{Any as DAny, FutureAny}; -use graphene_core::raster::{BlendMode, LuminanceCalculation}; +use graphene_core::raster::{to_primtive_string, BlendMode, LuminanceCalculation}; use graphene_core::{Color, Node, Type}; pub use dyn_any::StaticType; @@ -185,10 +185,11 @@ impl<'a> TaggedValue { match self { TaggedValue::None => "()".to_string(), TaggedValue::String(x) => format!("\"{}\"", x), - TaggedValue::U32(x) => x.to_string(), - TaggedValue::F32(x) => x.to_string(), - TaggedValue::F64(x) => x.to_string(), + TaggedValue::U32(x) => x.to_string() + "_u32", + TaggedValue::F32(x) => x.to_string() + "_f32", + TaggedValue::F64(x) => x.to_string() + "_f64", TaggedValue::Bool(x) => x.to_string(), + TaggedValue::BlendMode(blend_mode) => "BlendMode::".to_string() + to_primtive_string(blend_mode), _ => panic!("Cannot convert to primitive string"), } } diff --git a/node-graph/gstd/src/executor.rs b/node-graph/gstd/src/executor.rs index 4744dacc2..4c1e3a561 100644 --- a/node-graph/gstd/src/executor.rs +++ b/node-graph/gstd/src/executor.rs @@ -1,11 +1,11 @@ -use glam::UVec3; -use gpu_executor::{Bindgroup, PipelineLayout, StorageBufferOptions}; +use glam::{DAffine2, DMat2, DVec2, Mat2, UVec3, Vec2}; +use gpu_executor::{Bindgroup, ComputePassDimensions, PipelineLayout, StorageBufferOptions}; use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput}; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graph_craft::proto::*; +use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::*; -use graphene_core::value::ValueNode; use graphene_core::*; use wgpu_executor::NewExecutor; @@ -58,7 +58,7 @@ async fn map_gpu(image: ImageFrame, node: DocumentNode) -> ImageFrame, node: DocumentNode) -> ImageFrame, node: DocumentNode) -> ImageFrame, node: DocumentNode) -> ImageFrame, node: DocumentNode) -> ImageFrame, node: DocumentNode) -> ImageFrame, node: String) -> Image { Image { data, ..input } } */ + +#[derive(Debug, Clone, Copy)] +pub struct BlendGpuImageNode { + background: Background, + blend_mode: B, + opacity: O, +} + +#[node_macro::node_fn(BlendGpuImageNode)] +async fn blend_gpu_image(foreground: ImageFrame, background: ImageFrame, blend_mode: BlendMode, opacity: f32) -> ImageFrame { + let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64); + let background_size = DVec2::new(background.image.width as f64, background.image.height as f64); + // Transforms a point from the background image to the forground image + let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); + + let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2(); + let translation: Vec2 = bg_to_fg.translation.as_vec2(); + + log::debug!("Executing gpu blend node!"); + let compiler = graph_craft::executor::Compiler {}; + + let network = NodeNetwork { + inputs: vec![], + outputs: vec![NodeOutput::new(0, 0)], + nodes: [DocumentNode { + name: "BlendOp".into(), + inputs: vec![NodeInput::Inline(InlineRust::new( + format!( + r#"graphene_core::raster::adjustments::BlendNode::new( + graphene_core::value::CopiedNode::new({}), + graphene_core::value::CopiedNode::new({}), + ).eval(( + {{ + let bg_point = Vec2::new(_global_index.x as f32, _global_index.y as f32); + let fg_point = (*i4) * bg_point + (*i5); + + if !((fg_point.cmpge(Vec2::ZERO) & bg_point.cmpge(Vec2::ZERO)) == BVec2::new(true, true)) {{ + Color::from_rgbaf32_unchecked(0.0, 0.0, 0.0, 0.0) + }} else {{ + i2[((fg_point.y as u32) * i3 + (fg_point.x as u32)) as usize] + }} + }}, + i1[(_global_index.y * i0 + _global_index.x) as usize], + ))"#, + TaggedValue::BlendMode(blend_mode).to_primitive_string(), + TaggedValue::F32(opacity).to_primitive_string(), + ), + concrete![Color], + ))], + implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()), + ..Default::default() + }] + .into_iter() + .enumerate() + .map(|(i, n)| (i as u64, n)) + .collect(), + ..Default::default() + }; + log::debug!("compiling network"); + let proto_networks = compiler.compile(network.clone(), true).collect(); + log::debug!("compiling shader"); + + let shader = compilation_client::compile( + proto_networks, + vec![ + concrete!(u32), + concrete!(Color), + concrete!(Color), + concrete!(u32), + concrete_with_name!(Mat2, "Mat2"), + concrete_with_name!(Vec2, "Vec2"), + ], + vec![concrete!(Color)], + ShaderIO { + inputs: vec![ + ShaderInput::UniformBuffer((), concrete!(u32)), // width of the output image + ShaderInput::StorageBuffer((), concrete!(Color)), // background image + ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image + ShaderInput::UniformBuffer((), concrete!(u32)), // width of the foreground image + ShaderInput::UniformBuffer((), concrete_with_name!(Mat2, "Mat2")), // bg_to_fg.matrix2 + ShaderInput::UniformBuffer((), concrete_with_name!(Vec2, "Vec2")), // bg_to_fg.translation + ShaderInput::OutputBuffer((), concrete!(Color)), + ], + output: ShaderInput::OutputBuffer((), concrete!(Color)), + }, + ) + .await + .unwrap(); + let len = background.image.data.len(); + + let executor = NewExecutor::new() + .await + .expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser."); + log::debug!("creating buffer"); + let width_uniform = executor.create_uniform_buffer(background.image.width).unwrap(); + let bg_storage_buffer = executor + .create_storage_buffer( + background.image.data.clone(), + StorageBufferOptions { + cpu_writable: false, + gpu_writable: true, + cpu_readable: false, + storage: true, + }, + ) + .unwrap(); + let fg_storage_buffer = executor + .create_storage_buffer( + foreground.image.data.clone(), + StorageBufferOptions { + cpu_writable: false, + gpu_writable: true, + cpu_readable: false, + storage: true, + }, + ) + .unwrap(); + let fg_width_uniform = executor.create_uniform_buffer(foreground.image.width).unwrap(); + let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap(); + let translation_uniform = executor.create_uniform_buffer(translation).unwrap(); + let width_uniform = Arc::new(width_uniform); + let bg_storage_buffer = Arc::new(bg_storage_buffer); + let fg_storage_buffer = Arc::new(fg_storage_buffer); + let fg_width_uniform = Arc::new(fg_width_uniform); + let transform_uniform = Arc::new(transform_uniform); + let translation_uniform = Arc::new(translation_uniform); + let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap(); + let output_buffer = Arc::new(output_buffer); + let readback_buffer = executor.create_output_buffer(len, concrete!(Color), true).unwrap(); + let readback_buffer = Arc::new(readback_buffer); + log::debug!("created buffer"); + let bind_group = Bindgroup { + buffers: vec![ + width_uniform.clone(), + bg_storage_buffer.clone(), + fg_storage_buffer.clone(), + fg_width_uniform.clone(), + transform_uniform.clone(), + translation_uniform.clone(), + ], + }; + + let shader = gpu_executor::Shader { + source: shader.spirv_binary.into(), + name: "gpu::eval", + io: shader.io, + }; + log::debug!("loading shader"); + log::debug!("shader: {:?}", shader.source); + let shader = executor.load_shader(shader).unwrap(); + log::debug!("loaded shader"); + let pipeline = PipelineLayout { + shader, + entry_point: "eval".to_string(), + bind_group, + output_buffer: output_buffer.clone(), + }; + log::debug!("created pipeline"); + let compute_pass = executor + .create_compute_pass( + &pipeline, + Some(readback_buffer.clone()), + ComputePassDimensions::XY(background.image.width as u32, background.image.height as u32), + ) + .unwrap(); + executor.execute_compute_pipeline(compute_pass).unwrap(); + log::debug!("executed pipeline"); + log::debug!("reading buffer"); + let result = executor.read_output_buffer(readback_buffer).await.unwrap(); + let colors = bytemuck::pod_collect_to_vec::(result.as_slice()); + + ImageFrame { + image: Image { + data: colors, + width: background.image.width, + height: background.image.height, + }, + transform: background.transform, + } +} diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 3f251bdbb..5b758e73c 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -223,6 +223,26 @@ fn node_registry() -> HashMap), concrete!(ImageFrame), vec![value_fn!(DocumentNode)]), )], + #[cfg(feature = "gpu")] + vec![( + NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"), + |args| { + Box::pin(async move { + let background: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); + let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]); + let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity); + let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + + Box::pin(any) as TypeErasedPinned + }) + }, + NodeIOTypes::new( + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f32)], + ), + )], vec![( NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), |args| { @@ -326,7 +346,7 @@ fn node_registry() -> HashMap> = DowncastBothNode::new(args[0]); let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); - let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); + let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]); let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await)); let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node))); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); @@ -336,7 +356,7 @@ fn node_registry() -> HashMap), concrete!(ImageFrame), - vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f64)], + vec![value_fn!(ImageFrame), value_fn!(BlendMode), value_fn!(f32)], ), )], raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 46d6908c9..11745faee 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -3,7 +3,7 @@ mod executor; pub use context::Context; pub use executor::GpuExecutor; -use gpu_executor::{Shader, ShaderInput, StorageBufferOptions, ToStorageBuffer, ToUniformBuffer}; +use gpu_executor::{ComputePassDimensions, Shader, ShaderInput, StorageBufferOptions, ToStorageBuffer, ToUniformBuffer}; use graph_craft::Type; use anyhow::{bail, Result}; @@ -83,7 +83,7 @@ impl gpu_executor::GpuExecutor for NewExecutor { }; Ok(buffer) } - fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout, read_back: Option>>, instances: u32) -> Result { + fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout, read_back: Option>>, instances: ComputePassDimensions) -> Result { let compute_pipeline = self.context.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, @@ -113,11 +113,12 @@ impl gpu_executor::GpuExecutor for NewExecutor { let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { + let dimensions = instances.get(); let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None }); cpass.set_pipeline(&compute_pipeline); cpass.set_bind_group(0, &bind_group, &[]); cpass.insert_debug_marker("compute node network evaluation"); - cpass.dispatch_workgroups(instances, 1, 1); // Number of cells to run, the (x,y,z) size of item being processed + cpass.dispatch_workgroups(dimensions.0, dimensions.1, dimensions.2); // Number of cells to run, the (x,y,z) size of item being processed } // Sets adds copy operation to command encoder. // Will copy data from storage buffer on GPU to staging buffer on CPU.