mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
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:
@@ -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::*;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user