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
This commit is contained in:
Dennis Kobert 2023-07-04 17:04:09 +02:00 committed by Keavon Chambers
parent 61c5dd1f88
commit 3c2d371173
57 changed files with 10169 additions and 845 deletions

1166
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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);
});
}

View File

@ -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

View File

@ -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)),
),

View File

@ -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>);

View File

@ -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(),

View File

@ -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::*;

View File

@ -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)
}

View File

@ -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 = []

View File

@ -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)]

View File

@ -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;

View File

@ -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)
}
}

View File

@ -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()));

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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))

View File

@ -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,

View File

@ -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 }
}

View File

@ -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))
}

View File

@ -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);

View File

@ -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
}

View File

@ -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",
]

View File

@ -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)

View File

@ -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 %}

View File

@ -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();

View File

@ -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()
},

View File

@ -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"),
}
}

View File

@ -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| {

View File

@ -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,

View File

@ -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"

View File

@ -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()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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");

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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]),

View File

@ -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 }

View File

@ -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,
)

View File

@ -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),
})
}
}