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:
parent
9da83d3280
commit
57415b948b
|
|
@ -651,6 +651,7 @@ dependencies = [
|
|||
"gpu-compiler-bin-wrapper",
|
||||
"gpu-executor",
|
||||
"graph-craft",
|
||||
"graphene-core",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
|
|
|||
|
|
@ -723,6 +723,20 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<LuminanceCalculation> {
|
||||
luminance_calc: LuminanceCalculation,
|
||||
|
|
@ -410,7 +451,7 @@ pub struct BlendNode<BlendMode, Opacity> {
|
|||
}
|
||||
|
||||
#[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)]
|
||||
|
|
|
|||
|
|
@ -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) => {{
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>();
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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,)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,22 @@ use std::sync::Arc;
|
|||
|
||||
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 {
|
||||
type ShaderHandle;
|
||||
type BufferHandle;
|
||||
|
|
@ -22,7 +38,7 @@ pub trait GpuExecutor {
|
|||
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_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 read_output_buffer(&self, buffer: Arc<ShaderInput<Self::BufferHandle>>) -> ReadBackFuture;
|
||||
}
|
||||
|
|
@ -129,10 +145,15 @@ pub struct StorageBufferOptions {
|
|||
}
|
||||
|
||||
pub trait ToUniformBuffer: StaticType {
|
||||
type UniformBufferHandle;
|
||||
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 {
|
||||
fn to_bytes(&self) -> Cow<[u8]>;
|
||||
fn ty(&self) -> Type;
|
||||
|
|
@ -233,7 +254,12 @@ pub struct CreateComputePassNode<Executor, Output, Instances> {
|
|||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Color>, node: DocumentNode) -> ImageFrame<Col
|
|||
nodes: [
|
||||
DocumentNode {
|
||||
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()),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -108,10 +108,11 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
|
|||
log::debug!("compiling shader");
|
||||
let shader = compilation_client::compile(
|
||||
proto_networks,
|
||||
vec![concrete!(Color)], //, concrete!(u32)],
|
||||
vec![concrete!(u32), concrete!(Color)], //, concrete!(u32)],
|
||||
vec![concrete!(Color)],
|
||||
ShaderIO {
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)),
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)),
|
||||
//ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
|
||||
ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
|
|
@ -122,11 +123,11 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
|
|||
.await
|
||||
.unwrap();
|
||||
//return ImageFrame::empty();
|
||||
let len = image.image.data.len();
|
||||
log::debug!("instances: {}", len);
|
||||
let len: usize = image.image.data.len();
|
||||
|
||||
let executor = NewExecutor::new().await.unwrap();
|
||||
log::debug!("creating buffer");
|
||||
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
|
||||
let storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
image.image.data.clone(),
|
||||
|
|
@ -138,6 +139,7 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
|
|||
},
|
||||
)
|
||||
.unwrap();
|
||||
let width_uniform = Arc::new(width_uniform);
|
||||
let storage_buffer = Arc::new(storage_buffer);
|
||||
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
|
||||
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);
|
||||
log::debug!("created buffer");
|
||||
let bind_group = Bindgroup {
|
||||
buffers: vec![storage_buffer.clone()],
|
||||
buffers: vec![width_uniform.clone(), storage_buffer.clone()],
|
||||
};
|
||||
|
||||
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(),
|
||||
};
|
||||
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();
|
||||
log::debug!("executed pipeline");
|
||||
log::debug!("reading buffer");
|
||||
|
|
@ -243,3 +247,183 @@ fn map_gpu_single_image(input: Image<Color>, node: String) -> Image<Color> {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,6 +223,26 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
},
|
||||
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![(
|
||||
NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|
||||
|args| {
|
||||
|
|
@ -326,7 +346,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
Box::pin(async move {
|
||||
let image: DowncastBothNode<(), ImageFrame<Color>> = 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<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(
|
||||
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]),
|
||||
|
|
|
|||
|
|
@ -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<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 {
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue