Graphene CLI + quantization research (#1320)
* Implement skeleton for graphene-cli * Configure gpu surface on non wasm32 targets * Create window with full hd size * Create window using the graphen-cli * Use window size for surface creation * Reuse surface configuration * Reduce window size for native applications to 800x600 * Add compute pipeline test * Poll wgpu execution externally * Remove cache node after texture upload * Add profiling instructions * Add more debug markers * Evaluate extract node before flattening the network * Reenable hue saturation node for compilation * Make hue saturation node work on the gpu + make f32 default for user inputs * Add version of test files without caching * Only dispatch each workgroup not pixel * ICE * Add quantization to gpu code * Fix quantization * Load images at graph runtime * Fix quantization calculation * Feature gate quantization * Use git version of autoquant * Add license to `graphene-cli` * Fix graphene-cli test case * Ignore tests on non unix platforms * Fix flattening test
|
|
@ -8,6 +8,7 @@ members = [
|
|||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
"node-graph/compilation-server",
|
||||
|
|
|
|||
|
|
@ -111,12 +111,12 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
fn stroke_set(&mut self, stroke: Stroke) {
|
||||
self.modify_inputs("Stroke", false, |inputs| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F64(stroke.weight), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F32(stroke.weight as f32), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::F64(stroke.dash_offset), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::F32(stroke.dash_offset as f32), false);
|
||||
inputs[5] = NodeInput::value(TaggedValue::LineCap(stroke.line_cap), false);
|
||||
inputs[6] = NodeInput::value(TaggedValue::LineJoin(stroke.line_join), false);
|
||||
inputs[7] = NodeInput::value(TaggedValue::F64(stroke.line_join_miter_limit), false);
|
||||
inputs[7] = NodeInput::value(TaggedValue::F32(stroke.line_join_miter_limit as f32), false);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) {
|
|||
let (scale, angle, translation, shear) = compute_scale_angle_translation_shear(transform);
|
||||
|
||||
inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F64(angle), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F32(angle as f32), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::DVec2(shear), false);
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {
|
|||
};
|
||||
|
||||
let angle = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(angle),
|
||||
tagged_value: TaggedValue::F32(angle),
|
||||
..
|
||||
} = inputs[2]
|
||||
{
|
||||
|
|
@ -116,7 +116,7 @@ pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {
|
|||
DVec2::ZERO
|
||||
};
|
||||
|
||||
DAffine2::from_scale_angle_translation(scale, angle, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
|
||||
DAffine2::from_scale_angle_translation(scale, angle as f64, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
|
||||
}
|
||||
|
||||
/// Extract the current normalized pivot from the layer
|
||||
|
|
|
|||
|
|
@ -254,6 +254,50 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
}],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Load Image",
|
||||
category: "Structural",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Load Resource".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi)), NodeInput::Network(concrete!(String))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Decode Image".to_string(),
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
DocumentInputType {
|
||||
name: "api",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(WasmEditorApi)),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "path",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::value(TaggedValue::String("graphite:null".to_string()), false),
|
||||
},
|
||||
],
|
||||
outputs: vec![DocumentOutputType {
|
||||
name: "Image Frame",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
}],
|
||||
properties: node_properties::load_image_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Create Canvas",
|
||||
category: "Structural",
|
||||
|
|
@ -289,7 +333,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
name: "Canvas",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
}],
|
||||
properties: node_properties::input_properties,
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Draw Canvas",
|
||||
|
|
@ -345,7 +389,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
name: "Canvas",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
}],
|
||||
properties: node_properties::input_properties,
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Begin Scope",
|
||||
|
|
@ -490,7 +534,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Second", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("BlendMode", TaggedValue::BlendMode(BlendMode::Normal), false),
|
||||
DocumentInputType::value("Opacity", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("Opacity", TaggedValue::F32(100.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::blend_properties,
|
||||
|
|
@ -508,27 +552,27 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentInputType {
|
||||
name: "Shadows",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Midtones",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(50.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(50.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Highlights",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(100.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(100.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Output Minimums",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(0.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Output Maximums",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(100.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(100.), false),
|
||||
},
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
|
|
@ -552,32 +596,32 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentInputType {
|
||||
name: "Reds",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(40.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(40.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Yellows",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(60.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(60.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Greens",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(40.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(40.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Cyans",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(60.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(60.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Blues",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(20.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(20.), false),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "Magentas",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
default: NodeInput::value(TaggedValue::F64(80.), false),
|
||||
default: NodeInput::value(TaggedValue::F32(80.), false),
|
||||
},
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
|
|
@ -713,7 +757,10 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
name: "Memoize",
|
||||
category: "Structural",
|
||||
identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"),
|
||||
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
|
||||
inputs: vec![
|
||||
DocumentInputType::value("ShortCircut", TaggedValue::None, false),
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
|
|
@ -1136,7 +1183,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
category: "Gpu",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![1, 1, 0],
|
||||
outputs: vec![NodeOutput::new(2, 0)],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Extract Executor".to_string(),
|
||||
|
|
@ -1154,12 +1201,6 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("gpu_executor::RenderTextureNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Cache".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
|
@ -1367,9 +1408,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::HueSaturationNode<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Hue Shift", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Saturation Shift", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Lightness Shift", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Hue Shift", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Saturation Shift", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Lightness Shift", TaggedValue::F32(0.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_hsl_properties,
|
||||
|
|
@ -1380,8 +1421,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Brightness", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Contrast", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Brightness", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Contrast", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Use Legacy", TaggedValue::Bool(false), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
|
|
@ -1393,8 +1434,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Min Luminance", TaggedValue::F64(50.), false),
|
||||
DocumentInputType::value("Max Luminance", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("Min Luminance", TaggedValue::F32(50.), false),
|
||||
DocumentInputType::value("Max Luminance", TaggedValue::F32(100.), false),
|
||||
DocumentInputType::value("Luminance Calc", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
|
|
@ -1406,7 +1447,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Vibrance", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Vibrance", TaggedValue::F32(0.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_vibrance_properties,
|
||||
|
|
@ -1420,25 +1461,25 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
// Monochrome toggle
|
||||
DocumentInputType::value("Monochrome", TaggedValue::Bool(false), false),
|
||||
// Monochrome
|
||||
DocumentInputType::value("Red", TaggedValue::F64(40.), false),
|
||||
DocumentInputType::value("Green", TaggedValue::F64(40.), false),
|
||||
DocumentInputType::value("Blue", TaggedValue::F64(20.), false),
|
||||
DocumentInputType::value("Constant", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Red", TaggedValue::F32(40.), false),
|
||||
DocumentInputType::value("Green", TaggedValue::F32(40.), false),
|
||||
DocumentInputType::value("Blue", TaggedValue::F32(20.), false),
|
||||
DocumentInputType::value("Constant", TaggedValue::F32(0.), false),
|
||||
// Red output channel
|
||||
DocumentInputType::value("(Red) Red", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("(Red) Green", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Red) Blue", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Red) Constant", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Red) Red", TaggedValue::F32(100.), false),
|
||||
DocumentInputType::value("(Red) Green", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Red) Blue", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Red) Constant", TaggedValue::F32(0.), false),
|
||||
// Green output channel
|
||||
DocumentInputType::value("(Green) Red", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Green) Green", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("(Green) Blue", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Green) Constant", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Green) Red", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Green) Green", TaggedValue::F32(100.), false),
|
||||
DocumentInputType::value("(Green) Blue", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Green) Constant", TaggedValue::F32(0.), false),
|
||||
// Blue output channel
|
||||
DocumentInputType::value("(Blue) Red", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blue) Green", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blue) Blue", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("(Blue) Constant", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blue) Red", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blue) Green", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blue) Blue", TaggedValue::F32(100.), false),
|
||||
DocumentInputType::value("(Blue) Constant", TaggedValue::F32(0.), false),
|
||||
// Display-only properties (not used within the node)
|
||||
DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
||||
],
|
||||
|
|
@ -1456,50 +1497,50 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
// Mode
|
||||
DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), false),
|
||||
// Reds
|
||||
DocumentInputType::value("(Reds) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Reds) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Reds) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Reds) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Reds) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Reds) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Reds) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Reds) Black", TaggedValue::F32(0.), false),
|
||||
// Yellows
|
||||
DocumentInputType::value("(Yellows) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Yellows) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Yellows) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Yellows) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Yellows) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Yellows) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Yellows) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Yellows) Black", TaggedValue::F32(0.), false),
|
||||
// Greens
|
||||
DocumentInputType::value("(Greens) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Greens) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Greens) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Greens) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Greens) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Greens) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Greens) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Greens) Black", TaggedValue::F32(0.), false),
|
||||
// Cyans
|
||||
DocumentInputType::value("(Cyans) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Cyans) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Cyans) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Cyans) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Cyans) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Cyans) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Cyans) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Cyans) Black", TaggedValue::F32(0.), false),
|
||||
// Blues
|
||||
DocumentInputType::value("(Blues) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blues) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blues) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blues) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blues) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blues) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blues) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blues) Black", TaggedValue::F32(0.), false),
|
||||
// Magentas
|
||||
DocumentInputType::value("(Magentas) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Magentas) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Magentas) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Magentas) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Magentas) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Magentas) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Magentas) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Magentas) Black", TaggedValue::F32(0.), false),
|
||||
// Whites
|
||||
DocumentInputType::value("(Whites) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Whites) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Whites) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Whites) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Whites) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Whites) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Whites) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Whites) Black", TaggedValue::F32(0.), false),
|
||||
// Neutrals
|
||||
DocumentInputType::value("(Neutrals) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Neutrals) Black", TaggedValue::F32(0.), false),
|
||||
// Blacks
|
||||
DocumentInputType::value("(Blacks) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blacks) Magenta", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blacks) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blacks) Black", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blacks) Cyan", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blacks) Magenta", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blacks) Yellow", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("(Blacks) Black", TaggedValue::F32(0.), false),
|
||||
// Display-only properties (not used within the node)
|
||||
DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::Reds), false),
|
||||
],
|
||||
|
|
@ -1512,7 +1553,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Factor", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("Factor", TaggedValue::F32(100.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::multiply_opacity,
|
||||
|
|
@ -1523,7 +1564,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Value", TaggedValue::F64(4.), false),
|
||||
DocumentInputType::value("Value", TaggedValue::F32(4.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::posterize_properties,
|
||||
|
|
@ -1534,9 +1575,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
identifier: NodeImplementation::proto("graphene_core::raster::ExposureNode<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Exposure", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Gamma Correction", TaggedValue::F64(1.), false),
|
||||
DocumentInputType::value("Exposure", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Offset", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Gamma Correction", TaggedValue::F32(1.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::exposure_properties,
|
||||
|
|
@ -1546,8 +1587,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
category: "Math",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::AddParameterNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Input", TaggedValue::F64(0.), true),
|
||||
DocumentInputType::value("Addend", TaggedValue::F64(0.), true),
|
||||
DocumentInputType::value("Input", TaggedValue::F32(0.), true),
|
||||
DocumentInputType::value("Addend", TaggedValue::F32(0.), true),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Output", FrontendGraphDataType::Number)],
|
||||
properties: node_properties::add_properties,
|
||||
|
|
@ -1580,7 +1621,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentInputType::none(),
|
||||
DocumentInputType::value("Text", TaggedValue::String("hello world".to_string()), false),
|
||||
DocumentInputType::value("Font", TaggedValue::Font(Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into())), false),
|
||||
DocumentInputType::value("Size", TaggedValue::F64(24.), false),
|
||||
DocumentInputType::value("Size", TaggedValue::F32(24.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::node_section_font,
|
||||
|
|
@ -1592,7 +1633,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
inputs: vec![
|
||||
DocumentInputType::value("Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Rotation", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Rotation", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::value("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Pivot", TaggedValue::DVec2(DVec2::splat(0.5)), false),
|
||||
|
|
@ -1635,12 +1676,12 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Color", TaggedValue::OptionalColor(Some(Color::BLACK)), false),
|
||||
DocumentInputType::value("Weight", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Weight", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Dash Lengths", TaggedValue::VecF32(Vec::new()), false),
|
||||
DocumentInputType::value("Dash Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Dash Offset", TaggedValue::F32(0.), false),
|
||||
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt), false),
|
||||
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter), false),
|
||||
DocumentInputType::value("Miter Limit", TaggedValue::F64(4.), false),
|
||||
DocumentInputType::value("Miter Limit", TaggedValue::F32(4.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::stroke_properties,
|
||||
|
|
@ -1727,18 +1768,18 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeType> = Lazy::new(|| DocumentNodeTyp
|
|||
default: NodeInput::Network(concrete!(WasmEditorApi)),
|
||||
},
|
||||
DocumentInputType::value("Controller", TaggedValue::ImaginateController(Default::default()), false),
|
||||
DocumentInputType::value("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
|
||||
DocumentInputType::value("Seed", TaggedValue::F32(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
|
||||
DocumentInputType::value("Resolution", TaggedValue::OptionalDVec2(None), false),
|
||||
DocumentInputType::value("Samples", TaggedValue::U32(30), false),
|
||||
DocumentInputType::value("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
|
||||
DocumentInputType::value("Prompt Guidance", TaggedValue::F64(7.5), false),
|
||||
DocumentInputType::value("Prompt Guidance", TaggedValue::F32(7.5), false),
|
||||
DocumentInputType::value("Prompt", TaggedValue::String(String::new()), false),
|
||||
DocumentInputType::value("Negative Prompt", TaggedValue::String(String::new()), false),
|
||||
DocumentInputType::value("Adapt Input Image", TaggedValue::Bool(false), false),
|
||||
DocumentInputType::value("Image Creativity", TaggedValue::F64(66.), false),
|
||||
DocumentInputType::value("Image Creativity", TaggedValue::F32(66.), false),
|
||||
DocumentInputType::value("Masking Layer", TaggedValue::LayerPath(None), false),
|
||||
DocumentInputType::value("Inpaint", TaggedValue::Bool(true), false),
|
||||
DocumentInputType::value("Mask Blur", TaggedValue::F64(4.), false),
|
||||
DocumentInputType::value("Mask Blur", TaggedValue::F32(4.), false),
|
||||
DocumentInputType::value("Mask Starting Fill", TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false),
|
||||
DocumentInputType::value("Improve Faces", TaggedValue::Bool(false), false),
|
||||
DocumentInputType::value("Tiling", TaggedValue::Bool(false), false),
|
||||
|
|
@ -1767,7 +1808,9 @@ impl DocumentNodeType {
|
|||
|
||||
let inner_network = match &self.identifier {
|
||||
NodeImplementation::DocumentNode(network) => network.clone(),
|
||||
NodeImplementation::ProtoNode(ident) => {
|
||||
|
||||
NodeImplementation::ProtoNode(ident) => return DocumentNodeImplementation::Unresolved(ident.clone()),
|
||||
/*
|
||||
NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
|
|
@ -1785,23 +1828,10 @@ impl DocumentNodeType {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
}
|
||||
NodeImplementation::Extract => NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "ExtractNode".to_string(),
|
||||
implementation: DocumentNodeImplementation::Extract,
|
||||
inputs: self.inputs.iter().map(|i| NodeInput::Network(i.default.ty())).collect(),
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
*/
|
||||
NodeImplementation::Extract => return DocumentNodeImplementation::Extract,
|
||||
};
|
||||
|
||||
DocumentNodeImplementation::Network(inner_network)
|
||||
|
|
@ -1830,9 +1860,9 @@ impl DocumentNodeType {
|
|||
}
|
||||
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
|
|
@ -1927,7 +1957,7 @@ pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGrou
|
|||
network
|
||||
}
|
||||
|
||||
pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
|
||||
pub fn new_text_network(text: String, font: Font, size: f32) -> NodeNetwork {
|
||||
let text_generator = resolve_document_node_type("Text").expect("Text node does not exist");
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
|
||||
|
|
@ -1944,7 +1974,7 @@ pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
|
|||
NodeInput::Network(concrete!(WasmEditorApi)),
|
||||
NodeInput::value(TaggedValue::String(text), false),
|
||||
NodeInput::value(TaggedValue::Font(font), false),
|
||||
NodeInput::value(TaggedValue::F64(size), false),
|
||||
NodeInput::value(TaggedValue::F32(size), false),
|
||||
],
|
||||
DocumentNodeMetadata::position((0, 4)),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -624,6 +624,12 @@ pub fn blend_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
|||
vec![backdrop, blend_mode, LayoutGroup::Row { widgets: opacity }]
|
||||
}
|
||||
|
||||
pub fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let url = text_widget(document_node, node_id, 1, "Url", true);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: url }]
|
||||
}
|
||||
|
||||
pub fn output_properties(_document_node: &DocumentNode, _node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let output_type = context.executor.previous_output_type(context.layer_path);
|
||||
let raster_output_type = concrete!(ImageFrame<Color>);
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ impl TextToolData {
|
|||
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let network = new_text_network(String::new(), editing_text.font.clone(), editing_text.font_size);
|
||||
let network = new_text_network(String::new(), editing_text.font.clone(), editing_text.font_size as f32);
|
||||
|
||||
responses.add(Operation::AddFrame {
|
||||
path: self.layer_path.clone(),
|
||||
|
|
|
|||
|
|
@ -156,9 +156,6 @@ unsafe impl<T: StaticTypeSized> StaticType for *const [T] {
|
|||
unsafe impl<T: StaticTypeSized> StaticType for *mut [T] {
|
||||
type Static = *mut [<T as StaticTypeSized>::Static];
|
||||
}
|
||||
unsafe impl<'a, T: StaticTypeSized> StaticType for &'a [T] {
|
||||
type Static = &'static [<T as StaticTypeSized>::Static];
|
||||
}
|
||||
macro_rules! impl_slice {
|
||||
($($id:ident),*) => {
|
||||
$(
|
||||
|
|
@ -192,6 +189,9 @@ unsafe impl<'a, T: 'a + StaticType + ?Sized> StaticType for &'a T {
|
|||
unsafe impl<T: StaticTypeSized, const N: usize> StaticType for [T; N] {
|
||||
type Static = [<T as StaticTypeSized>::Static; N];
|
||||
}
|
||||
unsafe impl<T: StaticTypeSized> StaticType for [T] {
|
||||
type Static = [<T as StaticTypeSized>::Static];
|
||||
}
|
||||
|
||||
unsafe impl StaticType for dyn for<'i> DynAny<'_> + '_ {
|
||||
type Static = dyn DynAny<'static>;
|
||||
|
|
@ -271,7 +271,9 @@ impl_type!(Rc<T>);
|
|||
#[cfg(all(feature = "rc", feature = "alloc"))]
|
||||
use std::sync::Arc;
|
||||
#[cfg(all(feature = "rc", feature = "alloc"))]
|
||||
impl_type!(Arc<T>);
|
||||
unsafe impl<T: StaticType + ?Sized> StaticType for Arc<T> {
|
||||
type Static = Arc<<T as StaticType>::Static>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "glam")]
|
||||
use glam::*;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ async fn post_compile_spirv(State(state): State<Arc<AppState>>, Json(compile_req
|
|||
let result = compile_request.compile(state.compile_dir.path().to_str().expect("non utf8 tempdir path"), &path).map_err(|e| {
|
||||
eprintln!("compilation failed: {}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
});
|
||||
state.cache.write().unwrap().insert(compile_request, result.clone());
|
||||
result
|
||||
})?;
|
||||
state.cache.write().unwrap().insert(compile_request, Ok(result.clone()));
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,10 @@ license = "MIT OR Apache-2.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"dyn-any",
|
||||
"dyn-any/std",
|
||||
"alloc",
|
||||
"glam/std",
|
||||
"specta",
|
||||
"num-traits/std",
|
||||
"rustybuzz",
|
||||
]
|
||||
std = ["dyn-any", "dyn-any/std", "alloc", "glam/std", "specta", "num-traits/std", "rustybuzz"]
|
||||
default = ["async", "serde", "kurbo", "log", "std", "rand_chacha", "wasm"]
|
||||
log = ["dep:log"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"glam/serde",
|
||||
"bezier-rs/serde",
|
||||
"bezier-rs/serde",
|
||||
"base64",
|
||||
]
|
||||
serde = ["dep:serde", "glam/serde", "bezier-rs/serde", "bezier-rs/serde", "base64"]
|
||||
gpu = ["spirv-std", "glam/bytemuck", "dyn-any", "glam/libm"]
|
||||
async = ["async-trait", "alloc"]
|
||||
nightly = []
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ use dyn_any::StaticType;
|
|||
use dyn_any::StaticTypeSized;
|
||||
use glam::DAffine2;
|
||||
|
||||
use core::any::Any;
|
||||
use core::future::Future;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::pin::Pin;
|
||||
|
||||
use crate::text::FontCache;
|
||||
|
||||
|
|
@ -93,6 +96,7 @@ pub trait ApplicationIo {
|
|||
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||
None
|
||||
}
|
||||
fn load_resource<'a>(&self, url: impl AsRef<str>) -> Result<Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>, ApplicationError>;
|
||||
}
|
||||
|
||||
impl<T: ApplicationIo> ApplicationIo for &T {
|
||||
|
|
@ -110,6 +114,16 @@ impl<T: ApplicationIo> ApplicationIo for &T {
|
|||
fn gpu_executor(&self) -> Option<&T::Executor> {
|
||||
(**self).gpu_executor()
|
||||
}
|
||||
|
||||
fn load_resource<'a>(&self, url: impl AsRef<str>) -> Result<Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>, ApplicationError> {
|
||||
(**self).load_resource(url)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ApplicationError {
|
||||
NotFound,
|
||||
InvalidUrl,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use core::marker::PhantomData;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, NodeMut};
|
||||
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
|
||||
|
|
@ -16,6 +16,21 @@ impl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FnMutNode<T: FnMut(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, O: 'i, I: 'i> NodeMut<'i, I> for FnMutNode<T, I, O> {
|
||||
type MutOutput = O;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
self.0(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, I: 'i, O: 'i> FnMutNode<T, I, O> {
|
||||
pub fn new(f: T) -> Self {
|
||||
FnMutNode(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnNodeWithState<'i, T: Fn(I, &'i State) -> O, I, O, State: 'i>(T, State, PhantomData<(&'i O, I)>);
|
||||
impl<'i, I: 'i, O: 'i, State, T: Fn(I, &'i State) -> O + 'i> Node<'i, I> for FnNodeWithState<'i, T, I, O, State> {
|
||||
type Output = O;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,32 @@ pub trait Node<'i, Input: 'i>: 'i {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait NodeMut<'i, Input: 'i>: 'i {
|
||||
type MutOutput: 'i;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput;
|
||||
}
|
||||
|
||||
pub trait NodeOnce<'i, Input>
|
||||
where
|
||||
Input: 'i,
|
||||
{
|
||||
type OnceOutput: 'i;
|
||||
fn eval_once(self, input: Input) -> Self::OnceOutput;
|
||||
}
|
||||
|
||||
impl<'i, T: Node<'i, I>, I: 'i> NodeOnce<'i, I> for &'i T {
|
||||
type OnceOutput = T::Output;
|
||||
fn eval_once(self, input: I) -> Self::OnceOutput {
|
||||
(self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, T: Node<'i, I> + ?Sized, I: 'i> NodeMut<'i, I> for &'i T {
|
||||
type MutOutput = T::Output;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
(*self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod types;
|
||||
#[cfg(feature = "alloc")]
|
||||
|
|
@ -98,52 +124,40 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N {
|
||||
impl<'i, 's: 'i, I: 'i, N: Node<'i, I> + ?Sized> Node<'i, I> for &'i N {
|
||||
type Output = N::Output;
|
||||
fn eval(&'i self, input: I) -> N::Output {
|
||||
(*self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for Box<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for Box<N> {
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for alloc::sync::Arc<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for alloc::sync::Arc<N> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn Node<'i, I, Output = O> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
use core::pin::Pin;
|
||||
|
||||
use dyn_any::StaticTypeSized;
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {
|
||||
type Output = O;
|
||||
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
fn eval(&'i self, input: I) -> O {
|
||||
(**self).eval(input)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ where
|
|||
// TODO: This should return a reference to the cached cached_value
|
||||
// but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty acurate xD
|
||||
type Output = Pin<Box<dyn Future<Output = T> + 'i>>;
|
||||
fn eval(&'i self, input: ()) -> Self::Output {
|
||||
fn eval(&'i self, input: ()) -> Pin<Box<dyn Future<Output = T> + 'i>> {
|
||||
Box::pin(async move {
|
||||
if let Some(cached_value) = self.cache.take() {
|
||||
self.cache.set(Some(cached_value.clone()));
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ pub struct IntoNode<I, O> {
|
|||
_i: PhantomData<I>,
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
#[node_macro::node_fn(IntoNode<_I, _O>)]
|
||||
async fn into<_I, _O>(input: _I) -> _O
|
||||
where
|
||||
|
|
|
|||
|
|
@ -1,58 +1,121 @@
|
|||
use crate::raster::Color;
|
||||
use crate::raster::{Color, Pixel};
|
||||
use crate::Node;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
use num_traits::CheckedShr;
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::Float;
|
||||
|
||||
#[derive(Clone, Debug, DynAny, PartialEq)]
|
||||
#[derive(Clone, Copy, DynAny, PartialEq, Pod, Zeroable)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C, align(16))]
|
||||
pub struct Quantization {
|
||||
pub fn_index: usize,
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
pub d: f32,
|
||||
pub bits: u32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Quantization {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Quantization").field("a", &self.a).field("b", &self.b()).field("bits", &self.bits()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Quantization {
|
||||
pub fn new(a: f32, b: f32, bits: u32) -> Self {
|
||||
Self { a, b, bits, _padding: 0 }
|
||||
}
|
||||
|
||||
pub fn a(&self) -> f32 {
|
||||
self.a
|
||||
}
|
||||
|
||||
pub fn b(&self) -> f32 {
|
||||
self.b
|
||||
}
|
||||
|
||||
pub fn bits(&self) -> u32 {
|
||||
self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Quantization {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.fn_index.hash(state);
|
||||
self.a.to_bits().hash(state);
|
||||
self.b.to_bits().hash(state);
|
||||
self.c.to_bits().hash(state);
|
||||
self.d.to_bits().hash(state);
|
||||
self.bits().hash(state);
|
||||
self.a().to_bits().hash(state);
|
||||
self.b().to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quantization {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fn_index: Default::default(),
|
||||
a: 1.,
|
||||
b: Default::default(),
|
||||
c: Default::default(),
|
||||
d: Default::default(),
|
||||
}
|
||||
Self::new(1., 0., 8)
|
||||
}
|
||||
}
|
||||
|
||||
pub type QuantizationChannels = [Quantization; 4];
|
||||
#[repr(transparent)]
|
||||
#[derive(DynAny, Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
|
||||
pub struct PackedPixel(pub u32);
|
||||
|
||||
fn quantize(value: f32, quantization: &Quantization) -> f32 {
|
||||
let Quantization { fn_index, a, b, c, d } = quantization;
|
||||
match fn_index {
|
||||
1 => ((value + a) * d).abs().ln() * b + c,
|
||||
_ => a * value + b,
|
||||
}
|
||||
impl Pixel for PackedPixel {}
|
||||
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (((a * value) * ((1 << bits) - 1) as f32) as i32 + b) as u32;
|
||||
value.checked_shl(32 - bits - offset).unwrap_or(0)
|
||||
}*/
|
||||
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let b = quantization.b();
|
||||
let bits = quantization.bits();
|
||||
|
||||
// Calculate the quantized value
|
||||
// Scale the value by 'a' and the maximum quantization range
|
||||
let scaled_value = ((a * value) + b) * ((1 << bits) - 1) as f32;
|
||||
// Round the scaled value to the nearest integer
|
||||
let rounded_value = scaled_value.clamp(0., (1 << bits) as f32 - 1.) as u32;
|
||||
|
||||
// Shift the quantized value to the appropriate position based on the offset
|
||||
let shifted_value = rounded_value.checked_shl(32 - bits - offset).unwrap();
|
||||
|
||||
shifted_value as u32
|
||||
}
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (value << offset) >> (31 - bits);
|
||||
let value = value as i32 - b;
|
||||
(value as f32 / ((1 << bits) - 1) as f32) / a
|
||||
}*/
|
||||
|
||||
fn decode(value: f32, quantization: &Quantization) -> f32 {
|
||||
let Quantization { fn_index, a, b, c, d } = quantization;
|
||||
match fn_index {
|
||||
1 => -(-c / b).exp() * (a * d * (c / b).exp() - (value / b).exp()) / d,
|
||||
_ => (value - b) / a,
|
||||
}
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
|
||||
// Shift the value to the appropriate position based on the offset
|
||||
let shifted_value = value.checked_shr(32 - bits - offset).unwrap();
|
||||
|
||||
// Unpack the quantized value
|
||||
let unpacked_value = shifted_value & ((1 << bits) - 1); // Mask out the unnecessary bits
|
||||
let normalized_value = unpacked_value as f32 / ((1 << bits) - 1) as f32; // Normalize the value based on the quantization range
|
||||
let decoded_value = normalized_value - b;
|
||||
let original_value = decoded_value / a;
|
||||
|
||||
original_value
|
||||
}
|
||||
|
||||
pub struct QuantizeNode<Quantization> {
|
||||
|
|
@ -60,14 +123,22 @@ pub struct QuantizeNode<Quantization> {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(QuantizeNode)]
|
||||
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization.as_slice();
|
||||
let r = quantize(color.r(), &quant[0]);
|
||||
let g = quantize(color.g(), &quant[1]);
|
||||
let b = quantize(color.b(), &quant[2]);
|
||||
let a = quantize(color.a(), &quant[3]);
|
||||
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> PackedPixel {
|
||||
let quant = quantization;
|
||||
quantize_color(color, quant)
|
||||
}
|
||||
|
||||
Color::from_rgbaf32_unchecked(r, g, b, a)
|
||||
pub fn quantize_color(color: Color, quant: [Quantization; 4]) -> PackedPixel {
|
||||
let mut offset = 0;
|
||||
let r = quantize(color.r(), offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let g = quantize(color.g(), offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let b = quantize(color.b(), offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let a = quantize(color.a(), offset, quant[3]);
|
||||
|
||||
PackedPixel(r | g | b | a)
|
||||
}
|
||||
|
||||
pub struct DeQuantizeNode<Quantization> {
|
||||
|
|
@ -75,12 +146,53 @@ pub struct DeQuantizeNode<Quantization> {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(DeQuantizeNode)]
|
||||
fn dequantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization.as_slice();
|
||||
let r = decode(color.r(), &quant[0]);
|
||||
let g = decode(color.g(), &quant[1]);
|
||||
let b = decode(color.b(), &quant[2]);
|
||||
let a = decode(color.a(), &quant[3]);
|
||||
fn dequantize_fn<'a>(color: PackedPixel, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization;
|
||||
dequantize_color(color, quant)
|
||||
}
|
||||
|
||||
pub fn dequantize_color(color: PackedPixel, quant: [Quantization; 4]) -> Color {
|
||||
let mut offset = 0;
|
||||
let r = decode(color.0, offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let g = decode(color.0, offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let b = decode(color.0, offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let a = decode(color.0, offset, quant[3]);
|
||||
|
||||
Color::from_rgbaf32_unchecked(r, g, b, a)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quantize() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0.5, 0.5, 0.5, 0.5);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0x7f7f7f7f);
|
||||
let dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
//assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quantize_black() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0., 0., 0., 1.);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0xff);
|
||||
let dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_getters() {
|
||||
let quant = Quantization::new(1., 3., 8);
|
||||
assert_eq!(quant.a(), 1.);
|
||||
assert_eq!(quant.b(), 3.);
|
||||
assert_eq!(quant.bits(), 8);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub mod adjustments;
|
|||
pub mod bbox;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub mod brightness_contrast;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub mod brush_cache;
|
||||
pub mod color;
|
||||
pub mod discrete_srgb;
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ pub struct LevelsNode<InputStart, InputMid, InputEnd, OutputStart, OutputEnd> {
|
|||
|
||||
// From https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
|
||||
#[node_macro::node_fn(LevelsNode)]
|
||||
fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, output_start: f64, output_end: f64) -> Color {
|
||||
fn levels_node(color: Color, input_start: f32, input_mid: f32, input_end: f32, output_start: f32, output_end: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
// Input Range (Range: 0-1)
|
||||
|
|
@ -238,8 +238,8 @@ fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, o
|
|||
let input_highlights = (input_end / 100.) as f32;
|
||||
|
||||
// Output Range (Range: 0-1)
|
||||
let output_minimums = (output_start / 100.) as f32;
|
||||
let output_maximums = (output_end / 100.) as f32;
|
||||
let output_minimums = output_start / 100.;
|
||||
let output_maximums = output_end / 100.;
|
||||
|
||||
// Midtones interpolation factor between minimums and maximums (Range: 0-1)
|
||||
let midtones = output_minimums + (output_maximums - output_minimums) * input_midtones;
|
||||
|
|
@ -286,7 +286,7 @@ pub struct GrayscaleNode<Tint, Reds, Yellows, Greens, Cyans, Blues, Magentas> {
|
|||
// From <https://stackoverflow.com/a/55233732/775283>
|
||||
// Works the same for gamma and linear color
|
||||
#[node_macro::node_fn(GrayscaleNode)]
|
||||
fn grayscale_color_node(color: Color, tint: Color, reds: f64, yellows: f64, greens: f64, cyans: f64, blues: f64, magentas: f64) -> Color {
|
||||
fn grayscale_color_node(color: Color, tint: Color, reds: f32, yellows: f32, greens: f32, cyans: f32, blues: f32, magentas: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let reds = reds as f32 / 100.;
|
||||
|
|
@ -321,38 +321,29 @@ fn grayscale_color_node(color: Color, tint: Color, reds: f64, yellows: f64, gree
|
|||
color.to_linear_srgb()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub use hue_shift::HueSaturationNode;
|
||||
#[derive(Debug)]
|
||||
pub struct HueSaturationNode<Hue, Saturation, Lightness> {
|
||||
hue_shift: Hue,
|
||||
saturation_shift: Saturation,
|
||||
lightness_shift: Lightness,
|
||||
}
|
||||
|
||||
// TODO: Make this work on GPU so it can be removed from the wrapper module that excludes GPU (it doesn't work because of the modulo)
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
mod hue_shift {
|
||||
use super::*;
|
||||
#[node_macro::node_fn(HueSaturationNode)]
|
||||
fn hue_shift_color_node(color: Color, hue_shift: f32, saturation_shift: f32, lightness_shift: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HueSaturationNode<Hue, Saturation, Lightness> {
|
||||
hue_shift: Hue,
|
||||
saturation_shift: Saturation,
|
||||
lightness_shift: Lightness,
|
||||
}
|
||||
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
||||
|
||||
#[node_macro::node_fn(HueSaturationNode)]
|
||||
fn hue_shift_color_node(color: Color, hue_shift: f64, saturation_shift: f64, lightness_shift: f64) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
let color = Color::from_hsla(
|
||||
(hue + hue_shift / 360.) % 1.,
|
||||
// TODO: Improve the way saturation works (it's slightly off)
|
||||
(saturation + saturation_shift / 100.).clamp(0., 1.),
|
||||
// TODO: Fix the way lightness works (it's very off)
|
||||
(lightness + lightness_shift / 100.).clamp(0., 1.),
|
||||
alpha,
|
||||
);
|
||||
|
||||
let [hue, saturation, lightness, alpha] = color.to_hsla();
|
||||
|
||||
let color = Color::from_hsla(
|
||||
(hue + hue_shift as f32 / 360.) % 1.,
|
||||
// TODO: Improve the way saturation works (it's slightly off)
|
||||
(saturation + saturation_shift as f32 / 100.).clamp(0., 1.),
|
||||
// TODO: Fix the way lightness works (it's very off)
|
||||
(lightness + lightness_shift as f32 / 100.).clamp(0., 1.),
|
||||
alpha,
|
||||
);
|
||||
|
||||
color.to_linear_srgb()
|
||||
}
|
||||
color.to_linear_srgb()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -388,9 +379,9 @@ pub struct ThresholdNode<MinLuminance, MaxLuminance, LuminanceCalc> {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(ThresholdNode)]
|
||||
fn threshold_node(color: Color, min_luminance: f64, max_luminance: f64, luminance_calc: LuminanceCalculation) -> Color {
|
||||
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
|
||||
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
|
||||
fn threshold_node(color: Color, min_luminance: f32, max_luminance: f32, luminance_calc: LuminanceCalculation) -> Color {
|
||||
let min_luminance = Color::srgb_to_linear(min_luminance / 100.);
|
||||
let max_luminance = Color::srgb_to_linear(max_luminance / 100.);
|
||||
|
||||
let luminance = match luminance_calc {
|
||||
LuminanceCalculation::SRGB => color.luminance_srgb(),
|
||||
|
|
@ -414,7 +405,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 {
|
||||
blend_colors(input.0, input.1, blend_mode, opacity as f32 / 100.)
|
||||
}
|
||||
|
||||
|
|
@ -470,8 +461,8 @@ pub struct VibranceNode<Vibrance> {
|
|||
// Modified from https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
|
||||
// The results of this implementation are very close to correct, but not quite perfect
|
||||
#[node_macro::node_fn(VibranceNode)]
|
||||
fn vibrance_node(color: Color, vibrance: f64) -> Color {
|
||||
let vibrance = vibrance as f32 / 100.;
|
||||
fn vibrance_node(color: Color, vibrance: f32) -> Color {
|
||||
let vibrance = vibrance / 100.;
|
||||
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
|
||||
// So this scales the 0% to -50% range to 0% to -100%.
|
||||
let slowed_vibrance = if vibrance >= 0. { vibrance } else { vibrance * 0.5 };
|
||||
|
|
@ -562,22 +553,22 @@ pub struct ChannelMixerNode<Monochrome, MonochromeR, MonochromeG, MonochromeB, M
|
|||
fn channel_mixer_node(
|
||||
color: Color,
|
||||
monochrome: bool,
|
||||
monochrome_r: f64,
|
||||
monochrome_g: f64,
|
||||
monochrome_b: f64,
|
||||
monochrome_c: f64,
|
||||
red_r: f64,
|
||||
red_g: f64,
|
||||
red_b: f64,
|
||||
red_c: f64,
|
||||
green_r: f64,
|
||||
green_g: f64,
|
||||
green_b: f64,
|
||||
green_c: f64,
|
||||
blue_r: f64,
|
||||
blue_g: f64,
|
||||
blue_b: f64,
|
||||
blue_c: f64,
|
||||
monochrome_r: f32,
|
||||
monochrome_g: f32,
|
||||
monochrome_b: f32,
|
||||
monochrome_c: f32,
|
||||
red_r: f32,
|
||||
red_g: f32,
|
||||
red_b: f32,
|
||||
red_c: f32,
|
||||
green_r: f32,
|
||||
green_g: f32,
|
||||
green_b: f32,
|
||||
green_c: f32,
|
||||
blue_r: f32,
|
||||
blue_g: f32,
|
||||
blue_b: f32,
|
||||
blue_c: f32,
|
||||
) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
|
|
@ -699,42 +690,42 @@ pub struct SelectiveColorNode<Absolute, RC, RM, RY, RK, YC, YM, YY, YK, GC, GM,
|
|||
fn selective_color_node(
|
||||
color: Color,
|
||||
mode: RelativeAbsolute,
|
||||
r_c: f64,
|
||||
r_m: f64,
|
||||
r_y: f64,
|
||||
r_k: f64,
|
||||
y_c: f64,
|
||||
y_m: f64,
|
||||
y_y: f64,
|
||||
y_k: f64,
|
||||
g_c: f64,
|
||||
g_m: f64,
|
||||
g_y: f64,
|
||||
g_k: f64,
|
||||
c_c: f64,
|
||||
c_m: f64,
|
||||
c_y: f64,
|
||||
c_k: f64,
|
||||
b_c: f64,
|
||||
b_m: f64,
|
||||
b_y: f64,
|
||||
b_k: f64,
|
||||
m_c: f64,
|
||||
m_m: f64,
|
||||
m_y: f64,
|
||||
m_k: f64,
|
||||
w_c: f64,
|
||||
w_m: f64,
|
||||
w_y: f64,
|
||||
w_k: f64,
|
||||
n_c: f64,
|
||||
n_m: f64,
|
||||
n_y: f64,
|
||||
n_k: f64,
|
||||
k_c: f64,
|
||||
k_m: f64,
|
||||
k_y: f64,
|
||||
k_k: f64,
|
||||
r_c: f32,
|
||||
r_m: f32,
|
||||
r_y: f32,
|
||||
r_k: f32,
|
||||
y_c: f32,
|
||||
y_m: f32,
|
||||
y_y: f32,
|
||||
y_k: f32,
|
||||
g_c: f32,
|
||||
g_m: f32,
|
||||
g_y: f32,
|
||||
g_k: f32,
|
||||
c_c: f32,
|
||||
c_m: f32,
|
||||
c_y: f32,
|
||||
c_k: f32,
|
||||
b_c: f32,
|
||||
b_m: f32,
|
||||
b_y: f32,
|
||||
b_k: f32,
|
||||
m_c: f32,
|
||||
m_m: f32,
|
||||
m_y: f32,
|
||||
m_k: f32,
|
||||
w_c: f32,
|
||||
w_m: f32,
|
||||
w_y: f32,
|
||||
w_k: f32,
|
||||
n_c: f32,
|
||||
n_m: f32,
|
||||
n_y: f32,
|
||||
n_k: f32,
|
||||
k_c: f32,
|
||||
k_m: f32,
|
||||
k_y: f32,
|
||||
k_k: f32,
|
||||
) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
|
|
@ -784,7 +775,7 @@ fn selective_color_node(
|
|||
// Skip this color parameter group...
|
||||
// ...if it's unchanged from the default of zero offset on all CMYK paramters, or...
|
||||
// ...if this pixel's color isn't in the range affected by this color parameter group
|
||||
if (c < f64::EPSILON && m < f64::EPSILON && y < f64::EPSILON && k < f64::EPSILON) || (!pixel_color_range(color_parameter_group)) {
|
||||
if (c < f32::EPSILON && m < f32::EPSILON && y < f32::EPSILON && k < f32::EPSILON) || (!pixel_color_range(color_parameter_group)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
|
@ -816,7 +807,7 @@ pub struct OpacityNode<O> {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(OpacityNode)]
|
||||
fn image_opacity(color: Color, opacity_multiplier: f64) -> Color {
|
||||
fn image_opacity(color: Color, opacity_multiplier: f32) -> Color {
|
||||
let opacity_multiplier = opacity_multiplier as f32 / 100.;
|
||||
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
|
||||
}
|
||||
|
|
@ -829,7 +820,7 @@ pub struct PosterizeNode<P> {
|
|||
// Based on http://www.axiomx.com/posterize.htm
|
||||
// This algorithm is perfectly accurate.
|
||||
#[node_macro::node_fn(PosterizeNode)]
|
||||
fn posterize(color: Color, posterize_value: f64) -> Color {
|
||||
fn posterize(color: Color, posterize_value: f32) -> Color {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let posterize_value = posterize_value as f32;
|
||||
|
|
@ -850,7 +841,7 @@ pub struct ExposureNode<Exposure, Offset, GammaCorrection> {
|
|||
|
||||
// Based on https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[node_macro::node_fn(ExposureNode)]
|
||||
fn exposure(color: Color, exposure: f64, offset: f64, gamma_correction: f64) -> Color {
|
||||
fn exposure(color: Color, exposure: f32, offset: f32, gamma_correction: f32) -> Color {
|
||||
let adjusted = color
|
||||
// Exposure
|
||||
.map_rgb(|c: f32| c * 2_f32.powf(exposure as f32))
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ pub struct BrushPlan {
|
|||
}
|
||||
|
||||
#[derive(Debug, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushCache {
|
||||
inner: Arc<Mutex<BrushCacheImpl>>,
|
||||
proto: bool,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use core::marker::PhantomData;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, NodeMut};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComposeNode<First, Second, I> {
|
||||
first: First,
|
||||
second: Second,
|
||||
|
|
@ -21,12 +21,20 @@ where
|
|||
second.eval(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input>
|
||||
impl<'i, 'f: 'i, 's: 'i, Input: 'i, First, Second> NodeMut<'i, Input> for ComposeNode<First, Second, Input>
|
||||
where
|
||||
First: Node<'i, Input>,
|
||||
Second: Node<'i, <First as Node<'i, Input>>::Output>,
|
||||
Second: NodeMut<'i, <First as Node<'i, Input>>::Output> + 'i,
|
||||
{
|
||||
type MutOutput = <Second as NodeMut<'i, <First as Node<'i, Input>>::Output>>::MutOutput;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput {
|
||||
let arg = self.first.eval(input);
|
||||
let second = &mut self.second;
|
||||
second.eval_mut(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input> {
|
||||
pub const fn new(first: First, second: Second) -> Self {
|
||||
ComposeNode::<First, Second, Input> { first, second, phantom: PhantomData }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub struct TextGenerator<Text, FontName, Size> {
|
|||
}
|
||||
|
||||
#[node_fn(TextGenerator)]
|
||||
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
||||
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f32) -> crate::vector::VectorData {
|
||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
|
||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size as f64, None))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@ pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||
pub(crate) fn transform_vector_data<Data: TransformMut>(mut data: Data, translate: DVec2, rotate: f32, scale: DVec2, shear: DVec2, pivot: DVec2) -> Data {
|
||||
let pivot = DAffine2::from_translation(data.local_pivot(pivot));
|
||||
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
let data_transform = data.transform_mut();
|
||||
*data_transform = modification * (*data_transform);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,21 +53,21 @@ pub struct SetStrokeNode<Color, Weight, DashLengths, DashOffset, LineCap, LineJo
|
|||
fn set_vector_data_stroke(
|
||||
mut vector_data: VectorData,
|
||||
color: Option<Color>,
|
||||
weight: f64,
|
||||
weight: f32,
|
||||
dash_lengths: Vec<f32>,
|
||||
dash_offset: f64,
|
||||
dash_offset: f32,
|
||||
line_cap: super::style::LineCap,
|
||||
line_join: super::style::LineJoin,
|
||||
miter_limit: f64,
|
||||
miter_limit: f32,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_stroke(Stroke {
|
||||
color,
|
||||
weight,
|
||||
weight: weight as f64,
|
||||
dash_lengths,
|
||||
dash_offset,
|
||||
dash_offset: dash_offset as f64,
|
||||
line_cap,
|
||||
line_join,
|
||||
line_join_miter_limit: miter_limit,
|
||||
line_join_miter_limit: miter_limit as f64,
|
||||
});
|
||||
vector_data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -597,7 +597,6 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"js-sys",
|
||||
"kurbo",
|
||||
"log",
|
||||
"node-macro",
|
||||
|
|
@ -610,7 +609,6 @@ dependencies = [
|
|||
"specta",
|
||||
"spin",
|
||||
"spirv-std",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ impl SpirVCompiler for GpuCompiler {
|
|||
|
||||
pub fn serialize_gpu(networks: &[ProtoNetwork], io: &ShaderIO) -> anyhow::Result<String> {
|
||||
fn nid(id: &u64) -> String {
|
||||
format!("n{id}")
|
||||
format!("n{id:0x}")
|
||||
}
|
||||
|
||||
dbg!(&io);
|
||||
|
|
@ -151,13 +151,13 @@ pub fn serialize_gpu(networks: &[ProtoNetwork], io: &ShaderIO) -> anyhow::Result
|
|||
}
|
||||
for (i, id) in network.inputs.iter().enumerate() {
|
||||
let Some((_, node)) = network.nodes.iter().find(|(i, _)| i == id) else {
|
||||
anyhow::bail!("Input node not found");
|
||||
};
|
||||
anyhow::bail!("Input node not found");
|
||||
};
|
||||
let fqn = &node.identifier.name;
|
||||
let id = nid(id);
|
||||
let node = Node {
|
||||
id: id.clone(),
|
||||
index: i,
|
||||
index: i + 2,
|
||||
fqn: fqn.to_string().split('<').next().unwrap().to_owned(),
|
||||
args: node.construction_args.new_function_args(),
|
||||
};
|
||||
|
|
@ -202,7 +202,7 @@ pub fn serialize_gpu(networks: &[ProtoNetwork], io: &ShaderIO) -> anyhow::Result
|
|||
context.insert("input_nodes", &input_nodes);
|
||||
context.insert("output_nodes", &output_nodes);
|
||||
context.insert("nodes", &nodes);
|
||||
context.insert("compute_threads", &64);
|
||||
context.insert("compute_threads", "12, 8");
|
||||
Ok(tera.render("spirv", &context)?)
|
||||
}
|
||||
|
||||
|
|
@ -215,9 +215,13 @@ pub fn compile(dir: &Path) -> Result<spirv_builder::CompileResult, spirv_builder
|
|||
.preserve_bindings(true)
|
||||
.release(true)
|
||||
.spirv_metadata(SpirvMetadata::Full)
|
||||
//.extra_arg("no-early-report-zombies")
|
||||
//.extra_arg("no-infer-storage-classes")
|
||||
//.extra_arg("spirt-passes=qptr")
|
||||
//.scalar_block_layout(true)
|
||||
.relax_logical_pointer(true)
|
||||
//.capability(spirv_builder::Capability::Float64)
|
||||
//.capability(spirv_builder::Capability::VariablePointersStorageBuffer)
|
||||
.extra_arg("no-early-report-zombies")
|
||||
.extra_arg("no-infer-storage-classes")
|
||||
.extra_arg("spirt-passes=qptr")
|
||||
.build()?;
|
||||
|
||||
Ok(result)
|
||||
|
|
|
|||
|
|
@ -19,20 +19,18 @@ extern crate spirv_std;
|
|||
{{input}},
|
||||
{% endfor %}
|
||||
) {
|
||||
use graphene_core::Node;
|
||||
use graphene_core::{Node, NodeMut};
|
||||
use graphene_core::raster::adjustments::{BlendMode, BlendNode};
|
||||
use graphene_core::Color;
|
||||
|
||||
/*
|
||||
{% for input in input_nodes %}
|
||||
let i{{input.index}} = graphene_core::value::CopiedNode::new(i{{input.index}});
|
||||
let _i{{input.index}} = graphene_core::value::CopiedNode::new(*i{{input.index}});
|
||||
let _{{input.id}} = {{input.fqn}}::new({% for arg in input.args %}{{arg}}, {% endfor %});
|
||||
let {{input.id}} = graphene_core::structural::ComposeNode::new(i{{input.index}}, _{{input.id}});
|
||||
let {{input.id}} = graphene_core::structural::ComposeNode::new(_i{{input.index}}, _{{input.id}});
|
||||
{% endfor %}
|
||||
*/
|
||||
|
||||
{% for node in nodes %}
|
||||
let {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %});
|
||||
let mut {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %});
|
||||
{% endfor %}
|
||||
|
||||
{% for output in output_nodes %}
|
||||
|
|
|
|||
|
|
@ -357,12 +357,23 @@ where
|
|||
|
||||
/// A struct representing a compute pipeline.
|
||||
pub struct PipelineLayout<E: GpuExecutor + ?Sized> {
|
||||
pub shader: E::ShaderHandle,
|
||||
pub shader: Arc<E::ShaderHandle>,
|
||||
pub entry_point: String,
|
||||
pub bind_group: Bindgroup<E>,
|
||||
pub bind_group: Arc<Bindgroup<E>>,
|
||||
pub output_buffer: Arc<ShaderInput<E>>,
|
||||
}
|
||||
|
||||
impl<E: GpuExecutor + ?Sized> Clone for PipelineLayout<E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
shader: self.shader.clone(),
|
||||
entry_point: self.entry_point.clone(),
|
||||
bind_group: self.bind_group.clone(),
|
||||
output_buffer: self.output_buffer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for PipelineLayout<E>
|
||||
where
|
||||
E::Static: GpuExecutor,
|
||||
|
|
@ -457,9 +468,9 @@ pub struct CreatePipelineLayoutNode<_E, EntryPoint, Bindgroup, OutputBuffer> {
|
|||
#[node_macro::node_fn(CreatePipelineLayoutNode<_E>)]
|
||||
async fn create_pipeline_layout_node<_E: GpuExecutor>(shader: _E::ShaderHandle, entry_point: String, bind_group: Bindgroup<_E>, output_buffer: Arc<ShaderInput<_E>>) -> PipelineLayout<_E> {
|
||||
PipelineLayout {
|
||||
shader,
|
||||
shader: shader.into(),
|
||||
entry_point,
|
||||
bind_group,
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer,
|
||||
}
|
||||
}
|
||||
|
|
@ -520,6 +531,7 @@ where
|
|||
#[node_macro::node_fn(RenderTextureNode)]
|
||||
async fn render_texture_node<'a: 'input, E: 'a + GpuExecutor>(image: ShaderInputFrame<E>, surface: Arc<SurfaceHandle<E::Surface>>, executor: &'a E) -> SurfaceFrame {
|
||||
let surface_id = surface.surface_id;
|
||||
log::trace!("rendering to surface {:?}", surface_id);
|
||||
|
||||
executor.create_render_pass(image.shader_input, surface).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl DocumentNode {
|
|||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
}
|
||||
} else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node");
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -654,10 +654,13 @@ impl NodeNetwork {
|
|||
|
||||
/// Recursively dissolve non-primitive document nodes and return a single flattened network of nodes.
|
||||
pub fn flatten_with_fns(&mut self, node: NodeId, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, gen_id: impl Fn() -> NodeId + Copy) {
|
||||
let (id, mut node) = self
|
||||
self.resolve_extract_nodes();
|
||||
let Some((id, mut node)) = self
|
||||
.nodes
|
||||
.remove_entry(&node)
|
||||
.unwrap_or_else(|| panic!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self));
|
||||
.remove_entry(&node) else {
|
||||
warn!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self);
|
||||
return;
|
||||
};
|
||||
|
||||
if self.disabled.contains(&id) {
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
|
|
@ -665,11 +668,12 @@ impl NodeNetwork {
|
|||
self.nodes.insert(id, node);
|
||||
return;
|
||||
}
|
||||
log::debug!("Flattening node {:?}", &node.name);
|
||||
|
||||
// replace value inputs with value nodes
|
||||
for input in &mut node.inputs {
|
||||
// Skip inputs that are already value nodes
|
||||
if node.implementation == DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()) {
|
||||
if node.implementation == DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -690,7 +694,7 @@ impl NodeNetwork {
|
|||
DocumentNode {
|
||||
name: "Value".into(),
|
||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()),
|
||||
path,
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -706,6 +710,8 @@ impl NodeNetwork {
|
|||
}
|
||||
|
||||
if let DocumentNodeImplementation::Network(mut inner_network) = node.implementation {
|
||||
// Resolve all extract nodes in the inner network
|
||||
inner_network.resolve_extract_nodes();
|
||||
// Connect all network inputs to either the parent network nodes, or newly created value nodes.
|
||||
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
|
||||
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
|
||||
|
|
@ -717,8 +723,10 @@ impl NodeNetwork {
|
|||
assert_eq!(
|
||||
node.inputs.len(),
|
||||
inner_network.inputs.len(),
|
||||
"The number of inputs to the node and the inner network must be the same {}",
|
||||
node.name
|
||||
"The number of inputs to the node and the inner network must be the same for {}. The node has {:?} inputs, the network has {:?} inputs.",
|
||||
node.name,
|
||||
node.inputs,
|
||||
inner_network.inputs
|
||||
);
|
||||
// Match the document node input and the inputs of the inner network
|
||||
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
||||
|
|
@ -829,21 +837,30 @@ impl NodeNetwork {
|
|||
self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract));
|
||||
|
||||
for (_, node) in &mut extraction_nodes {
|
||||
log::info!("extraction network: {:#?}", &self);
|
||||
if let DocumentNodeImplementation::Extract = node.implementation {
|
||||
assert_eq!(node.inputs.len(), 1);
|
||||
log::debug!("Resolving extract node {:?}", node);
|
||||
let NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else {
|
||||
panic!("Extract node has no input");
|
||||
panic!("Extract node has no input, inputs: {:?}", node.inputs);
|
||||
};
|
||||
assert_eq!(output_index, 0);
|
||||
// TODO: check if we can readd lambda checking
|
||||
let mut input_node = self.nodes.remove(&node_id).unwrap();
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into());
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into());
|
||||
if let Some(input) = input_node.inputs.get_mut(0) {
|
||||
*input = match &input {
|
||||
NodeInput::Node { .. } => NodeInput::Network(generic!(T)),
|
||||
ni => NodeInput::Network(ni.ty()),
|
||||
};
|
||||
}
|
||||
|
||||
for input in input_node.inputs.iter_mut() {
|
||||
match input {
|
||||
NodeInput::Node { .. } | NodeInput::Value { .. } => *input = NodeInput::Network(generic!(T)),
|
||||
_ => (),
|
||||
if let NodeInput::Node { .. } = input {
|
||||
*input = NodeInput::Network(generic!(T))
|
||||
}
|
||||
}
|
||||
log::debug!("Extract node {:?} resolved to {:?}", node, input_node);
|
||||
node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)];
|
||||
}
|
||||
}
|
||||
|
|
@ -1108,7 +1125,7 @@ mod test {
|
|||
tagged_value: TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
}],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()),
|
||||
path: Some(vec![1, 4]),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ pub enum TaggedValue {
|
|||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
Font(graphene_core::text::Font),
|
||||
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
BrushCache(BrushCache),
|
||||
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
|
||||
DocumentNode(DocumentNode),
|
||||
|
|
@ -193,6 +194,7 @@ impl<'a> TaggedValue {
|
|||
TaggedValue::F64(x) => x.to_string() + "_f64",
|
||||
TaggedValue::Bool(x) => x.to_string(),
|
||||
TaggedValue::BlendMode(blend_mode) => "BlendMode::".to_string() + &blend_mode.to_string(),
|
||||
TaggedValue::Color(color) => "graphene_core::Color::from_rgbaf32_unchecked(0.,0.,0.,1.)".to_string(),
|
||||
_ => panic!("Cannot convert to primitive string"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ pub struct Compiler {}
|
|||
|
||||
impl Compiler {
|
||||
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
println!("flattening");
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
network.remove_redundant_id_nodes();
|
||||
network.resolve_extract_nodes();
|
||||
network.remove_dead_nodes();
|
||||
let proto_networks = network.into_proto_networks();
|
||||
proto_networks.map(move |mut proto_network| {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ pub type LocalFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>
|
|||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
|
||||
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
|
||||
pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n)>;
|
||||
pub type TypeErasedRef<'n> = &'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n);
|
||||
pub type TypeErasedBox<'n> = Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>;
|
||||
pub type TypeErasedPinned<'n> = Pin<Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>>;
|
||||
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
|
||||
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
|
||||
pub type TypeErasedBox<'n> = Box<TypeErasedNode<'n>>;
|
||||
pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
|
||||
|
||||
pub type NodeConstructor = for<'a> fn(Vec<Arc<NodeContainer>>) -> DynFuture<'static, TypeErasedBox<'static>>;
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ impl Hash for ConstructionArgs {
|
|||
impl ConstructionArgs {
|
||||
pub fn new_function_args(&self) -> Vec<String> {
|
||||
match self {
|
||||
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("&n{}", n.0)).collect(),
|
||||
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{:0x}", n.0)).collect(),
|
||||
ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
|
||||
ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
|
||||
}
|
||||
|
|
@ -248,7 +248,7 @@ impl ProtoNode {
|
|||
|
||||
pub fn value(value: ConstructionArgs, path: Vec<NodeId>) -> Self {
|
||||
Self {
|
||||
identifier: NodeIdentifier::new("graphene_core::value::ValueNode"),
|
||||
identifier: NodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||
construction_args: value,
|
||||
input: ProtoNodeInput::None,
|
||||
document_node_path: path,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
[package]
|
||||
name = "graphene-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "CLI interface for the graphene language"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
gpu = [
|
||||
"interpreted-executor/gpu",
|
||||
"graphene-std/gpu",
|
||||
"graphene-core/gpu",
|
||||
"wgpu-executor",
|
||||
"gpu-executor",
|
||||
]
|
||||
default = ["wgpu"]
|
||||
wgpu = ["wgpu-executor", "gpu", "graphene-std/wgpu"]
|
||||
wayland = ["graphene-std/wayland"]
|
||||
profiling = ["wgpu-executor/profiling"]
|
||||
passthrough = ["wgpu-executor/passthrough"]
|
||||
quantization = ["graphene-std/quantization"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
bitflags = "1.2.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
bezier-rs = { path = "../../libraries/bezier-rs" }
|
||||
glam = { version = "0.22", features = ["serde"] }
|
||||
|
||||
# Node graph
|
||||
graphene-std = { path = "../gstd" }
|
||||
image = { version = "0.24", default-features = false, features = [
|
||||
"bmp",
|
||||
"png",
|
||||
] }
|
||||
graph-craft = { path = "../graph-craft" }
|
||||
wgpu-executor = { path = "../wgpu-executor", optional = true }
|
||||
gpu-executor = { path = "../gpu-executor", optional = true }
|
||||
interpreted-executor = { path = "../interpreted-executor" }
|
||||
dyn-any = { path = "../../libraries/dyn-any" }
|
||||
graphene-core = { path = "../gcore" }
|
||||
future-executor = { path = "../future-executor", optional = true }
|
||||
|
||||
wasm-bindgen = { version = "0.2.86", optional = true }
|
||||
futures = "0.3.28"
|
||||
fern = { version = "0.6.2", features = ["colored"] }
|
||||
chrono = "0.4.26"
|
||||
tokio = { version = "1.28.2", features = ["macros", "rt"] }
|
||||
wgpu = "0.16.1"
|
||||
|
||||
[dependencies.document-legacy]
|
||||
path = "../../document-legacy"
|
||||
package = "graphite-document-legacy"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.8.4"
|
||||
test-case = "2.1"
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use std::{collections::HashMap, error::Error, sync::Arc};
|
||||
|
||||
use document_legacy::{
|
||||
document::Document,
|
||||
layers::layer_info::{LayerData, LayerDataType},
|
||||
};
|
||||
use futures::executor::block_on;
|
||||
use graph_craft::{
|
||||
concrete,
|
||||
document::{value::TaggedValue, *},
|
||||
graphene_compiler::{Compiler, Executor},
|
||||
imaginate_input::ImaginatePreferences,
|
||||
NodeIdentifier, Type, TypeDescriptor,
|
||||
};
|
||||
use graphene_core::{
|
||||
application_io::{self, ApplicationIo, NodeGraphUpdateSender},
|
||||
raster::ImageFrame,
|
||||
text::FontCache,
|
||||
Cow,
|
||||
};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
|
||||
struct UpdateLogger {}
|
||||
|
||||
impl NodeGraphUpdateSender for UpdateLogger {
|
||||
fn send(&self, message: graphene_core::application_io::NodeGraphUpdateMessage) {
|
||||
println!("{:?}", message);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
init_logging();
|
||||
|
||||
let document_path = std::env::args().nth(1).expect("No document path provided");
|
||||
|
||||
let image_path = std::env::args().nth(2);
|
||||
|
||||
let document_string = std::fs::read_to_string(&document_path).expect("Failed to read document");
|
||||
|
||||
let executor = create_executor(document_string)?;
|
||||
println!("creating gpu context",);
|
||||
let mut application_io = block_on(WasmApplicationIo::new());
|
||||
if let Some(image_path) = image_path {
|
||||
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
|
||||
}
|
||||
|
||||
let device = application_io.gpu_executor().unwrap().context.device.clone();
|
||||
std::thread::spawn(move || loop {
|
||||
std::thread::sleep(std::time::Duration::from_nanos(10));
|
||||
device.poll(wgpu::Maintain::Poll);
|
||||
});
|
||||
|
||||
let editor_api = WasmEditorApi {
|
||||
image_frame: None,
|
||||
font_cache: &FontCache::default(),
|
||||
application_io: &application_io,
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
};
|
||||
|
||||
loop {
|
||||
//println!("executing");
|
||||
let result = (&executor).execute(editor_api.clone()).await?;
|
||||
//println!("result: {:?}", result);
|
||||
std::thread::sleep(std::time::Duration::from_millis(16));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_logging() {
|
||||
let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);
|
||||
fern::Dispatch::new()
|
||||
.chain(std::io::stdout())
|
||||
.level_for("iced", log::LevelFilter::Trace)
|
||||
.level_for("wgpu", log::LevelFilter::Debug)
|
||||
.level(log::LevelFilter::Trace)
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"[{}]{} {}",
|
||||
// This will color the log level only, not the whole line. Just a touch.
|
||||
colors.color(record.level()),
|
||||
chrono::Utc::now().format("[%Y-%m-%d %H:%M:%S]"),
|
||||
message
|
||||
))
|
||||
})
|
||||
.apply()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn create_executor(document_string: String) -> Result<DynamicExecutor, Box<dyn Error>> {
|
||||
let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document");
|
||||
let document = serde_json::from_value::<Document>(document["document_legacy"].clone()).expect("Failed to parse document");
|
||||
let Some(LayerDataType::Layer(ref node_graph)) = document.root.iter().find(|layer| matches!(layer.data, LayerDataType::Layer(_))).map(|x|&x.data) else { panic!("failed to extract node graph from docmuent") };
|
||||
let network = &node_graph.network;
|
||||
let wrapped_network = wrap_network_in_scope(network.clone());
|
||||
let compiler = Compiler {};
|
||||
let protograph = compiler.compile_single(wrapped_network, true)?;
|
||||
let executor = block_on(DynamicExecutor::new(protograph))?;
|
||||
Ok(executor)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "wayland"), ignore)]
|
||||
async fn grays_scale() {
|
||||
let document_string = include_str!("../test_files/gray.graphite");
|
||||
let executor = create_executor(document_string.to_string()).unwrap();
|
||||
let editor_api = WasmEditorApi {
|
||||
image_frame: None,
|
||||
font_cache: &FontCache::default(),
|
||||
application_io: &block_on(WasmApplicationIo::new()),
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
};
|
||||
let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "wayland"), ignore)]
|
||||
async fn hue() {
|
||||
let document_string = include_str!("../test_files/hue.graphite");
|
||||
let executor = create_executor(document_string.to_string()).unwrap();
|
||||
let editor_api = WasmEditorApi {
|
||||
image_frame: None,
|
||||
font_cache: &FontCache::default(),
|
||||
application_io: &block_on(WasmApplicationIo::new()),
|
||||
node_graph_message_sender: &UpdateLogger {},
|
||||
imaginate_preferences: &ImaginatePreferences::default(),
|
||||
};
|
||||
let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
network.generate_node_paths(&[]);
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
|
||||
let mut network_inputs = Vec::new();
|
||||
let mut input_type = None;
|
||||
for (id, node) in network.nodes.iter() {
|
||||
for input in node.inputs.iter() {
|
||||
if let NodeInput::Network(_) = input {
|
||||
if input_type.is_none() {
|
||||
input_type = Some(input.clone());
|
||||
}
|
||||
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type");
|
||||
network_inputs.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
let len = network_inputs.len();
|
||||
network.inputs = network_inputs;
|
||||
|
||||
// if the network has no inputs, it doesn't need to be wrapped in a scope
|
||||
if len == 0 {
|
||||
return network;
|
||||
}
|
||||
|
||||
let inner_network = DocumentNode {
|
||||
name: "Scope".to_string(),
|
||||
implementation: DocumentNodeImplementation::Network(network),
|
||||
inputs: core::iter::repeat(NodeInput::node(0, 1)).take(len).collect(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// wrap the inner network in a scope
|
||||
let nodes = vec![
|
||||
begin_scope(),
|
||||
inner_network,
|
||||
DocumentNode {
|
||||
name: "End Scope".to_string(),
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_>"),
|
||||
inputs: vec![NodeInput::node(0, 0), NodeInput::node(1, 0)],
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(2, 0)],
|
||||
nodes: nodes.into_iter().enumerate().map(|(id, node)| (id as NodeId, node)).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_scope() -> DocumentNode {
|
||||
DocumentNode {
|
||||
name: "Begin Scope".to_string(),
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "SetNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "LetNode".to_string(),
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::LetNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "RefNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
|
@ -21,7 +21,7 @@ wgpu = ["gpu", "wgpu-executor"]
|
|||
quantization = ["autoquant"]
|
||||
wasm = ["wasm-bindgen", "web-sys", "js-sys"]
|
||||
imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"]
|
||||
|
||||
wayland = []
|
||||
|
||||
[dependencies]
|
||||
autoquant = { git = "https://github.com/truedoctor/autoquant", optional = true, features = [
|
||||
|
|
@ -42,7 +42,7 @@ gpu-compiler-bin-wrapper = { path = "../gpu-compiler/gpu-compiler-bin-wrapper",
|
|||
compilation-client = { path = "../compilation-client", optional = true }
|
||||
bytemuck = { version = "1.8" }
|
||||
tempfile = "3"
|
||||
image = { version = "*", default-features = false }
|
||||
image = { version = "*", default-features = false, features = ["png", "jpeg"] }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
dyn-clone = "1.0"
|
||||
|
||||
|
|
@ -62,6 +62,9 @@ js-sys = { version = "0.3.63", optional = true }
|
|||
wgpu-types = "0.16.0"
|
||||
wgpu = "0.16.1"
|
||||
wasm-bindgen-futures = { version = "0.4.36", optional = true }
|
||||
winit = "0.28.6"
|
||||
url = "2.4.0"
|
||||
tokio = { version = "1.29.0", optional = true, features = ["fs", "io-std"] }
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
use dyn_any::{StaticType, StaticTypeSized};
|
||||
use glam::{DAffine2, DVec2, Mat2, 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::quantization::{PackedPixel, QuantizationChannels};
|
||||
use graphene_core::raster::*;
|
||||
use graphene_core::*;
|
||||
use wgpu_executor::WgpuExecutor;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::wasm_application_io::WasmApplicationIo;
|
||||
|
|
@ -42,18 +46,110 @@ async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingConte
|
|||
pub struct MapGpuNode<Node, EditorApi> {
|
||||
node: Node,
|
||||
editor_api: EditorApi,
|
||||
cache: RefCell<HashMap<String, ComputePass<WgpuExecutor>>>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(MapGpuNode)]
|
||||
struct ComputePass<T: GpuExecutor> {
|
||||
pipeline_layout: PipelineLayout<T>,
|
||||
readback_buffer: Option<Arc<ShaderInput<T>>>,
|
||||
}
|
||||
|
||||
impl<T: GpuExecutor> Clone for ComputePass<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pipeline_layout: self.pipeline_layout.clone(),
|
||||
readback_buffer: self.readback_buffer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(MapGpuNode)]
|
||||
async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, editor_api: graphene_core::application_io::EditorApi<'a, WasmApplicationIo>) -> ImageFrame<Color> {
|
||||
log::debug!("Executing gpu node");
|
||||
let executor = &editor_api.application_io.gpu_executor.as_ref().unwrap();
|
||||
|
||||
#[cfg(feature = "quantization")]
|
||||
let quantization = crate::quantization::generate_quantization_from_image_frame(&image);
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
let quantization = QuantizationChannels::default();
|
||||
log::debug!("quantization: {:?}", quantization);
|
||||
|
||||
#[cfg(feature = "quantization")]
|
||||
let image = ImageFrame {
|
||||
image: Image {
|
||||
data: image.image.data.iter().map(|c| quantization::quantize_color(*c, quantization)).collect(),
|
||||
width: image.image.width,
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
};
|
||||
// TODO: The cache should be based on the network topology not the node name
|
||||
let compute_pass_descriptor = if self.cache.borrow().contains_key(&node.name) {
|
||||
self.cache.borrow().get(&node.name).unwrap().clone()
|
||||
} else {
|
||||
let name = node.name.clone();
|
||||
let compute_pass_descriptor = create_compute_pass_descriptor(node, &image, executor, quantization).await;
|
||||
self.cache.borrow_mut().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.image.width / 12 + 1, image.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();
|
||||
#[cfg(feature = "quantization")]
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, PackedPixel>(result.as_slice());
|
||||
#[cfg(feature = "quantization")]
|
||||
log::debug!("first color: {:b}", colors[0].0);
|
||||
#[cfg(feature = "quantization")]
|
||||
let colors: Vec<_> = colors.iter().map(|c| quantization::dequantize_color(*c, quantization)).collect();
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
log::debug!("first color: {:?}", colors[0]);
|
||||
ImageFrame {
|
||||
image: Image {
|
||||
data: colors,
|
||||
width: image.image.width,
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
|
||||
pub fn new(node: Node, editor_api: EditorApi) -> Self {
|
||||
Self {
|
||||
node,
|
||||
editor_api,
|
||||
cache: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
|
||||
node: DocumentNode,
|
||||
image: &ImageFrame<T>,
|
||||
executor: &&WgpuExecutor,
|
||||
quantization: QuantizationChannels,
|
||||
) -> ComputePass<WgpuExecutor> {
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
let inner_network = NodeNetwork::value_network(node);
|
||||
|
||||
log::debug!("inner_network: {:?}", inner_network);
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![], //vec![0, 1],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
inputs: vec![2, 1], //vec![0, 1],
|
||||
#[cfg(feature = "quantization")]
|
||||
outputs: vec![NodeOutput::new(5, 0)],
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
outputs: vec![NodeOutput::new(3, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Slice".into(),
|
||||
|
|
@ -61,6 +157,18 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Quantization".into(),
|
||||
inputs: vec![NodeInput::Network(concrete!(quantization::Quantization))],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Width".into(),
|
||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
/*DocumentNode {
|
||||
name: "Index".into(),
|
||||
//inputs: vec![NodeInput::Network(concrete!(UVec3))],
|
||||
|
|
@ -68,30 +176,48 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::CopiedNode".into()),
|
||||
..Default::default()
|
||||
},*/
|
||||
/*
|
||||
/*
|
||||
DocumentNode {
|
||||
name: "GetNode".into(),
|
||||
inputs: vec![NodeInput::node(1, 0), NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::storage::GetNode".into()),
|
||||
..Default::default()
|
||||
},*/
|
||||
#[cfg(feature = "quantization")]
|
||||
DocumentNode {
|
||||
name: "Dequantize".into(),
|
||||
inputs: vec![NodeInput::node(0, 0), NodeInput::node(1, 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::DeQuantizeNode"),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "MapNode".into(),
|
||||
#[cfg(feature = "quantization")]
|
||||
inputs: vec![NodeInput::node(3, 0)],
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
..Default::default()
|
||||
},
|
||||
#[cfg(feature = "quantization")]
|
||||
DocumentNode {
|
||||
name: "Quantize".into(),
|
||||
inputs: vec![NodeInput::node(4, 0), NodeInput::node(1, 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::QuantizeNode"),
|
||||
..Default::default()
|
||||
},
|
||||
/*
|
||||
DocumentNode {
|
||||
name: "SaveNode".into(),
|
||||
inputs: vec![
|
||||
//NodeInput::node(0, 0),
|
||||
NodeInput::node(5, 0),
|
||||
NodeInput::Inline(InlineRust::new(
|
||||
"o0[_global_index.x as usize] = i0[_global_index.x as usize]".into(),
|
||||
Type::Fn(Box::new(concrete!(Color)), Box::new(concrete!(()))),
|
||||
"|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::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::generic::FnMutNode".into()),
|
||||
..Default::default()
|
||||
},
|
||||
*/
|
||||
|
|
@ -110,12 +236,23 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
vec![concrete!(u32), concrete!(Color)], //, concrete!(u32)],
|
||||
vec![concrete!(Color)],
|
||||
ShaderIO {
|
||||
#[cfg(feature = "quantization")]
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)),
|
||||
ShaderInput::StorageBuffer((), concrete!(PackedPixel)),
|
||||
ShaderInput::UniformBuffer((), concrete!(quantization::QuantizationChannels)),
|
||||
//ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
|
||||
ShaderInput::OutputBuffer((), concrete!(PackedPixel)),
|
||||
],
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)),
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)),
|
||||
//ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
|
||||
ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
],
|
||||
#[cfg(feature = "quantization")]
|
||||
output: ShaderInput::OutputBuffer((), concrete!(PackedPixel)),
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
output: ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
},
|
||||
)
|
||||
|
|
@ -124,8 +261,6 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
//return ImageFrame::empty();
|
||||
let len: usize = image.image.data.len();
|
||||
|
||||
let executor = &editor_api.application_io.gpu_executor.as_ref().unwrap();
|
||||
|
||||
/*
|
||||
let canvas = editor_api.application_io.create_surface();
|
||||
|
||||
|
|
@ -144,6 +279,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
return frame;*/
|
||||
log::debug!("creating buffer");
|
||||
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
|
||||
let quantization_uniform = executor.create_uniform_buffer(quantization).unwrap();
|
||||
let storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
image.image.data.clone(),
|
||||
|
|
@ -156,6 +292,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
)
|
||||
.unwrap();
|
||||
let width_uniform = Arc::new(width_uniform);
|
||||
let quantization_uniform = Arc::new(quantization_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);
|
||||
|
|
@ -163,6 +300,9 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
let readback_buffer = Arc::new(readback_buffer);
|
||||
log::debug!("created buffer");
|
||||
let bind_group = Bindgroup {
|
||||
#[cfg(feature = "quantization")]
|
||||
buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()],
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
buffers: vec![width_uniform.clone(), storage_buffer.clone()],
|
||||
};
|
||||
|
||||
|
|
@ -175,36 +315,18 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
|
|||
let shader = executor.load_shader(shader).unwrap();
|
||||
log::debug!("loaded shader");
|
||||
let pipeline = PipelineLayout {
|
||||
shader,
|
||||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group,
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer: output_buffer.clone(),
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
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");
|
||||
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: image.image.width,
|
||||
height: image.image.height,
|
||||
},
|
||||
transform: image.transform,
|
||||
}
|
||||
|
||||
/*
|
||||
let executor: GpuExecutor = GpuExecutor::new(Context::new().await.unwrap(), shader.into(), "gpu::eval".into()).unwrap();
|
||||
let data: Vec<_> = input.into_iter().collect();
|
||||
let result = executor.execute(Box::new(data)).unwrap();
|
||||
let result = dyn_any::downcast::<Vec<_O>>(result).unwrap();
|
||||
*result
|
||||
*/
|
||||
let compute_pass_descriptor = ComputePass {
|
||||
pipeline_layout: pipeline,
|
||||
readback_buffer: Some(readback_buffer.clone()),
|
||||
};
|
||||
compute_pass_descriptor
|
||||
}
|
||||
/*
|
||||
#[node_macro::node_fn(MapGpuNode)]
|
||||
|
|
@ -414,9 +536,9 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
|
|||
let shader = executor.load_shader(shader).unwrap();
|
||||
log::debug!("loaded shader");
|
||||
let pipeline = PipelineLayout {
|
||||
shader,
|
||||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group,
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer: output_buffer.clone(),
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 241 B |
|
|
@ -1,3 +1,4 @@
|
|||
use autoquant::packing::ErrorFunction;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use graphene_core::quantization::*;
|
||||
use graphene_core::raster::{Color, ImageFrame};
|
||||
|
|
@ -13,47 +14,93 @@ pub struct GenerateQuantizationNode<N, M> {
|
|||
|
||||
#[node_macro::node_fn(GenerateQuantizationNode)]
|
||||
fn generate_quantization_fn(image_frame: ImageFrame<Color>, samples: u32, function: u32) -> [Quantization; 4] {
|
||||
let image = image_frame.image;
|
||||
generate_quantization_from_image_frame(&image_frame)
|
||||
}
|
||||
|
||||
pub fn generate_quantization_from_image_frame(image_frame: &ImageFrame<Color>) -> [Quantization; 4] {
|
||||
let image = &image_frame.image;
|
||||
|
||||
let len = image.data.len().min(10000);
|
||||
let mut channels: Vec<_> = (0..4).map(|_| Vec::with_capacity(image.data.len())).collect();
|
||||
image
|
||||
let data = image
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| i % (image.data.len() / len) == 0)
|
||||
.map(|(_, x)| vec![x.r() as f64, x.g() as f64, x.b() as f64, x.a() as f64])
|
||||
.for_each(|x| x.into_iter().enumerate().for_each(|(i, value)| channels[i].push(value)));
|
||||
let quantization: Vec<Quantization> = channels.into_iter().map(|x| generate_quantization_per_channel(x, samples)).collect();
|
||||
core::array::from_fn(|i| quantization[i].clone())
|
||||
.flat_map(|(_, x)| vec![x.r() as f64, x.g() as f64, x.b() as f64, x.a() as f64])
|
||||
.collect::<Vec<_>>();
|
||||
generate_quantization(data, len)
|
||||
}
|
||||
fn generate_quantization(data: Vec<f64>, samples: usize) -> [Quantization; 4] {
|
||||
let red = create_distribution(data.clone(), samples, 0);
|
||||
let green = create_distribution(data.clone(), samples, 1);
|
||||
let blue = create_distribution(data.clone(), samples, 2);
|
||||
let alpha = create_distribution(data, samples, 3);
|
||||
|
||||
let fit_red = autoquant::calculate_error_function(&red, 1, &red);
|
||||
let fit_green = autoquant::calculate_error_function(&green, 1, &green);
|
||||
let fit_blue = autoquant::calculate_error_function(&blue, 1, &blue);
|
||||
let fit_alpha = autoquant::calculate_error_function(&alpha, 1, &alpha);
|
||||
let red_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_red.as_slice());
|
||||
let green_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_green.as_slice());
|
||||
let blue_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_blue.as_slice());
|
||||
let alpha_error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit_alpha.as_slice());
|
||||
let merged: ErrorFunction<20> = autoquant::packing::merge_error_functions(&red_error, &green_error);
|
||||
let merged: ErrorFunction<30> = autoquant::packing::merge_error_functions(&merged, &blue_error);
|
||||
let merged: ErrorFunction<40> = autoquant::packing::merge_error_functions(&merged, &alpha_error);
|
||||
|
||||
let bin_size = 32;
|
||||
let mut distributions = [red, green, blue, alpha].into_iter();
|
||||
|
||||
let bits = &merged.bits[bin_size];
|
||||
|
||||
core::array::from_fn(|i| {
|
||||
let fit = autoquant::models::OptimizedLin::new(distributions.next().unwrap(), (1 << bits[i]) - 1);
|
||||
let parameters = fit.parameters();
|
||||
Quantization::new(parameters[0] as f32, parameters[1] as f32, bits[i] as u32)
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_quantization_per_channel(data: Vec<f64>, samples: u32) -> Quantization {
|
||||
/*
|
||||
// TODO: make this work with generic size parameters
|
||||
fn generate_quantization<const N: usize>(data: Vec<f64>, samples: usize, channels: usize) -> [Quantization; N] {
|
||||
let mut quantizations = Vec::new();
|
||||
let mut merged_error: Option<ErrorFunction<10>> = None;
|
||||
let bin_size = 32;
|
||||
|
||||
for i in 0..channels {
|
||||
let channel_data = create_distribution(data.clone(), samples, i);
|
||||
|
||||
let fit = autoquant::calculate_error_function(&channel_data, 0, &channel_data);
|
||||
let error: ErrorFunction<10> = autoquant::packing::ErrorFunction::new(fit.as_slice());
|
||||
|
||||
// Merge current error function with previous ones
|
||||
merged_error = match merged_error {
|
||||
Some(prev_error) => Some(autoquant::packing::merge_error_functions(&prev_error, &error)),
|
||||
None => Some(error.clone()),
|
||||
};
|
||||
|
||||
println!("Merged: {:?}", merged_error);
|
||||
|
||||
let bits = merged_error.as_ref().unwrap().bits.iter().map(|x| x[i]).collect::<Vec<_>>();
|
||||
let model_fit = autoquant::models::OptimizedLin::new(channel_data, 1 << bits[bin_size]);
|
||||
let parameters = model_fit.parameters();
|
||||
let quantization = Quantization::new(parameters[0] as f32, parameters[1] as u32, bits[bin_size] as u32);
|
||||
|
||||
quantizations.push(quantization);
|
||||
}
|
||||
|
||||
core::array::from_fn(|x| quantizations[x])
|
||||
}*/
|
||||
|
||||
fn create_distribution(data: Vec<f64>, samples: usize, channel: usize) -> Vec<(f64, f64)> {
|
||||
let data: Vec<f64> = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel] as f64).collect();
|
||||
let max = *data.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap();
|
||||
let data: Vec<f64> = data.iter().map(|x| x / max).collect();
|
||||
dbg!(max);
|
||||
//let data = autoquant::generate_normal_distribution(3.0, 1.1, 1000);
|
||||
//data.iter_mut().for_each(|x| *x = x.abs());
|
||||
let mut dist = autoquant::integrate_distribution(data);
|
||||
autoquant::drop_duplicates(&mut dist);
|
||||
let dist = autoquant::normalize_distribution(dist.as_slice());
|
||||
let max = dist.last().unwrap().0;
|
||||
/*let linear = Box::new(autoquant::SimpleFitFn {
|
||||
function: move |x| x / max,
|
||||
inverse: move |x| x * max,
|
||||
name: "identity",
|
||||
});*/
|
||||
|
||||
let linear = Quantization {
|
||||
fn_index: 0,
|
||||
a: max as f32,
|
||||
b: 0.,
|
||||
c: 0.,
|
||||
d: 0.,
|
||||
};
|
||||
let log_fit = autoquant::models::OptimizedLog::new(dist, samples as u64);
|
||||
let parameters = log_fit.parameters();
|
||||
let log_fit = Quantization {
|
||||
fn_index: 1,
|
||||
a: parameters[0] as f32,
|
||||
b: parameters[1] as f32,
|
||||
c: parameters[2] as f32,
|
||||
d: parameters[3] as f32,
|
||||
};
|
||||
log_fit
|
||||
dist
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ use dyn_any::DynAny;
|
|||
pub struct AnyRefNode<'n, N: Node<'n>>(N, PhantomData<&'n ()>);
|
||||
|
||||
impl<'n, N: Node<'n, Output = &'n O>, O: DynAny<'n> + 'n> Node<'n> for AnyRefNode<'n, N> {
|
||||
type Output = &'n (dyn DynAny<'n>);
|
||||
fn eval(&'n self) -> Self::Output {
|
||||
fn eval(&'n self) -> &'n (dyn DynAny<'n>) {
|
||||
let value: &O = self.0.eval();
|
||||
value
|
||||
}
|
||||
|
|
@ -22,8 +21,7 @@ impl<'n, N: Node<'n, Output = &'n O>, O: 'n + ?Sized> AnyRefNode<'n, N> {
|
|||
pub struct StorageNode<'n>(&'n dyn Node<'n, Output = &'n dyn DynAny<'n>>);
|
||||
|
||||
impl<'n> Node<'n> for StorageNode<'n> {
|
||||
type Output = &'n (dyn DynAny<'n>);
|
||||
fn eval(&'n self) -> Self::Output {
|
||||
fn eval(&'n self) -> &'n (dyn DynAny<'n>) {
|
||||
self.0.eval()
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +34,6 @@ impl<'n> StorageNode<'n> {
|
|||
#[derive(Default)]
|
||||
pub struct AnyValueNode<'n, T>(T, PhantomData<&'n ()>);
|
||||
impl<'n, T: 'n + DynAny<'n>> Node<'n> for AnyValueNode<'n, T> {
|
||||
type Output = &'n dyn DynAny<'n>;
|
||||
fn eval(&'n self) -> &'n dyn DynAny<'n> {
|
||||
&self.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use core::future::Future;
|
||||
use dyn_any::StaticType;
|
||||
use graphene_core::application_io::{ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::{
|
||||
raster::{color::SRGBA8, ImageFrame},
|
||||
Node,
|
||||
};
|
||||
use js_sys::{Object, Reflect};
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio::io::AsyncReadExt;
|
||||
use wasm_bindgen::{Clamped, JsCast, JsValue};
|
||||
use web_sys::{window, CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
#[cfg(feature = "wgpu")]
|
||||
|
|
@ -20,15 +28,23 @@ pub struct WasmApplicationIo {
|
|||
ids: RefCell<u64>,
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub(crate) gpu_executor: Option<WgpuExecutor>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
windows: RefCell<Vec<Arc<winit::window::Window>>>,
|
||||
pub resources: HashMap<String, Arc<[u8]>>,
|
||||
}
|
||||
|
||||
impl WasmApplicationIo {
|
||||
pub async fn new() -> Self {
|
||||
Self {
|
||||
let mut io = Self {
|
||||
ids: RefCell::new(0),
|
||||
#[cfg(feature = "wgpu")]
|
||||
gpu_executor: WgpuExecutor::new().await,
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
windows: RefCell::new(Vec::new()),
|
||||
resources: HashMap::new(),
|
||||
};
|
||||
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
|
||||
io
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,12 +67,16 @@ impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
|
|||
pub type WasmEditorApi<'a> = graphene_core::application_io::EditorApi<'a, WasmApplicationIo>;
|
||||
|
||||
impl ApplicationIo for WasmApplicationIo {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type Surface = HtmlCanvasElement;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type Surface = Arc<winit::window::Window>;
|
||||
#[cfg(feature = "wgpu")]
|
||||
type Executor = WgpuExecutor;
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
type Executor = ();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn create_surface(&self) -> SurfaceHandle<Self::Surface> {
|
||||
let wrapper = || {
|
||||
let document = window().expect("should have a window in this context").document().expect("window should have a document");
|
||||
|
|
@ -90,7 +110,29 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
|
||||
wrapper().expect("should be able to set canvas in global scope")
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn create_surface(&self) -> SurfaceHandle<Self::Surface> {
|
||||
#[cfg(feature = "wayland")]
|
||||
use winit::platform::wayland::EventLoopBuilderExtWayland;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build();
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
let event_loop = winit::event_loop::EventLoop::new();
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title("Graphite")
|
||||
.with_inner_size(winit::dpi::PhysicalSize::new(800, 600))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let window = Arc::new(window);
|
||||
self.windows.borrow_mut().push(window.clone());
|
||||
SurfaceHandle {
|
||||
surface_id: SurfaceId(window.id().into()),
|
||||
surface: window,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn destroy_surface(&self, surface_id: SurfaceId) {
|
||||
let window = window().expect("should have a window in this context");
|
||||
let window = Object::from(window);
|
||||
|
|
@ -111,10 +153,50 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
wrapper().expect("should be able to set canvas in global scope")
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn destroy_surface(&self, _surface_id: SurfaceId) {}
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||
self.gpu_executor.as_ref()
|
||||
}
|
||||
|
||||
fn load_resource(&self, url: impl AsRef<str>) -> Result<Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>, ApplicationError> {
|
||||
let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;
|
||||
log::info!("Loading resource: {:?}", url);
|
||||
match url.scheme() {
|
||||
#[cfg(feature = "tokio")]
|
||||
"file" => {
|
||||
let path = url.to_file_path().map_err(|_| ApplicationError::NotFound)?;
|
||||
let path = path.to_str().ok_or(ApplicationError::NotFound)?;
|
||||
let path = path.to_owned();
|
||||
Ok(Box::pin(async move {
|
||||
let file = tokio::fs::File::open(path).await.map_err(|_| ApplicationError::NotFound)?;
|
||||
let mut reader = tokio::io::BufReader::new(file);
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data).await.map_err(|_| ApplicationError::NotFound)?;
|
||||
Ok(Arc::from(data))
|
||||
}) as Pin<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
|
||||
}
|
||||
"http" | "https" => {
|
||||
let url = url.to_string();
|
||||
Ok(Box::pin(async move {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get(url).send().await.map_err(|_| ApplicationError::NotFound)?;
|
||||
let data = response.bytes().await.map_err(|_| ApplicationError::NotFound)?;
|
||||
Ok(Arc::from(data.to_vec()))
|
||||
}) as Pin<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
|
||||
}
|
||||
"graphite" => {
|
||||
let path = url.path();
|
||||
let path = path.to_owned();
|
||||
log::info!("Loading resource: {}", path);
|
||||
let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();
|
||||
Ok(Box::pin(async move { Ok(data.clone()) }) as Pin<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
|
||||
}
|
||||
_ => Err(ApplicationError::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type WasmSurfaceHandle = SurfaceHandle<HtmlCanvasElement>;
|
||||
|
|
@ -123,7 +205,7 @@ pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<HtmlCanvasElement>;
|
|||
pub struct CreateSurfaceNode {}
|
||||
|
||||
#[node_macro::node_fn(CreateSurfaceNode)]
|
||||
async fn create_surface_node<'a: 'input>(editor: WasmEditorApi<'a>) -> Arc<SurfaceHandle<HtmlCanvasElement>> {
|
||||
async fn create_surface_node<'a: 'input>(editor: WasmEditorApi<'a>) -> Arc<SurfaceHandle<<WasmApplicationIo as ApplicationIo>::Surface>> {
|
||||
editor.application_io.create_surface().into()
|
||||
}
|
||||
|
||||
|
|
@ -149,3 +231,29 @@ async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_ha
|
|||
transform: image.transform,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoadResourceNode<Url> {
|
||||
url: Url,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(LoadResourceNode)]
|
||||
async fn load_resource_node<'a: 'input>(editor: WasmEditorApi<'a>, url: String) -> Arc<[u8]> {
|
||||
editor.application_io.load_resource(url).unwrap().await.unwrap()
|
||||
}
|
||||
|
||||
pub struct DecodeImageNode;
|
||||
|
||||
#[node_macro::node_fn(DecodeImageNode)]
|
||||
fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
let image = image::load_from_memory(data.as_ref()).expect("Failed to decode image");
|
||||
let image = image.to_rgba32f();
|
||||
let image = ImageFrame {
|
||||
image: Image {
|
||||
data: image.chunks(4).map(|pixel| Color::from_unassociated_alpha(pixel[0], pixel[1], pixel[2], pixel[3])).collect(),
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
},
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
};
|
||||
image
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use graph_craft::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
|
||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
||||
use graphene_core::ops::IdNode;
|
||||
use graphene_core::quantization::QuantizationChannels;
|
||||
use graphene_core::quantization::{PackedPixel, QuantizationChannels};
|
||||
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::color::Color;
|
||||
|
|
@ -191,10 +191,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [u32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [&u32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [&u32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [f64]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [f64]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: f32, params: [f32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f32, params: [f32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: f32, params: [&f32]),
|
||||
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f32, params: [&f32]),
|
||||
register_node!(graphene_core::ops::SomeNode, input: WasmEditorApi, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, output: ImageFrame<SRGBA8>, params: []),
|
||||
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, output: ImageFrame<Color>, params: []),
|
||||
|
|
@ -251,6 +251,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
|
||||
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
|
||||
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
|
||||
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
|
||||
async_node!(
|
||||
graphene_std::wasm_application_io::DrawImageFrameNode<_>,
|
||||
|
|
@ -284,7 +286,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(gpu_executor::ReadOutputBufferNode<_, _>, input: Arc<ShaderInput<WgpuExecutor>>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
|
||||
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(gpu_executor::CreateGpuSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface>>, params: []),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(gpu_executor::RenderTextureNode<_, _>, input: ShaderInputFrame<WgpuExecutor>, output: SurfaceFrame, params: [Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface>>, &WgpuExecutor]),
|
||||
|
|
@ -310,7 +312,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
},
|
||||
NodeIOTypes::new(
|
||||
concrete!(ImageFrame<Color>),
|
||||
concrete!(SurfaceFrame),
|
||||
concrete!(ImageFrame<Color>),
|
||||
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
|
||||
),
|
||||
)],
|
||||
|
|
@ -355,7 +357,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]),
|
||||
raster_node!(graphene_core::raster::ExtractAlphaNode<>, params: []),
|
||||
raster_node!(graphene_core::raster::ExtractOpaqueNode<>, params: []),
|
||||
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f64, f64, f64, f64, f64]),
|
||||
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f32, f32, f32, f32, f32]),
|
||||
register_node!(graphene_std::image_segmentation::ImageSegmentationNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),
|
||||
vec![(
|
||||
|
|
@ -364,7 +366,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
Box::pin(async move {
|
||||
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
|
||||
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone());
|
||||
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone());
|
||||
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, blend_node);
|
||||
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
|
|
@ -374,21 +376,21 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
NodeIOTypes::new(
|
||||
concrete!(ImageFrame<Color>),
|
||||
concrete!(ImageFrame<Color>),
|
||||
vec![fn_type!(ImageFrame<Color>), fn_type!(BlendMode), fn_type!(f64)],
|
||||
vec![fn_type!(ImageFrame<Color>), fn_type!(BlendMode), fn_type!(f32)],
|
||||
),
|
||||
)],
|
||||
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]),
|
||||
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]),
|
||||
raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f32, f32, f32, f32, f32, f32]),
|
||||
raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f32, f32, f32]),
|
||||
raster_node!(graphene_core::raster::InvertRGBNode, params: []),
|
||||
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f64, f64, LuminanceCalculation]),
|
||||
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]),
|
||||
raster_node!(graphene_core::raster::ThresholdNode<_, _, _>, params: [f32, f32, LuminanceCalculation]),
|
||||
raster_node!(graphene_core::raster::VibranceNode<_>, params: [f32]),
|
||||
raster_node!(
|
||||
graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
|
||||
params: [bool, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64]
|
||||
params: [bool, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32]
|
||||
),
|
||||
raster_node!(
|
||||
graphene_core::raster::SelectiveColorNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>,
|
||||
params: [RelativeAbsolute, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64]
|
||||
params: [RelativeAbsolute, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32]
|
||||
),
|
||||
vec![(
|
||||
NodeIdentifier::new("graphene_core::raster::BrightnessContrastNode<_, _, _>"),
|
||||
|
|
@ -396,9 +398,9 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
Box::pin(async move {
|
||||
use graphene_core::raster::brightness_contrast::*;
|
||||
|
||||
let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0].clone());
|
||||
let brightness: DowncastBothNode<(), f32> = DowncastBothNode::new(args[0].clone());
|
||||
let brightness = ClonedNode::new(brightness.eval(()).await as f32);
|
||||
let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1].clone());
|
||||
let contrast: DowncastBothNode<(), f32> = DowncastBothNode::new(args[1].clone());
|
||||
let contrast = ClonedNode::new(contrast.eval(()).await as f32);
|
||||
let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2].clone());
|
||||
|
||||
|
|
@ -417,11 +419,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
}
|
||||
})
|
||||
},
|
||||
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
|
||||
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f32), fn_type!(f32), fn_type!(bool)]),
|
||||
)],
|
||||
raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),
|
||||
raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]),
|
||||
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]),
|
||||
raster_node!(graphene_core::raster::OpacityNode<_>, params: [f32]),
|
||||
raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f32]),
|
||||
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f32, f32, f32]),
|
||||
register_node!(graphene_core::memo::LetNode<_>, input: Option<ImageFrame<Color>>, params: []),
|
||||
register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []),
|
||||
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
|
|
@ -479,18 +481,18 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
vec![
|
||||
fn_type!(WasmEditorApi),
|
||||
fn_type!(ImaginateController),
|
||||
fn_type!(f64),
|
||||
fn_type!(f32),
|
||||
fn_type!(Option<DVec2>),
|
||||
fn_type!(u32),
|
||||
fn_type!(ImaginateSamplingMethod),
|
||||
fn_type!(f64),
|
||||
fn_type!(f32),
|
||||
fn_type!(String),
|
||||
fn_type!(String),
|
||||
fn_type!(bool),
|
||||
fn_type!(f64),
|
||||
fn_type!(f32),
|
||||
fn_type!(Option<Vec<u64>>),
|
||||
fn_type!(bool),
|
||||
fn_type!(f64),
|
||||
fn_type!(f32),
|
||||
fn_type!(ImaginateMaskStartingFill),
|
||||
fn_type!(bool),
|
||||
fn_type!(bool),
|
||||
|
|
@ -513,25 +515,25 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
|
||||
#[cfg(feature = "quantization")]
|
||||
register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame<Color>, params: [u32, u32]),
|
||||
raster_node!(graphene_core::quantization::QuantizeNode<_>, params: [QuantizationChannels]),
|
||||
raster_node!(graphene_core::quantization::DeQuantizeNode<_>, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame<Color>, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: WasmSurfaceHandleFrame, params: [DVec2, f32, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [DAffine2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::vector::style::FillType, Option<graphene_core::Color>, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option<graphene_core::Color>)>]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f64, Vec<f32>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f32, Vec<f32>, f32, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f32]),
|
||||
register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []),
|
||||
register_node!(
|
||||
graphene_core::vector::generator_nodes::PathGenerator<_>,
|
||||
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
||||
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
||||
),
|
||||
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
||||
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f32]),
|
||||
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::vector::VectorData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
profiling = ["nvtx"]
|
||||
passthrough = []
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
@ -38,3 +40,4 @@ futures-intrusive = "0.5.0"
|
|||
futures = "0.3.25"
|
||||
web-sys = { version = "0.3.4", features = ["HtmlCanvasElement"] }
|
||||
winit = "0.28.6"
|
||||
nvtx = { version = "1.1.1", optional = true }
|
||||
|
|
|
|||
|
|
@ -12,23 +12,25 @@ pub struct Context {
|
|||
impl Context {
|
||||
pub async fn new() -> Option<Self> {
|
||||
// Instantiates instance of WebGPU
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
|
||||
let mut instance_descriptor = wgpu::InstanceDescriptor::default();
|
||||
instance_descriptor.backends = wgpu::Backends::VULKAN | wgpu::Backends::BROWSER_WEBGPU;
|
||||
let instance = wgpu::Instance::new(instance_descriptor);
|
||||
|
||||
// `request_adapter` instantiates the general connection to the GPU
|
||||
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?;
|
||||
|
||||
//let limits = adapter.limits();
|
||||
|
||||
//log::trace!("Adapter limits: {:?}", limits);
|
||||
|
||||
let limits = adapter.limits();
|
||||
// `request_device` instantiates the feature specific connection to the GPU, defining some parameters,
|
||||
// `features` being the available features.
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
#[cfg(not(feature = "passthrough"))]
|
||||
features: wgpu::Features::empty(),
|
||||
limits: Default::default(),
|
||||
#[cfg(feature = "passthrough")]
|
||||
features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH,
|
||||
limits: limits,
|
||||
},
|
||||
None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,19 +11,30 @@ use anyhow::{bail, Result};
|
|||
use futures::Future;
|
||||
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, Texture, TextureView};
|
||||
use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, SurfaceConfiguration, SurfaceError, Texture, TextureView};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
#[derive(Debug, dyn_any::DynAny)]
|
||||
#[derive(dyn_any::DynAny)]
|
||||
pub struct WgpuExecutor {
|
||||
context: Context,
|
||||
pub context: Context,
|
||||
render_configuration: RenderConfiguration,
|
||||
surface_config: Cell<Option<SurfaceConfiguration>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WgpuExecutor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WgpuExecutor")
|
||||
.field("context", &self.context)
|
||||
.field("render_configuration", &self.render_configuration)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<EditorApi<'a, T>> for &'a WgpuExecutor {
|
||||
|
|
@ -104,13 +115,21 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
type Window = HtmlCanvasElement;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type Window = winit::window::Window;
|
||||
type Window = Arc<winit::window::Window>;
|
||||
|
||||
fn load_shader(&self, shader: Shader) -> Result<Self::ShaderHandle> {
|
||||
#[cfg(not(feature = "passthrough"))]
|
||||
let shader_module = self.context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(shader.name),
|
||||
source: wgpu::ShaderSource::SpirV(shader.source),
|
||||
});
|
||||
#[cfg(feature = "passthrough")]
|
||||
let shader_module = unsafe {
|
||||
self.context.device.create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV {
|
||||
label: Some(shader.name),
|
||||
source: shader.source,
|
||||
})
|
||||
};
|
||||
Ok(ShaderModuleWrapper(shader_module))
|
||||
}
|
||||
|
||||
|
|
@ -234,14 +253,16 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
entries: entries.as_slice(),
|
||||
});
|
||||
|
||||
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: Some("compute encoder") });
|
||||
{
|
||||
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.push_debug_group("compute shader");
|
||||
cpass.dispatch_workgroups(dimensions.0, dimensions.1, dimensions.2); // Number of cells to run, the (x,y,z) size of item being processed
|
||||
cpass.pop_debug_group();
|
||||
}
|
||||
// Sets adds copy operation to command encoder.
|
||||
// Will copy data from storage buffer on GPU to staging buffer on CPU.
|
||||
|
|
@ -265,7 +286,40 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
fn create_render_pass(&self, texture: Arc<ShaderInput<Self>>, canvas: Arc<SurfaceHandle<wgpu::Surface>>) -> Result<()> {
|
||||
let texture = texture.texture().expect("Expected texture input");
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let output = canvas.as_ref().surface.get_current_texture()?;
|
||||
let result = canvas.as_ref().surface.get_current_texture();
|
||||
|
||||
let surface = &canvas.as_ref().surface;
|
||||
let surface_caps = surface.get_capabilities(&self.context.adapter);
|
||||
println!("{:?}", surface_caps);
|
||||
if surface_caps.formats.is_empty() {
|
||||
log::warn!("No surface formats available");
|
||||
//return Ok(());
|
||||
}
|
||||
let Some(config) = self.surface_config.take() else {return Ok(())};
|
||||
let new_config = config.clone();
|
||||
self.surface_config.replace(Some(config));
|
||||
let output = match result {
|
||||
Err(SurfaceError::Timeout) => {
|
||||
log::warn!("Timeout when getting current texture");
|
||||
return Ok(());
|
||||
}
|
||||
Err(SurfaceError::Lost) => {
|
||||
log::warn!("Surface lost");
|
||||
|
||||
surface.configure(&self.context.device, &new_config);
|
||||
return Ok(());
|
||||
}
|
||||
Err(SurfaceError::OutOfMemory) => {
|
||||
log::warn!("Out of memory");
|
||||
return Ok(());
|
||||
}
|
||||
Err(SurfaceError::Outdated) => {
|
||||
log::warn!("Surface outdated");
|
||||
surface.configure(&self.context.device, &new_config);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(surface) => surface,
|
||||
};
|
||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
format: Some(wgpu::TextureFormat::Bgra8Unorm),
|
||||
..Default::default()
|
||||
|
|
@ -306,10 +360,16 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
render_pass.set_vertex_buffer(0, self.render_configuration.vertex_buffer.slice(..));
|
||||
render_pass.set_index_buffer(self.render_configuration.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
||||
render_pass.draw_indexed(0..self.render_configuration.num_indices, 0, 0..1);
|
||||
render_pass.insert_debug_marker("render node network");
|
||||
}
|
||||
|
||||
let encoder = encoder.finish();
|
||||
#[cfg(feature = "profiling")]
|
||||
nvtx::range_push!("render");
|
||||
self.context.queue.submit(Some(encoder));
|
||||
#[cfg(feature = "profiling")]
|
||||
nvtx::range_pop!();
|
||||
log::trace!("Submitted render pass");
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
|
|
@ -318,10 +378,6 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()> {
|
||||
self.context.queue.submit(Some(encoder.0));
|
||||
|
||||
// Poll the device in a blocking manner so that our future resolves.
|
||||
// In an actual application, `device.poll(...)` should
|
||||
// be called in an event loop or on another thread.
|
||||
self.context.device.poll(wgpu::Maintain::Wait);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -394,8 +450,25 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
})
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn create_surface(&self, window: SurfaceHandle<winit::window::Window>) -> Result<SurfaceHandle<wgpu::Surface>> {
|
||||
let surface = unsafe { self.context.instance.create_surface(&window.surface) }?;
|
||||
fn create_surface(&self, window: SurfaceHandle<Self::Window>) -> Result<SurfaceHandle<wgpu::Surface>> {
|
||||
let surface = unsafe { self.context.instance.create_surface(window.surface.as_ref()) }?;
|
||||
|
||||
let size = window.surface.inner_size();
|
||||
let surface_caps = surface.get_capabilities(&self.context.adapter);
|
||||
println!("{:?}", surface_caps);
|
||||
let surface_format = wgpu::TextureFormat::Bgra8Unorm;
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: surface_caps.present_modes[0],
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
};
|
||||
surface.configure(&self.context.device, &config);
|
||||
self.surface_config.set(Some(config));
|
||||
|
||||
let surface_id = window.surface_id;
|
||||
Ok(SurfaceHandle { surface_id, surface })
|
||||
}
|
||||
|
|
@ -404,6 +477,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
|
|||
impl WgpuExecutor {
|
||||
pub async fn new() -> Option<Self> {
|
||||
let context = Context::new().await?;
|
||||
println!("wgpu executor created");
|
||||
|
||||
let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
|
|
@ -512,7 +586,11 @@ impl WgpuExecutor {
|
|||
sampler,
|
||||
};
|
||||
|
||||
Some(Self { context, render_configuration })
|
||||
Some(Self {
|
||||
context,
|
||||
render_configuration,
|
||||
surface_config: Cell::new(None),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||