Restore functionality of GPU infrastructure (#1797)

* Update gpu nodes to compile again

Restructure `gpu-executor` and `wgpu-executor`

And libssl to nix shell

Fix graphene-cli and add half percision color format

Fix texture scaling

Remove vulkan executor

Fix compile errors

Improve execution request deduplication

* Fix warnings

* Fix graph compile issues

* Code review

* Remove test file

* Fix lint

* Wip make node futures send

* Make futures Send on non wasm targets

* Fix warnings

* Fix nested use of block_on

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-07-15 15:14:48 +02:00
committed by GitHub
parent 59a943f42f
commit 212f08c6c8
66 changed files with 1572 additions and 1577 deletions

View File

@@ -1,6 +1,7 @@
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::NodeIO;
use graphene_core::WasmNotSend;
pub use graphene_core::{generic, ops, Node};
use dyn_any::StaticType;
@@ -13,7 +14,7 @@ pub struct DynAnyNode<I, O, Node> {
_o: PhantomData<O>,
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
where
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
{
@@ -21,9 +22,9 @@ where
#[inline]
fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node_name = core::any::type_name::<N>();
let output = |input| async move {
let result = self.node.eval(input).await;
Box::new(result) as Any<'input>
let output = |input| {
let result = self.node.eval(input);
async move { Box::new(result.await) as Any<'input> }
};
match dyn_any::downcast(input) {
Ok(input) => Box::pin(output(*input)),
@@ -63,7 +64,7 @@ pub struct DynAnyRefNode<I, O, Node> {
node: Node,
_i: PhantomData<(I, O)>,
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyRefNode<_I, _O, N>
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend + Sync, N: 'input> Node<'input, Any<'input>> for DynAnyRefNode<_I, _O, N>
where
N: for<'any_input> Node<'any_input, _I, Output = &'any_input _O>,
{
@@ -93,7 +94,7 @@ pub struct DynAnyInRefNode<I, O, Node> {
node: Node,
_i: PhantomData<(I, O)>,
}
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N>
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N>
where
N: for<'any_input> Node<'any_input, &'any_input _I, Output = DynFuture<'any_input, _O>>,
{
@@ -117,13 +118,14 @@ pub struct FutureWrapperNode<Node> {
node: Node,
}
impl<'i, T: 'i, N: Node<'i, T>> Node<'i, T> for FutureWrapperNode<N>
impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>
where
N: Node<'i, T>,
N: Node<'i, T, Output: WasmNotSend> + WasmNotSend,
{
type Output = DynFuture<'i, N::Output>;
fn eval(&'i self, input: T) -> Self::Output {
Box::pin(async move { self.node.eval(input) })
let result = self.node.eval(input);
Box::pin(async move { result })
}
fn reset(&self) {
self.node.reset();
@@ -146,7 +148,7 @@ pub trait IntoTypeErasedNode<'n> {
impl<'n, N: 'n> IntoTypeErasedNode<'n> for N
where
N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n,
N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + Sync + WasmNotSend,
{
fn into_type_erased(self) -> TypeErasedBox<'n> {
Box::new(self)
@@ -182,7 +184,7 @@ pub struct DowncastBothNode<I, O> {
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode<I, O> {
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> {
type Output = DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
@@ -206,32 +208,6 @@ impl<I, O> DowncastBothNode<I, O> {
}
}
}
/// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone)]
pub struct DowncastBothRefNode<I, O> {
node: SharedNodeContainer,
_i: PhantomData<(I, O)>,
}
impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode<I, O> {
type Output = DynFuture<'input, &'input O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
{
let node_name = self.node.node_name();
let input = Box::new(input);
Box::pin(async move {
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input).await).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e} in {node_name}"));
*out
})
}
}
}
impl<I, O> DowncastBothRefNode<I, O> {
pub const fn new(node: SharedNodeContainer) -> Self {
Self { node, _i: core::marker::PhantomData }
}
}
pub struct ComposeTypeErased {
first: SharedNodeContainer,
@@ -261,27 +237,30 @@ pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> Do
DowncastBothNode::new(n)
}
pub struct PanicNode<I, O>(PhantomData<I>, PhantomData<O>);
pub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);
impl<'i, I: 'i, O: 'i> Node<'i, I> for PanicNode<I, O> {
impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {
type Output = O;
fn eval(&'i self, _: I) -> Self::Output {
unimplemented!("This node should never be evaluated")
}
}
impl<I, O> PanicNode<I, O> {
impl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {
pub const fn new() -> Self {
Self(PhantomData, PhantomData)
}
}
impl<I, O> Default for PanicNode<I, O> {
impl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {
fn default() -> Self {
Self::new()
}
}
// TODO: Evaluate safety
unsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -6,10 +6,10 @@ use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample};
use graphene_core::raster::{BlendMode, BlendNode};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{ClonedNode, CopiedNode, OnceCellNode, ValueNode};
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
use graphene_core::vector::VectorData;
use graphene_core::Node;
use graphene_core::{Node, WasmNotSend};
use node_macro::node_fn;
use glam::{DAffine2, DVec2};
@@ -35,10 +35,11 @@ pub struct ChainApplyNode<Value> {
}
#[node_fn(ChainApplyNode)]
async fn chain_apply<I: Iterator, T>(iter: I, mut value: T) -> T
async fn chain_apply<I: Iterator + WasmNotSend, T: WasmNotSend>(iter: I, value: T) -> T
where
I::Item: for<'a> Node<'a, T, Output = T>,
{
let mut value = value;
for lambda in iter {
value = lambda.eval(value);
}
@@ -304,7 +305,7 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
background_bounds = bounds.transform;
}
let mut actual_image = ExtendImageToBoundsNode::new(OnceCellNode::new(background_bounds)).eval(brush_plan.background);
let mut actual_image = ExtendImageToBoundsNode::new(ClonedNode::new(background_bounds)).eval(brush_plan.background);
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {
// Create brush texture.

View File

@@ -1,7 +1,5 @@
use dyn_any::StaticTypeSized;
use glam::{DAffine2, DVec2, Mat2, Vec2};
use gpu_executor::{Bindgroup, ComputePassDimensions, PipelineLayout, StorageBufferOptions};
use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput};
use gpu_executor::{ComputePassDimensions, StorageBufferOptions};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::*;
use graph_craft::proto::*;
@@ -9,14 +7,16 @@ use graphene_core::application_io::ApplicationIo;
use graphene_core::quantization::QuantizationChannels;
use graphene_core::raster::*;
use graphene_core::*;
use wgpu_executor::WgpuExecutor;
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
use glam::{DAffine2, DVec2, Mat2, Vec2};
#[cfg(feature = "quantization")]
use graphene_core::quantization::PackedPixel;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use crate::wasm_application_io::WasmApplicationIo;
@@ -27,7 +27,8 @@ pub struct GpuCompiler<TypingContext, ShaderIO> {
// TODO: Move to graph-craft
#[node_macro::node_fn(GpuCompiler)]
async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
async fn compile_gpu(node: &'input DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
let mut typing_context = typing_context;
let compiler = graph_craft::graphene_compiler::Compiler {};
let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() };
let proto_networks: Vec<_> = compiler.compile(network.clone())?.collect();
@@ -50,15 +51,15 @@ 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>>>,
cache: Mutex<HashMap<String, ComputePass>>,
}
struct ComputePass<T: GpuExecutor> {
pipeline_layout: PipelineLayout<T>,
readback_buffer: Option<Arc<ShaderInput<T>>>,
struct ComputePass {
pipeline_layout: PipelineLayout,
readback_buffer: Option<Arc<WgpuShaderInput>>,
}
impl<T: GpuExecutor> Clone for ComputePass<T> {
impl Clone for ComputePass {
fn clone(&self) -> Self {
Self {
pipeline_layout: self.pipeline_layout.clone(),
@@ -96,15 +97,15 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
};
// 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()
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key(&node.name) {
self.cache.lock().as_ref().unwrap().get(&node.name).unwrap().clone()
} else {
let name = node.name.to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor, quantization).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrame::empty();
};
self.cache.borrow_mut().insert(name, compute_pass_descriptor.clone());
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
log::error!("created compute pass");
compute_pass_descriptor
};
@@ -154,7 +155,7 @@ impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
Self {
node,
editor_api,
cache: RefCell::new(HashMap::new()),
cache: Mutex::new(HashMap::new()),
}
}
}
@@ -164,7 +165,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
image: &ImageFrame<T>,
executor: &&WgpuExecutor,
quantization: QuantizationChannels,
) -> Result<ComputePass<WgpuExecutor>, String> {
) -> Result<ComputePass, String> {
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
@@ -335,7 +336,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
buffers: vec![width_uniform, storage_buffer],
};
let shader = gpu_executor::Shader {
let shader = Shader {
source: shader.spirv_binary.into(),
name: "gpu::eval",
io: shader.io,
@@ -557,7 +558,7 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
],
};
let shader = gpu_executor::Shader {
let shader = Shader {
source: shader.spirv_binary.into(),
name: "gpu::eval",
io: shader.io,

View File

@@ -219,7 +219,7 @@ impl Default for ImaginateImageToImageRequestOverrideSettings {
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct ImaginateTextToImageRequest<'a> {
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
common: ImaginateCommonImageRequest<'a>,
override_settings: ImaginateTextToImageRequestOverrideSettings,
}
@@ -237,13 +237,13 @@ struct ImaginateMask {
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct ImaginateImageToImageRequest<'a> {
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
common: ImaginateCommonImageRequest<'a>,
override_settings: ImaginateImageToImageRequestOverrideSettings,
init_images: Vec<String>,
denoising_strength: f64,
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
mask: Option<ImaginateMask>,
}
@@ -262,7 +262,7 @@ struct ImaginateCommonImageRequest<'a> {
sampler_index: &'a str,
}
#[cfg(feature = "imaginate")]
#[cfg(all(feature = "imaginate", feature = "serde"))]
#[allow(clippy::too_many_arguments)]
pub async fn imaginate<'a, P: Pixel>(
image: Image<P>,
@@ -328,7 +328,7 @@ pub async fn imaginate<'a, P: Pixel>(
})
}
#[cfg(feature = "imaginate")]
#[cfg(all(feature = "imaginate", feature = "serde"))]
#[allow(clippy::too_many_arguments)]
async fn imaginate_maybe_fail<'a, P: Pixel, F: Fn(ImaginateStatus)>(
image: Image<P>,

View File

@@ -92,7 +92,7 @@ fn generate_quantization<const N: usize>(data: Vec<f64>, samples: usize, channel
}*/
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 data: Vec<f64> = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel]).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);

View File

@@ -10,7 +10,7 @@ use graphene_core::raster::{
};
use graphene_core::transform::{Footprint, Transform};
use graphene_core::value::CopiedNode;
use graphene_core::{AlphaBlending, Color, Node};
use graphene_core::{AlphaBlending, Color, Node, WasmNotSend};
use fastnoise_lite;
use glam::{DAffine2, DVec2, UVec2, Vec2};
@@ -265,12 +265,15 @@ pub struct BlendImageNode<P, Background, MapFn> {
}
#[node_macro::node_fn(BlendImageNode<_P>)]
async fn blend_image_node<_P: Alpha + Pixel + Debug, Forground: Sample<Pixel = _P> + Transform>(
async fn blend_image_node<_P: Alpha + Pixel + Debug + WasmNotSend + Sync + 'static, MapFn, Forground: Sample<Pixel = _P> + Transform + Send>(
foreground: Forground,
background: ImageFrame<_P>,
map_fn: impl Node<(_P, _P), Output = _P>,
) -> ImageFrame<_P> {
blend_new_image(foreground, background, &self.map_fn)
map_fn: &'input MapFn,
) -> ImageFrame<_P>
where
for<'a> MapFn: Node<'a, (_P, _P), Output = _P> + 'input,
{
blend_new_image(foreground, background, map_fn)
}
#[derive(Debug, Clone, Copy)]
@@ -468,13 +471,14 @@ fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> {
}
}
#[cfg(feature = "serde")]
macro_rules! generate_imaginate_node {
($($val:ident: $t:ident: $o:ty,)*) => {
pub struct ImaginateNode<P: Pixel, E, C, $($t,)*> {
editor_api: E,
controller: C,
$($val: $t,)*
cache: std::sync::Mutex<HashMap<u64, Image<P>>>,
cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
}
impl<'e, P: Pixel, E, C, $($t,)*> ImaginateNode<P, E, C, $($t,)*>
@@ -488,7 +492,7 @@ macro_rules! generate_imaginate_node {
}
}
impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, $($t,)*>
impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, $($t,)*>
where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
@@ -503,19 +507,20 @@ macro_rules! generate_imaginate_node {
let mut hasher = rustc_hash::FxHasher::default();
frame.image.hash(&mut hasher);
let hash = hasher.finish();
let editor_api = self.editor_api.eval(());
let cache = self.cache.clone();
Box::pin(async move {
let controller: std::pin::Pin<Box<dyn std::future::Future<Output = ImaginateController>>> = controller;
// let controller: std::pin::Pin<Box<dyn std::future::Future<Output = ImaginateController> + Send>> = controller;
let controller: ImaginateController = controller.await;
if controller.take_regenerate_trigger() {
let editor_api = self.editor_api.eval(());
let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
self.cache.lock().unwrap().insert(hash, image.clone());
cache.lock().unwrap().insert(hash, image.clone());
return ImageFrame { image, ..frame }
}
let image = self.cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
ImageFrame { image, ..frame }
})
@@ -524,6 +529,7 @@ macro_rules! generate_imaginate_node {
}
}
#[cfg(feature = "serde")]
generate_imaginate_node! {
seed: Seed: f64,
res: Res: Option<DVec2>,

View File

@@ -1,13 +1,12 @@
use crate::Node;
use bezier_rs::{ManipulatorGroup, Subpath};
use graphene_core::raster::ImageFrame;
use graphene_core::transform::Transform;
pub use graphene_core::vector::*;
use graphene_core::Color;
use graphene_core::{transform::Footprint, GraphicGroup};
use graphene_core::{vector::misc::BooleanOperation, GraphicElement};
use futures::Future;
use glam::{DAffine2, DVec2};
use wasm_bindgen::prelude::*;
@@ -17,11 +16,7 @@ pub struct BinaryBooleanOperationNode<LowerVectorData, BooleanOp> {
}
#[node_macro::node_fn(BinaryBooleanOperationNode)]
async fn binary_boolean_operation_node<Fut: Future<Output = VectorData>>(
upper_vector_data: VectorData,
lower_vector_data: impl Node<Footprint, Output = Fut>,
boolean_operation: BooleanOperation,
) -> VectorData {
async fn binary_boolean_operation_node(upper_vector_data: VectorData, lower_vector_data: impl Node<Footprint, Output = VectorData>, boolean_operation: BooleanOperation) -> VectorData {
let lower_vector_data = self.lower_vector_data.eval(Footprint::default()).await;
let transform_of_lower_into_space_of_upper = upper_vector_data.transform.inverse() * lower_vector_data.transform;
@@ -58,11 +53,11 @@ pub struct BooleanOperationNode<BooleanOp> {
#[node_macro::node_fn(BooleanOperationNode)]
fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: BooleanOperation) -> VectorData {
fn vector_from_image<P: graphene_core::raster::Pixel>(image_frame: &ImageFrame<P>) -> VectorData {
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
let corner1 = DVec2::ZERO;
let corner2 = DVec2::new(1., 1.);
let mut subpath = Subpath::new_rect(corner1, corner2);
subpath.apply_transform(image_frame.transform);
subpath.apply_transform(image_frame.transform());
let mut vector_data = VectorData::from_subpath(subpath);
vector_data
.style
@@ -79,6 +74,7 @@ fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: Boolea
boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union)
}
GraphicElement::ImageFrame(image) => vector_from_image(image),
GraphicElement::Surface(image) => vector_from_image(image),
}
}

View File

@@ -1,39 +1,49 @@
use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig, SurfaceHandle, SurfaceHandleFrame};
use dyn_any::DynFuture;
pub use graph_craft::wasm_application_io::*;
#[cfg(target_arch = "wasm32")]
use graphene_core::application_io::SurfaceHandle;
use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_arch = "wasm32")]
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::Image;
use graphene_core::raster::{color::SRGBA8, ImageFrame};
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::{Footprint, TransformMut};
use graphene_core::Color;
use graphene_core::transform::Footprint;
use graphene_core::Node;
use graphene_core::{Color, WasmNotSend};
#[cfg(target_arch = "wasm32")]
use base64::Engine;
use glam::DAffine2;
use core::future::Future;
#[cfg(target_arch = "wasm32")]
use glam::DAffine2;
use std::marker::PhantomData;
use std::sync::Arc;
use wasm_bindgen::{Clamped, JsCast};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::Clamped;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
pub use graph_craft::wasm_application_io::*;
pub type WasmSurfaceHandle = SurfaceHandle<HtmlCanvasElement>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<HtmlCanvasElement>;
pub struct CreateSurfaceNode {}
#[node_macro::node_fn(CreateSurfaceNode)]
async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc<SurfaceHandle<<WasmApplicationIo as ApplicationIo>::Surface>> {
editor.application_io.as_ref().unwrap().create_surface().into()
async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
Arc::new(editor.application_io.as_ref().unwrap().create_surface())
}
#[cfg(target_arch = "wasm32")]
pub struct DrawImageFrameNode<Surface> {
surface_handle: Surface,
}
#[node_macro::node_fn(DrawImageFrameNode)]
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> SurfaceHandleFrame<HtmlCanvasElement> {
#[cfg(target_arch = "wasm32")]
async fn draw_image_frame_node<'a: 'input>(
image: ImageFrame<graphene_core::raster::SRGBA8>,
surface_handle: Arc<WasmSurfaceHandle>,
) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
let image_data = image.image.data;
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
if image.image.width > 0 && image.image.height > 0 {
@@ -45,7 +55,7 @@ async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_ha
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData");
context.put_image_data(&image_data, 0.0, 0.0).unwrap();
}
SurfaceHandleFrame {
graphene_core::application_io::SurfaceHandleFrame {
surface_handle,
transform: image.transform,
}
@@ -105,14 +115,14 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
RenderOutput::Svg(render.svg.to_svg_string())
}
#[cfg(any(feature = "resvg", feature = "vello"))]
fn _render_canvas(
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
fn render_canvas(
data: impl GraphicElementRendered,
mut render: SvgRender,
render_params: RenderParams,
footprint: Footprint,
editor: &'_ WasmEditorApi,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
surface_handle: wgpu_executor::WindowHandle,
) -> RenderOutput {
let resolution = footprint.resolution;
data.render_svg(&mut render, &render_params);
@@ -149,20 +159,26 @@ fn _render_canvas(
wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap();
context.draw_image_with_html_image_element(&image_data, 0.0, 0.0).unwrap();
*/
let frame = SurfaceHandleFrame {
let frame = graphene_core::application_io::SurfaceHandleFrame {
surface_handle,
transform: glam::DAffine2::IDENTITY,
};
RenderOutput::CanvasFrame(frame.into())
}
#[cfg(target_arch = "wasm32")]
pub struct RasterizeNode<Footprint, Surface> {
footprint: Footprint,
surface_handle: Surface,
}
#[node_macro::node_fn(RasterizeNode)]
async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, footprint: Footprint, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> ImageFrame<Color> {
#[cfg(target_arch = "wasm32")]
async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend>(
mut data: _T,
footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> {
let mut render = SvgRender::new();
if footprint.transform.matrix2.determinant() == 0. {
@@ -211,18 +227,27 @@ async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, foot
}
// Render with the data node taking in Footprint.
impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig>
for RenderNode<Data, Surface, Footprint>
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, Footprint>
where
Data: Node<'input, Footprint, Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
for<'a> Data: Node<'a, Footprint, Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
type Output = DynFuture<'input, RenderOutput>;
#[inline]
fn eval(&'input self, render_config: RenderConfig) -> Self::Output {
let footprint = render_config.viewport;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let RenderConfig { hide_artboards, for_export, .. } = render_config;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let data_fut = self.data.eval(footprint);
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let surface_fut = self.surface_handle.eval(());
Box::pin(async move {
let data = data_fut.await;
let footprint = render_config.viewport;
let RenderConfig { hide_artboards, for_export, .. } = render_config;
@@ -230,9 +255,9 @@ where
let output_format = render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint),
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
})
@@ -240,17 +265,25 @@ where
}
// Render with the data node taking in ().
impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig>
for RenderNode<Data, Surface, ()>
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, ()>
where
Data: Node<'input, (), Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
for<'a> Data: Node<'a, (), Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
type Output = DynFuture<'input, RenderOutput>;
#[inline]
fn eval(&'input self, render_config: RenderConfig) -> Self::Output {
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let RenderConfig { hide_artboards, for_export, .. } = render_config;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let data_fut = self.data.eval(());
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let surface_fut = self.surface_handle.eval(());
Box::pin(async move {
let data = data_fut.await;
let footprint = render_config.viewport;
let RenderConfig { hide_artboards, for_export, .. } = render_config;
@@ -258,14 +291,15 @@ where
let output_format = render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint),
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
})
}
}
#[automatically_derived]
impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
pub fn new(data: Data, _surface_handle: Surface) -> Self {