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 <dennis@kobert.dev>
This commit is contained in:
Alexandru Ică 2023-05-28 11:34:09 +03:00 committed by Keavon Chambers
parent 9da83d3280
commit 57415b948b
14 changed files with 369 additions and 67 deletions

1
Cargo.lock generated
View File

@ -651,6 +651,7 @@ dependencies = [
"gpu-compiler-bin-wrapper", "gpu-compiler-bin-wrapper",
"gpu-executor", "gpu-executor",
"graph-craft", "graph-craft",
"graphene-core",
"reqwest", "reqwest",
"serde_json", "serde_json",
"tempfile", "tempfile",

View File

@ -723,6 +723,20 @@ fn static_nodes() -> Vec<DocumentNodeType> {
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties, 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 { DocumentNodeType {
name: "Extract", name: "Extract",
category: "Macros", category: "Macros",

View File

@ -12,6 +12,7 @@ serde_json = "1.0"
graph-craft = { version = "0.1.0", path = "../graph-craft", features = [ graph-craft = { version = "0.1.0", path = "../graph-craft", features = [
"serde", "serde",
] } ] }
graphene-core = { version = "0.1.0", path = "../gcore" }
gpu-executor = { version = "0.1.0", path = "../gpu-executor" } gpu-executor = { version = "0.1.0", path = "../gpu-executor" }
gpu-compiler-bin-wrapper = { version = "0.1.0", path = "../gpu-compiler/gpu-compiler-bin-wrapper" } gpu-compiler-bin-wrapper = { version = "0.1.0", path = "../gpu-compiler/gpu-compiler-bin-wrapper" }
tempfile = "3.3.0" tempfile = "3.3.0"

View File

@ -1,8 +1,11 @@
use gpu_compiler_bin_wrapper::CompileRequest; use gpu_compiler_bin_wrapper::CompileRequest;
use gpu_executor::{ShaderIO, ShaderInput}; use gpu_executor::{ShaderIO, ShaderInput};
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::*; use graph_craft::*;
use graphene_core::raster::adjustments::{BlendMode, BlendNode};
use graphene_core::Color;
use std::borrow::Cow; use std::borrow::Cow;
use std::time::Duration; use std::time::Duration;
@ -10,33 +13,21 @@ use std::time::Duration;
fn main() { fn main() {
let client = reqwest::blocking::Client::new(); 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 network = add_network();
let compiler = graph_craft::executor::Compiler {}; let compiler = graph_craft::executor::Compiler {};
let proto_network = compiler.compile_single(network, true).unwrap(); let proto_network = compiler.compile_single(network, true).unwrap();
let io = ShaderIO { let io = ShaderIO {
inputs: vec![ShaderInput::StorageBuffer((), concrete!(u32))], inputs: vec![
output: ShaderInput::OutputBuffer((), concrete!(&mut [u32])), 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 let response = client
.post("http://localhost:3000/compile/spirv") .post("http://localhost:3000/compile/spirv")
.timeout(Duration::from_secs(30)) .timeout(Duration::from_secs(30))
@ -52,27 +43,33 @@ fn add_network() -> NodeNetwork {
outputs: vec![NodeOutput::new(0, 0)], outputs: vec![NodeOutput::new(0, 0)],
disabled: vec![], disabled: vec![],
previous_outputs: None, previous_outputs: None,
nodes: [ nodes: [DocumentNode {
( name: "Blend Image".into(),
0, inputs: vec![NodeInput::Inline(InlineRust::new(
DocumentNode { format!(
name: "Dup".into(), r#"graphene_core::raster::adjustments::BlendNode::new(
inputs: vec![NodeInput::value(value::TaggedValue::U32(5u32), false)], graphene_core::value::CopiedNode::new({}),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), graphene_core::value::CopiedNode::new({}),
..Default::default() ).eval((
}, i1[_global_index.x as usize],
), if _global_index.x < i2[2] {{
// ( i0[_global_index.x as usize]
// 1, }} else {{
// DocumentNode { Color::from_rgbaf32_unchecked(0.0, 0.0, 0.0, 0.0)
// name: "Add".into(), }},
// inputs: vec![NodeInput::node(0, 0)], ))"#,
// metadata: DocumentNodeMetadata::default(), TaggedValue::BlendMode(BlendMode::Normal).to_primitive_string(),
// implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode")), TaggedValue::F32(1.0).to_primitive_string(),
// }, ),
// ), concrete![Color],
] ))],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()),
..Default::default()
}]
.into_iter() .into_iter()
.enumerate()
.map(|(i, n)| (i as u64, n))
.collect(), .collect(),
..Default::default()
} }
} }

View File

@ -84,6 +84,7 @@ impl BlendMode {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))] #[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)] #[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 { pub enum BlendMode {
#[default] #[default]
// Basic group // 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)] #[derive(Debug, Clone, Copy, Default)]
pub struct LuminanceNode<LuminanceCalculation> { pub struct LuminanceNode<LuminanceCalculation> {
luminance_calc: LuminanceCalculation, luminance_calc: LuminanceCalculation,
@ -410,7 +451,7 @@ pub struct BlendNode<BlendMode, Opacity> {
} }
#[node_macro::node_fn(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 opacity = opacity / 100.;
let (foreground, background) = input; 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()), 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)] #[derive(Debug, Clone, Copy)]

View File

@ -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_export]
macro_rules! generic { macro_rules! generic {
($type:ty) => {{ ($type:ty) => {{

View File

@ -17,7 +17,7 @@ pub fn compile_spirv(request: &CompileRequest, compile_dir: Option<&str>, manife
println!("calling cargo run!"); println!("calling cargo run!");
let non_cargo_env_vars = std::env::vars().filter(|(k, _)| k.starts_with("PATH")).collect::<Vec<_>>(); let non_cargo_env_vars = std::env::vars().filter(|(k, _)| k.starts_with("PATH")).collect::<Vec<_>>();
let mut cargo_command = std::process::Command::new("/usr/bin/cargo") let mut cargo_command = std::process::Command::new("cargo")
.arg("run") .arg("run")
.arg("--release") .arg("--release")
.arg("--manifest-path") .arg("--manifest-path")

View File

@ -71,7 +71,7 @@ pub fn construct_argument(input: &ShaderInput<()>, position: u32, binding_offset
let line = match input { let line = match input {
ShaderInput::Constant(constant) => format!("#[spirv({})] i{}: {}", constant_attribute(constant), position, constant.ty()), ShaderInput::Constant(constant) => format!("#[spirv({})] i{}: {}", constant_attribute(constant), position, constant.ty()),
ShaderInput::UniformBuffer(_, 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) => { ShaderInput::StorageBuffer(_, ty) | ShaderInput::ReadBackBuffer(_, ty) => {
format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] i{}: &[{}]", position + binding_offset, position, ty,) format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] i{}: &[{}]", position + binding_offset, position, ty,)

View File

@ -8,7 +8,8 @@ extern crate spirv_std;
//pub mod gpu { //pub mod gpu {
//use super::*; //use super::*;
use spirv_std::spirv; use spirv_std::spirv;
use spirv_std::glam::UVec3; use spirv_std::glam;
use spirv_std::glam::{UVec3, Vec2, Mat2, BVec2};
#[allow(unused)] #[allow(unused)]
#[spirv(compute(threads({{compute_threads}})))] #[spirv(compute(threads({{compute_threads}})))]
@ -19,6 +20,8 @@ extern crate spirv_std;
{% endfor %} {% endfor %}
) { ) {
use graphene_core::Node; use graphene_core::Node;
use graphene_core::raster::adjustments::{BlendMode, BlendNode};
use graphene_core::Color;
/* /*
{% for input in input_nodes %} {% for input in input_nodes %}
@ -34,7 +37,7 @@ extern crate spirv_std;
{% for output in output_nodes %} {% for output in output_nodes %}
let v = {{output}}.eval(()); 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 %} {% endfor %}
// TODO: Write output to buffer // TODO: Write output to buffer
} }

View File

@ -13,6 +13,22 @@ use std::sync::Arc;
type ReadBackFuture = Pin<Box<dyn Future<Output = Result<Vec<u8>>>>>; type ReadBackFuture = Pin<Box<dyn Future<Output = Result<Vec<u8>>>>>;
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 { pub trait GpuExecutor {
type ShaderHandle; type ShaderHandle;
type BufferHandle; type BufferHandle;
@ -22,7 +38,7 @@ pub trait GpuExecutor {
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self::BufferHandle>>; fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self::BufferHandle>>; fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self::BufferHandle>>; fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_compute_pass(&self, layout: &PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: u32) -> Result<Self::CommandBuffer>; fn create_compute_pass(&self, layout: &PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer>;
fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>; fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>;
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self::BufferHandle>>) -> ReadBackFuture; fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self::BufferHandle>>) -> ReadBackFuture;
} }
@ -129,10 +145,15 @@ pub struct StorageBufferOptions {
} }
pub trait ToUniformBuffer: StaticType { pub trait ToUniformBuffer: StaticType {
type UniformBufferHandle;
fn to_bytes(&self) -> Cow<[u8]>; fn to_bytes(&self) -> Cow<[u8]>;
} }
impl<T: StaticType + Pod + Zeroable> ToUniformBuffer for T {
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Owned(bytemuck::bytes_of(self).into())
}
}
pub trait ToStorageBuffer: StaticType { pub trait ToStorageBuffer: StaticType {
fn to_bytes(&self) -> Cow<[u8]>; fn to_bytes(&self) -> Cow<[u8]>;
fn ty(&self) -> Type; fn ty(&self) -> Type;
@ -233,7 +254,12 @@ pub struct CreateComputePassNode<Executor, Output, Instances> {
} }
#[node_macro::node_fn(CreateComputePassNode)] #[node_macro::node_fn(CreateComputePassNode)]
fn create_compute_pass_node<E: GpuExecutor + 'input>(layout: PipelineLayout<E>, executor: &'input E, output: ShaderInput<E::BufferHandle>, instances: u32) -> E::CommandBuffer { fn create_compute_pass_node<'any_input, E: 'any_input + GpuExecutor>(
layout: PipelineLayout<E>,
executor: &'any_input E,
output: ShaderInput<E::BufferHandle>,
instances: ComputePassDimensions,
) -> E::CommandBuffer {
executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap()
} }

View File

@ -3,7 +3,7 @@ use crate::executor::Any;
pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus};
use crate::proto::{Any as DAny, FutureAny}; 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}; use graphene_core::{Color, Node, Type};
pub use dyn_any::StaticType; pub use dyn_any::StaticType;
@ -185,10 +185,11 @@ impl<'a> TaggedValue {
match self { match self {
TaggedValue::None => "()".to_string(), TaggedValue::None => "()".to_string(),
TaggedValue::String(x) => format!("\"{}\"", x), TaggedValue::String(x) => format!("\"{}\"", x),
TaggedValue::U32(x) => x.to_string(), TaggedValue::U32(x) => x.to_string() + "_u32",
TaggedValue::F32(x) => x.to_string(), TaggedValue::F32(x) => x.to_string() + "_f32",
TaggedValue::F64(x) => x.to_string(), TaggedValue::F64(x) => x.to_string() + "_f64",
TaggedValue::Bool(x) => x.to_string(), TaggedValue::Bool(x) => x.to_string(),
TaggedValue::BlendMode(blend_mode) => "BlendMode::".to_string() + to_primtive_string(blend_mode),
_ => panic!("Cannot convert to primitive string"), _ => panic!("Cannot convert to primitive string"),
} }
} }

View File

@ -1,11 +1,11 @@
use glam::UVec3; use glam::{DAffine2, DMat2, DVec2, Mat2, UVec3, Vec2};
use gpu_executor::{Bindgroup, PipelineLayout, StorageBufferOptions}; use gpu_executor::{Bindgroup, ComputePassDimensions, PipelineLayout, StorageBufferOptions};
use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput}; use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::proto::*; use graph_craft::proto::*;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::value::ValueNode;
use graphene_core::*; use graphene_core::*;
use wgpu_executor::NewExecutor; use wgpu_executor::NewExecutor;
@ -58,7 +58,7 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
nodes: [ nodes: [
DocumentNode { DocumentNode {
name: "Slice".into(), name: "Slice".into(),
inputs: vec![NodeInput::Inline(InlineRust::new("i0[_global_index.x as usize]".into(), concrete![Color]))], inputs: vec![NodeInput::Inline(InlineRust::new("i1[(_global_index.y * i0 + _global_index.x) as usize]".into(), concrete![Color]))],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()), implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()),
..Default::default() ..Default::default()
}, },
@ -108,10 +108,11 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
log::debug!("compiling shader"); log::debug!("compiling shader");
let shader = compilation_client::compile( let shader = compilation_client::compile(
proto_networks, proto_networks,
vec![concrete!(Color)], //, concrete!(u32)], vec![concrete!(u32), concrete!(Color)], //, concrete!(u32)],
vec![concrete!(Color)], vec![concrete!(Color)],
ShaderIO { ShaderIO {
inputs: vec![ inputs: vec![
ShaderInput::UniformBuffer((), concrete!(u32)),
ShaderInput::StorageBuffer((), concrete!(Color)), ShaderInput::StorageBuffer((), concrete!(Color)),
//ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId), //ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
ShaderInput::OutputBuffer((), concrete!(Color)), ShaderInput::OutputBuffer((), concrete!(Color)),
@ -122,11 +123,11 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
.await .await
.unwrap(); .unwrap();
//return ImageFrame::empty(); //return ImageFrame::empty();
let len = image.image.data.len(); let len: usize = image.image.data.len();
log::debug!("instances: {}", len);
let executor = NewExecutor::new().await.unwrap(); let executor = NewExecutor::new().await.unwrap();
log::debug!("creating buffer"); log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
let storage_buffer = executor let storage_buffer = executor
.create_storage_buffer( .create_storage_buffer(
image.image.data.clone(), image.image.data.clone(),
@ -138,6 +139,7 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
}, },
) )
.unwrap(); .unwrap();
let width_uniform = Arc::new(width_uniform);
let storage_buffer = Arc::new(storage_buffer); let storage_buffer = Arc::new(storage_buffer);
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap(); let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
let output_buffer = Arc::new(output_buffer); let output_buffer = Arc::new(output_buffer);
@ -145,7 +147,7 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
let readback_buffer = Arc::new(readback_buffer); let readback_buffer = Arc::new(readback_buffer);
log::debug!("created buffer"); log::debug!("created buffer");
let bind_group = Bindgroup { let bind_group = Bindgroup {
buffers: vec![storage_buffer.clone()], buffers: vec![width_uniform.clone(), storage_buffer.clone()],
}; };
let shader = gpu_executor::Shader { let shader = gpu_executor::Shader {
@ -164,7 +166,9 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
output_buffer: output_buffer.clone(), output_buffer: output_buffer.clone(),
}; };
log::debug!("created pipeline"); log::debug!("created pipeline");
let compute_pass = executor.create_compute_pass(&pipeline, Some(readback_buffer.clone()), len as u32).unwrap(); let compute_pass = executor
.create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(image.image.width, image.image.height))
.unwrap();
executor.execute_compute_pipeline(compute_pass).unwrap(); executor.execute_compute_pipeline(compute_pass).unwrap();
log::debug!("executed pipeline"); log::debug!("executed pipeline");
log::debug!("reading buffer"); log::debug!("reading buffer");
@ -243,3 +247,183 @@ fn map_gpu_single_image(input: Image<Color>, node: String) -> Image<Color> {
Image { data, ..input } Image { data, ..input }
} }
*/ */
#[derive(Debug, Clone, Copy)]
pub struct BlendGpuImageNode<Background, B, O> {
background: Background,
blend_mode: B,
opacity: O,
}
#[node_macro::node_fn(BlendGpuImageNode)]
async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<Color>, blend_mode: BlendMode, opacity: f32) -> ImageFrame<Color> {
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::<u8, Color>(result.as_slice());
ImageFrame {
image: Image {
data: colors,
width: background.image.width,
height: background.image.height,
},
transform: background.transform,
}
}

View File

@ -223,6 +223,26 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
}, },
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![value_fn!(DocumentNode)]), NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![value_fn!(DocumentNode)]),
)], )],
#[cfg(feature = "gpu")]
vec![(
NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
|args| {
Box::pin(async move {
let background: DowncastBothNode<(), ImageFrame<Color>> = 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<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(
concrete!(ImageFrame<Color>),
concrete!(ImageFrame<Color>),
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f32)],
),
)],
vec![( vec![(
NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|args| { |args| {
@ -326,7 +346,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
Box::pin(async move { Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]); let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); 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 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 node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
@ -336,7 +356,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIOTypes::new( NodeIOTypes::new(
concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>),
concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>),
vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f64)], vec![value_fn!(ImageFrame<Color>), value_fn!(BlendMode), value_fn!(f32)],
), ),
)], )],
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),

View File

@ -3,7 +3,7 @@ mod executor;
pub use context::Context; pub use context::Context;
pub use executor::GpuExecutor; 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 graph_craft::Type;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -83,7 +83,7 @@ impl gpu_executor::GpuExecutor for NewExecutor {
}; };
Ok(buffer) Ok(buffer)
} }
fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: u32) -> Result<CommandBuffer> { fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: ComputePassDimensions) -> Result<CommandBuffer> {
let compute_pipeline = self.context.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { let compute_pipeline = self.context.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: None, label: None,
layout: 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 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 }); let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
cpass.set_pipeline(&compute_pipeline); cpass.set_pipeline(&compute_pipeline);
cpass.set_bind_group(0, &bind_group, &[]); cpass.set_bind_group(0, &bind_group, &[]);
cpass.insert_debug_marker("compute node network evaluation"); 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. // Sets adds copy operation to command encoder.
// Will copy data from storage buffer on GPU to staging buffer on CPU. // Will copy data from storage buffer on GPU to staging buffer on CPU.