mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Remove the old node macro and fix/clean up several raster nodes (#2650)
* Fix several broken raster nodes and clean up leftover old node system code * Migrate Brightness/Contrast to the new node macro, and fix it * Remove last usages of old_node_fn * Remove old_node_fn
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::raster::{BlendImageTupleNode, ExtendImageToBoundsNode, blend_image_closure};
|
||||
use crate::raster::{BlendImageTupleNode, blend_image_closure, extend_image_to_bounds};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::generic::FnNode;
|
||||
use graph_craft::proto::FutureWrapperNode;
|
||||
@@ -8,7 +8,7 @@ use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::image::{Image, ImageFrameTable};
|
||||
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||
use graphene_core::value::{ClonedNode, ValueNode};
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::{Ctx, GraphicElement, Node};
|
||||
@@ -225,7 +225,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
||||
background_bounds = bounds.transform();
|
||||
}
|
||||
|
||||
let mut actual_image = ExtendImageToBoundsNode::new(ClonedNode::new(background_bounds)).eval(brush_plan.background);
|
||||
let mut actual_image = extend_image_to_bounds((), brush_plan.background, background_bounds);
|
||||
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
|
||||
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
|
||||
// Create brush texture.
|
||||
@@ -262,7 +262,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
|
||||
);
|
||||
let blit_target = if idx == 0 {
|
||||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
ExtendImageToBoundsNode::new(CopiedNode::new(stroke_to_layer)).eval(target)
|
||||
extend_image_to_bounds((), target, stroke_to_layer)
|
||||
} else {
|
||||
use crate::raster::empty_image;
|
||||
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
use crate::wasm_application_io::WasmApplicationIo;
|
||||
use dyn_any::StaticTypeSized;
|
||||
use glam::{DAffine2, DVec2, Mat2, Vec2};
|
||||
use gpu_executor::{ComputePassDimensions, StorageBufferOptions};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::proto::*;
|
||||
use graphene_core::application_io::ApplicationIo;
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::image::{Image, ImageFrameTable};
|
||||
use graphene_core::raster::{BlendMode, Pixel};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
|
||||
use std::sync::Arc;
|
||||
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor};
|
||||
|
||||
// TODO: Move to graph-craft
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
@@ -39,240 +35,6 @@ async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context
|
||||
Ok(compilation_client::compile(proto_networks, input_types, output_types, io).await.unwrap())
|
||||
}
|
||||
|
||||
pub struct MapGpuNode<Node, EditorApi> {
|
||||
node: Node,
|
||||
editor_api: EditorApi,
|
||||
cache: Mutex<HashMap<String, ComputePass>>,
|
||||
}
|
||||
|
||||
struct ComputePass {
|
||||
pipeline_layout: PipelineLayout,
|
||||
readback_buffer: Option<Arc<WgpuShaderInput>>,
|
||||
}
|
||||
|
||||
impl Clone for ComputePass {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pipeline_layout: self.pipeline_layout.clone(),
|
||||
readback_buffer: self.readback_buffer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::old_node_impl(MapGpuNode)]
|
||||
async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
|
||||
let image_frame_table = ℑ
|
||||
let image = image.one_instance_ref().instance;
|
||||
|
||||
log::debug!("Executing gpu node");
|
||||
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
|
||||
|
||||
#[cfg(feature = "image-compare")]
|
||||
let img: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(image.data.clone())).unwrap().into();
|
||||
|
||||
// TODO: The cache should be based on the network topology not the node name
|
||||
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key("placeholder") {
|
||||
self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone()
|
||||
} else {
|
||||
let name = "placeholder".to_string();
|
||||
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
|
||||
log::error!("Error creating compute pass descriptor in 'map_gpu()");
|
||||
return ImageFrameTable::one_empty_image();
|
||||
};
|
||||
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
|
||||
log::error!("created compute pass");
|
||||
compute_pass_descriptor
|
||||
};
|
||||
|
||||
let compute_pass = executor
|
||||
.create_compute_pass(
|
||||
&compute_pass_descriptor.pipeline_layout,
|
||||
compute_pass_descriptor.readback_buffer.clone(),
|
||||
ComputePassDimensions::XY(image.width / 12 + 1, image.height / 8 + 1),
|
||||
)
|
||||
.unwrap();
|
||||
executor.execute_compute_pipeline(compute_pass).unwrap();
|
||||
log::debug!("executed pipeline");
|
||||
log::debug!("reading buffer");
|
||||
let result = executor.read_output_buffer(compute_pass_descriptor.readback_buffer.clone().unwrap()).await.unwrap();
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
log::debug!("first color: {:?}", colors[0]);
|
||||
|
||||
#[cfg(feature = "image-compare")]
|
||||
let img2: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(colors.clone())).unwrap().into();
|
||||
#[cfg(feature = "image-compare")]
|
||||
let score = image_compare::rgb_hybrid_compare(&img.into_rgb8(), &img2.into_rgb8()).unwrap();
|
||||
#[cfg(feature = "image-compare")]
|
||||
log::debug!("score: {:?}", score.score);
|
||||
|
||||
let new_image = Image {
|
||||
data: colors,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
..Default::default()
|
||||
};
|
||||
let mut result = ImageFrameTable::new(new_image);
|
||||
*result.transform_mut() = image_frame_table.transform();
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
|
||||
pub fn new(node: Node, editor_api: EditorApi) -> Self {
|
||||
Self {
|
||||
node,
|
||||
editor_api,
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrameTable<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String>
|
||||
where
|
||||
GraphicElement: From<Image<T>>,
|
||||
T::Static: Pixel,
|
||||
{
|
||||
let image = image.one_instance_ref().instance;
|
||||
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
let inner_network = NodeNetwork::value_network(node);
|
||||
|
||||
log::debug!("inner_network: {inner_network:?}");
|
||||
let network = NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::Inline(InlineRust::new("i1[(_global_index.y * i0 + _global_index.x) as usize]".into(), concrete![Color]))],
|
||||
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(u32), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
// DocumentNode {
|
||||
// name: "Index".into(),
|
||||
// // inputs: vec![NodeInput::Network(concrete!(UVec3))],
|
||||
// inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))],
|
||||
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// DocumentNode {
|
||||
// name: "Get Node".into(),
|
||||
// inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)],
|
||||
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
..Default::default()
|
||||
},
|
||||
// DocumentNode {
|
||||
// name: "Save Node".into(),
|
||||
// inputs: vec![
|
||||
// NodeInput::node(NodeId(5), 0),
|
||||
// NodeInput::Inline(InlineRust::new(
|
||||
// "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(),
|
||||
// // "|x|()".into(),
|
||||
// Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))),
|
||||
// )),
|
||||
// ],
|
||||
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
log::debug!("compiling network");
|
||||
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
|
||||
log::debug!("compiling shader");
|
||||
let shader = compilation_client::compile(
|
||||
proto_networks?,
|
||||
vec![concrete!(u32), concrete!(Color)],
|
||||
vec![concrete!(Color)],
|
||||
ShaderIO {
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)),
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)),
|
||||
ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
],
|
||||
output: ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let len: usize = image.data.len();
|
||||
|
||||
let storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
image.data.clone(),
|
||||
StorageBufferOptions {
|
||||
cpu_writable: false,
|
||||
gpu_writable: true,
|
||||
cpu_readable: false,
|
||||
storage: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// let canvas = editor_api.application_io.create_surface();
|
||||
|
||||
// let surface = unsafe { executor.create_surface(canvas) }.unwrap();
|
||||
// let surface_id = surface.surface_id;
|
||||
|
||||
// let texture = executor.create_texture_buffer(image.clone(), TextureBufferOptions::Texture).unwrap();
|
||||
|
||||
// // executor.create_render_pass(texture, surface).unwrap();
|
||||
|
||||
// let frame = SurfaceFrame {
|
||||
// surface_id,
|
||||
// transform: image.transform,
|
||||
// };
|
||||
// return frame;
|
||||
|
||||
log::debug!("creating buffer");
|
||||
let width_uniform = executor.create_uniform_buffer(image.width).unwrap();
|
||||
|
||||
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);
|
||||
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.into(), storage_buffer],
|
||||
};
|
||||
|
||||
let shader = Shader {
|
||||
source: shader.spirv_binary.into(),
|
||||
name: "gpu::eval",
|
||||
io: shader.io,
|
||||
};
|
||||
log::debug!("loading shader");
|
||||
let shader = executor.load_shader(shader).unwrap();
|
||||
log::debug!("loaded shader");
|
||||
let pipeline = PipelineLayout {
|
||||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer,
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
|
||||
Ok(ComputePass {
|
||||
pipeline_layout: pipeline,
|
||||
readback_buffer: Some(readback_buffer),
|
||||
})
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
let foreground_transform = foreground.transform();
|
||||
@@ -457,3 +219,237 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// struct ComputePass {
|
||||
// pipeline_layout: PipelineLayout,
|
||||
// readback_buffer: Option<Arc<WgpuShaderInput>>,
|
||||
// }
|
||||
|
||||
// impl Clone for ComputePass {
|
||||
// fn clone(&self) -> Self {
|
||||
// Self {
|
||||
// pipeline_layout: self.pipeline_layout.clone(),
|
||||
// readback_buffer: self.readback_buffer.clone(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub struct MapGpuNode<Node, EditorApi> {
|
||||
// node: Node,
|
||||
// editor_api: EditorApi,
|
||||
// cache: Mutex<HashMap<String, ComputePass>>,
|
||||
// }
|
||||
|
||||
// #[node_macro::old_node_impl(MapGpuNode)]
|
||||
// async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
|
||||
// let image_frame_table = ℑ
|
||||
// let image = image.one_instance_ref().instance;
|
||||
|
||||
// log::debug!("Executing gpu node");
|
||||
// let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
|
||||
|
||||
// #[cfg(feature = "image-compare")]
|
||||
// let img: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(image.data.clone())).unwrap().into();
|
||||
|
||||
// // TODO: The cache should be based on the network topology not the node name
|
||||
// let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key("placeholder") {
|
||||
// self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone()
|
||||
// } else {
|
||||
// let name = "placeholder".to_string();
|
||||
// let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
|
||||
// log::error!("Error creating compute pass descriptor in 'map_gpu()");
|
||||
// return ImageFrameTable::one_empty_image();
|
||||
// };
|
||||
// self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
|
||||
// log::error!("created compute pass");
|
||||
// compute_pass_descriptor
|
||||
// };
|
||||
|
||||
// let compute_pass = executor
|
||||
// .create_compute_pass(
|
||||
// &compute_pass_descriptor.pipeline_layout,
|
||||
// compute_pass_descriptor.readback_buffer.clone(),
|
||||
// ComputePassDimensions::XY(image.width / 12 + 1, image.height / 8 + 1),
|
||||
// )
|
||||
// .unwrap();
|
||||
// executor.execute_compute_pipeline(compute_pass).unwrap();
|
||||
// log::debug!("executed pipeline");
|
||||
// log::debug!("reading buffer");
|
||||
// let result = executor.read_output_buffer(compute_pass_descriptor.readback_buffer.clone().unwrap()).await.unwrap();
|
||||
// let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
// log::debug!("first color: {:?}", colors[0]);
|
||||
|
||||
// #[cfg(feature = "image-compare")]
|
||||
// let img2: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(colors.clone())).unwrap().into();
|
||||
// #[cfg(feature = "image-compare")]
|
||||
// let score = image_compare::rgb_hybrid_compare(&img.into_rgb8(), &img2.into_rgb8()).unwrap();
|
||||
// #[cfg(feature = "image-compare")]
|
||||
// log::debug!("score: {:?}", score.score);
|
||||
|
||||
// let new_image = Image {
|
||||
// data: colors,
|
||||
// width: image.width,
|
||||
// height: image.height,
|
||||
// ..Default::default()
|
||||
// };
|
||||
// let mut result = ImageFrameTable::new(new_image);
|
||||
// *result.transform_mut() = image_frame_table.transform();
|
||||
// *result.one_instance_mut().alpha_blending = *image_frame_table.one_instance_ref().alpha_blending;
|
||||
|
||||
// result
|
||||
// }
|
||||
|
||||
// impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
|
||||
// pub fn new(node: Node, editor_api: EditorApi) -> Self {
|
||||
// Self {
|
||||
// node,
|
||||
// editor_api,
|
||||
// cache: Mutex::new(HashMap::new()),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrameTable<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String>
|
||||
// where
|
||||
// GraphicElement: From<Image<T>>,
|
||||
// T::Static: Pixel,
|
||||
// {
|
||||
// let image = image.one_instance_ref().instance;
|
||||
|
||||
// let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
// let inner_network = NodeNetwork::value_network(node);
|
||||
|
||||
// log::debug!("inner_network: {inner_network:?}");
|
||||
// let network = NodeNetwork {
|
||||
// exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
// nodes: [
|
||||
// DocumentNode {
|
||||
// inputs: vec![NodeInput::Inline(InlineRust::new("i1[(_global_index.y * i0 + _global_index.x) as usize]".into(), concrete![Color]))],
|
||||
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// DocumentNode {
|
||||
// inputs: vec![NodeInput::network(concrete!(u32), 0)],
|
||||
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// // DocumentNode {
|
||||
// // name: "Index".into(),
|
||||
// // // inputs: vec![NodeInput::Network(concrete!(UVec3))],
|
||||
// // inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))],
|
||||
// // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
// // ..Default::default()
|
||||
// // },
|
||||
// // DocumentNode {
|
||||
// // name: "Get Node".into(),
|
||||
// // inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 0)],
|
||||
// // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::storage::GetNode".into()),
|
||||
// // ..Default::default()
|
||||
// // },
|
||||
// DocumentNode {
|
||||
// inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
// implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// // DocumentNode {
|
||||
// // name: "Save Node".into(),
|
||||
// // inputs: vec![
|
||||
// // NodeInput::node(NodeId(5), 0),
|
||||
// // NodeInput::Inline(InlineRust::new(
|
||||
// // "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(),
|
||||
// // // "|x|()".into(),
|
||||
// // Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))),
|
||||
// // )),
|
||||
// // ],
|
||||
// // implementation: DocumentNodeImplementation::ProtoNode("graphene_core::generic::FnMutNode".into()),
|
||||
// // ..Default::default()
|
||||
// // },
|
||||
// ]
|
||||
// .into_iter()
|
||||
// .enumerate()
|
||||
// .map(|(id, node)| (NodeId(id as u64), node))
|
||||
// .collect(),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// log::debug!("compiling network");
|
||||
// let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
|
||||
// log::debug!("compiling shader");
|
||||
// let shader = compilation_client::compile(
|
||||
// proto_networks?,
|
||||
// vec![concrete!(u32), concrete!(Color)],
|
||||
// vec![concrete!(Color)],
|
||||
// ShaderIO {
|
||||
// inputs: vec![
|
||||
// ShaderInput::UniformBuffer((), concrete!(u32)),
|
||||
// ShaderInput::StorageBuffer((), concrete!(Color)),
|
||||
// ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
// ],
|
||||
// output: ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
// },
|
||||
// )
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let len: usize = image.data.len();
|
||||
|
||||
// let storage_buffer = executor
|
||||
// .create_storage_buffer(
|
||||
// image.data.clone(),
|
||||
// StorageBufferOptions {
|
||||
// cpu_writable: false,
|
||||
// gpu_writable: true,
|
||||
// cpu_readable: false,
|
||||
// storage: true,
|
||||
// },
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// // let canvas = editor_api.application_io.create_surface();
|
||||
|
||||
// // let surface = unsafe { executor.create_surface(canvas) }.unwrap();
|
||||
// // let surface_id = surface.surface_id;
|
||||
|
||||
// // let texture = executor.create_texture_buffer(image.clone(), TextureBufferOptions::Texture).unwrap();
|
||||
|
||||
// // // executor.create_render_pass(texture, surface).unwrap();
|
||||
|
||||
// // let frame = SurfaceFrame {
|
||||
// // surface_id,
|
||||
// // transform: image.transform,
|
||||
// // };
|
||||
// // return frame;
|
||||
|
||||
// log::debug!("creating buffer");
|
||||
// let width_uniform = executor.create_uniform_buffer(image.width).unwrap();
|
||||
|
||||
// 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);
|
||||
// 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.into(), storage_buffer],
|
||||
// };
|
||||
|
||||
// let shader = Shader {
|
||||
// source: shader.spirv_binary.into(),
|
||||
// name: "gpu::eval",
|
||||
// io: shader.io,
|
||||
// };
|
||||
// log::debug!("loading shader");
|
||||
// let shader = executor.load_shader(shader).unwrap();
|
||||
// log::debug!("loaded shader");
|
||||
// let pipeline = PipelineLayout {
|
||||
// shader: shader.into(),
|
||||
// entry_point: "eval".to_string(),
|
||||
// bind_group: bind_group.into(),
|
||||
// output_buffer,
|
||||
// };
|
||||
// log::debug!("created pipeline");
|
||||
|
||||
// Ok(ComputePass {
|
||||
// pipeline_layout: pipeline,
|
||||
// readback_buffer: Some(readback_buffer),
|
||||
// })
|
||||
// }
|
||||
|
||||
@@ -4,7 +4,7 @@ use glam::{DAffine2, DVec2, Vec2};
|
||||
use graphene_core::raster::bbox::Bbox;
|
||||
use graphene_core::raster::image::{Image, ImageFrameTable};
|
||||
use graphene_core::raster::{
|
||||
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample,
|
||||
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Sample,
|
||||
};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
|
||||
@@ -12,7 +12,6 @@ use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug, DynAny)]
|
||||
pub enum Error {
|
||||
@@ -90,95 +89,28 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MapImageNode<P, MapFn> {
|
||||
map_fn: MapFn,
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
|
||||
#[node_macro::old_node_fn(MapImageNode<_P>)]
|
||||
fn map_image<MapFn, _P, Img: BitmapMut<Pixel = _P>>(image: Img, map_fn: &'input MapFn) -> Img
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, _P, Output = _P> + 'input,
|
||||
{
|
||||
let mut image = image;
|
||||
|
||||
image.map_pixels(|c| map_fn.eval(c));
|
||||
image
|
||||
}
|
||||
|
||||
#[node_macro::node]
|
||||
fn insert_channel<
|
||||
// _P is the color of the input image.
|
||||
_P: RGBMut,
|
||||
_S: Pixel + Luminance,
|
||||
// Input image
|
||||
Input: BitmapMut<Pixel = _P>,
|
||||
Insertion: Bitmap<Pixel = _S>,
|
||||
>(
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn combine_channels<_I, Red, Green, Blue, Alpha>(
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] insertion: Insertion,
|
||||
target_channel: RedGreenBlue,
|
||||
) -> Input
|
||||
where
|
||||
_P::ColorChannel: Linear,
|
||||
{
|
||||
if insertion.width() == 0 {
|
||||
return image;
|
||||
}
|
||||
|
||||
if insertion.width() != image.width() || insertion.height() != image.height() {
|
||||
log::warn!("Stencil and image have different sizes. This is not supported.");
|
||||
return image;
|
||||
}
|
||||
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
let insertion_pixel = insertion.get_pixel(x, y).unwrap();
|
||||
match target_channel {
|
||||
RedGreenBlue::Red => image_pixel.set_red(insertion_pixel.l().cast_linear_channel()),
|
||||
RedGreenBlue::Green => image_pixel.set_green(insertion_pixel.l().cast_linear_channel()),
|
||||
RedGreenBlue::Blue => image_pixel.set_blue(insertion_pixel.l().cast_linear_channel()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
#[node_macro::node]
|
||||
fn combine_channels<
|
||||
// _P is the color of the input image.
|
||||
_P: RGBMut + AlphaMut,
|
||||
_S: Pixel + Luminance,
|
||||
// Input image
|
||||
Input: BitmapMut<Pixel = _P>,
|
||||
Red: Bitmap<Pixel = _S>,
|
||||
Green: Bitmap<Pixel = _S>,
|
||||
Blue: Bitmap<Pixel = _S>,
|
||||
Alpha: Bitmap<Pixel = _S>,
|
||||
>(
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
_primary: (),
|
||||
#[implementations(ImageFrameTable<Color>)] red: Red,
|
||||
#[implementations(ImageFrameTable<Color>)] green: Green,
|
||||
#[implementations(ImageFrameTable<Color>)] blue: Blue,
|
||||
#[implementations(ImageFrameTable<Color>)] alpha: Alpha,
|
||||
) -> Input
|
||||
) -> ImageFrameTable<Color>
|
||||
where
|
||||
_P::ColorChannel: Linear,
|
||||
_I: Pixel + Luminance,
|
||||
Red: Bitmap<Pixel = _I>,
|
||||
Green: Bitmap<Pixel = _I>,
|
||||
Blue: Bitmap<Pixel = _I>,
|
||||
Alpha: Bitmap<Pixel = _I>,
|
||||
{
|
||||
let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()];
|
||||
if dimensions.iter().all(|&(x, _)| x == 0) {
|
||||
return image;
|
||||
if dimensions.iter().any(|&(x, y)| x == 0 || y == 0) || dimensions.iter().any(|&(x, y)| dimensions.iter().any(|&(other_x, other_y)| x != other_x || y != other_y)) {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
}
|
||||
|
||||
if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) {
|
||||
log::warn!("Stencil and image have different sizes. This is not supported.");
|
||||
return image;
|
||||
}
|
||||
let mut image = Image::new(red.width(), red.height(), Color::TRANSPARENT);
|
||||
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
@@ -198,26 +130,30 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
image
|
||||
ImageFrameTable::new(image)
|
||||
}
|
||||
|
||||
#[node_macro::node()]
|
||||
fn mask_image<
|
||||
// _P is the color of the input image. It must have an alpha channel because that is going to
|
||||
// be modified by the mask
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mask<_P, _S, Input, Stencil>(
|
||||
_: impl Ctx,
|
||||
/// The image to be masked.
|
||||
#[implementations(ImageFrameTable<Color>)]
|
||||
mut image: Input,
|
||||
/// The stencil to be used for masking.
|
||||
#[implementations(ImageFrameTable<Color>)]
|
||||
#[expose]
|
||||
stencil: Stencil,
|
||||
) -> Input
|
||||
where
|
||||
// _P is the color of the input image. It must have an alpha channel because that is going to be modified by the mask.
|
||||
_P: Alpha,
|
||||
// _S is the color of the stencil. It must have a luminance channel because that is used to
|
||||
// mask the input image
|
||||
// _S is the color of the stencil. It must have a luminance channel because that is used to mask the input image.
|
||||
_S: Luminance,
|
||||
// Input image
|
||||
Input: Transform + BitmapMut<Pixel = _P>,
|
||||
// Stencil
|
||||
Stencil: Transform + Sample<Pixel = _S>,
|
||||
>(
|
||||
_: impl Ctx,
|
||||
#[implementations(ImageFrameTable<Color>)] mut image: Input,
|
||||
#[implementations(ImageFrameTable<Color>)] stencil: Stencil,
|
||||
) -> Input {
|
||||
{
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let mask_size = stencil.transform().decompose_scale();
|
||||
|
||||
@@ -313,13 +249,8 @@ where
|
||||
background
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ExtendImageToBoundsNode<Bounds> {
|
||||
bounds: Bounds,
|
||||
}
|
||||
|
||||
#[node_macro::old_node_fn(ExtendImageToBoundsNode)]
|
||||
fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
|
||||
#[node_macro::node(category(""))]
|
||||
fn extend_image_to_bounds(_: impl Ctx, image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
|
||||
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
|
||||
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
|
||||
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
|
||||
|
||||
Reference in New Issue
Block a user