Remove surface and window from ApplicationIo (#3941)
* Remove surface and window from ApplicationIo * Seperate Wasm and Native ApplicationIo * Fix warnings * Fix tests * Remove redundant PlatformApplicationIo::new_offscreen * Fixup * Remove unused From implementaitions for ApplicationIo
This commit is contained in:
parent
b100892bfa
commit
661e8bc569
|
|
@ -1942,6 +1942,23 @@ dependencies = [
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphene-canvas-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"core-types",
|
||||||
|
"dyn-any",
|
||||||
|
"glam",
|
||||||
|
"graphene-application-io",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"text-nodes",
|
||||||
|
"vector-types",
|
||||||
|
"web-sys",
|
||||||
|
"wgpu",
|
||||||
|
"wgpu-executor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphene-cli"
|
name = "graphene-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -1990,6 +2007,7 @@ dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
"graphene-application-io",
|
"graphene-application-io",
|
||||||
|
"graphene-canvas-utils",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
"graphic-nodes",
|
"graphic-nodes",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
|
|
@ -6601,7 +6619,6 @@ dependencies = [
|
||||||
"vello",
|
"vello",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ interpreted-executor = { path = "node-graph/interpreted-executor" }
|
||||||
node-macro = { path = "node-graph/node-macro" }
|
node-macro = { path = "node-graph/node-macro" }
|
||||||
wgpu-executor = { path = "node-graph/libraries/wgpu-executor" }
|
wgpu-executor = { path = "node-graph/libraries/wgpu-executor" }
|
||||||
graphite-proc-macros = { path = "proc-macros" }
|
graphite-proc-macros = { path = "proc-macros" }
|
||||||
|
graphite-editor = { path = "editor" }
|
||||||
|
graphene-canvas-utils = { path = "node-graph/libraries/canvas-utils" }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
rustc-hash = "2.0"
|
rustc-hash = "2.0"
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ gpu = ["graphite-editor/gpu", "graphene-std/shader-nodes"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
graphite-editor = { path = "../../editor", features = [
|
graphite-editor = { workspace = true }
|
||||||
"gpu",
|
|
||||||
] }
|
|
||||||
graphene-std = { workspace = true }
|
graphene-std = { workspace = true }
|
||||||
graph-craft = { workspace = true }
|
graph-craft = { workspace = true }
|
||||||
wgpu-executor = { workspace = true }
|
wgpu-executor = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use graph_craft::wasm_application_io::WasmApplicationIo;
|
use graph_craft::application_io::PlatformApplicationIo;
|
||||||
use graphite_editor::application::{Editor, Environment, Host, Platform};
|
use graphite_editor::application::{Editor, Environment, Host, Platform};
|
||||||
use graphite_editor::messages::prelude::{FrontendMessage, Message};
|
use graphite_editor::messages::prelude::{FrontendMessage, Message};
|
||||||
use message_dispatcher::DesktopWrapperMessageDispatcher;
|
use message_dispatcher::DesktopWrapperMessageDispatcher;
|
||||||
|
|
@ -38,7 +38,7 @@ impl DesktopWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&self, wgpu_context: WgpuContext) {
|
pub fn init(&self, wgpu_context: WgpuContext) {
|
||||||
let application_io = WasmApplicationIo::new_with_context(wgpu_context);
|
let application_io = PlatformApplicationIo::new_with_context(wgpu_context);
|
||||||
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl DesktopWrapper {
|
||||||
pub async fn execute_node_graph() -> NodeGraphExecutionResult {
|
pub async fn execute_node_graph() -> NodeGraphExecutionResult {
|
||||||
let result = graphite_editor::node_graph_executor::run_node_graph().await;
|
let result = graphite_editor::node_graph_executor::run_node_graph().await;
|
||||||
match result {
|
match result {
|
||||||
(true, texture) => NodeGraphExecutionResult::HasRun(texture.map(|t| t.texture)),
|
(true, texture) => NodeGraphExecutionResult::HasRun(texture.map(Into::into)),
|
||||||
(false, _) => NodeGraphExecutionResult::NotRun,
|
(false, _) => NodeGraphExecutionResult::NotRun,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ repository = "https://github.com/GraphiteEditor/Graphite"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wasm", "gpu"]
|
default = ["gpu"]
|
||||||
wasm = ["wasm-bindgen", "graphene-std/wasm"]
|
wasm = ["graphene-std/wasm", "interpreted-executor/wasm", "dep:wasm-bindgen"]
|
||||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
gpu = ["interpreted-executor/gpu", "dep:wgpu-executor"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ pub enum FrontendMessage {
|
||||||
TriggerOpen,
|
TriggerOpen,
|
||||||
TriggerImport,
|
TriggerImport,
|
||||||
TriggerSavePreferences {
|
TriggerSavePreferences {
|
||||||
#[tsify(type = "unknown")]
|
#[cfg_attr(feature = "wasm", tsify(type = "unknown"))]
|
||||||
preferences: PreferencesMessageHandler,
|
preferences: PreferencesMessageHandler,
|
||||||
},
|
},
|
||||||
TriggerSaveWorkspaceLayout {
|
TriggerSaveWorkspaceLayout {
|
||||||
|
|
|
||||||
|
|
@ -947,12 +947,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::import(concrete!(String), 1)],
|
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::import(concrete!(String), 1)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::load_resource::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::decode_image::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -1010,8 +1010,8 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::create_canvas::IDENTIFIER),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
@ -1022,7 +1022,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::rasterize::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ use graphene_std::vector::misc::ManipulatorPointId;
|
||||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{LazyLock, Mutex};
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
|
pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
|
||||||
let window = web_sys::window()?;
|
let window = web_sys::window()?;
|
||||||
let document = window.document()?;
|
let document = window.document()?;
|
||||||
|
|
@ -20,6 +22,7 @@ pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
|
||||||
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
||||||
let create_context = || {
|
let create_context = || {
|
||||||
let context = overlay_canvas_element()?.get_context("2d").ok().flatten()?;
|
let context = overlay_canvas_element()?.get_context("2d").ok().flatten()?;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::application_io::EditorPreferences;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct PreferencesMessageContext<'a> {
|
pub struct PreferencesMessageContext<'a> {
|
||||||
|
|
@ -51,7 +51,7 @@ impl PreferencesMessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supports_wgpu(&self) -> bool {
|
pub fn supports_wgpu(&self) -> bool {
|
||||||
graph_craft::wasm_application_io::wgpu_available().unwrap_or_default()
|
graph_craft::application_io::wgpu_available().unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
use graph_craft::document::value::{RenderOutput, TaggedValue};
|
use graph_craft::application_io::EditorPreferences;
|
||||||
|
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
|
||||||
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation};
|
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation};
|
||||||
use graphene_std::raster::{CPU, Raster};
|
use graphene_std::raster::{CPU, Raster};
|
||||||
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
|
use graphene_std::renderer::RenderMetadata;
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::wasm_application_io::RenderOutputType;
|
|
||||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||||
|
|
||||||
mod runtime_io;
|
mod runtime_io;
|
||||||
|
|
@ -380,6 +379,7 @@ impl NodeGraphExecutor {
|
||||||
let (data, width, height) = raster.to_flat_u8();
|
let (data, width, height) = raster.to_flat_u8();
|
||||||
responses.add(EyedropperToolMessage::PreviewImage { data, width, height });
|
responses.add(EyedropperToolMessage::PreviewImage { data, width, height });
|
||||||
}
|
}
|
||||||
|
NodeGraphUpdate::NodeGraphUpdateMessage(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,12 +397,11 @@ impl NodeGraphExecutor {
|
||||||
responses.add(FrontendMessage::UpdateImageData { image_data });
|
responses.add(FrontendMessage::UpdateImageData { image_data });
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
}
|
}
|
||||||
RenderOutputType::CanvasFrame(frame) => {
|
#[cfg(target_family = "wasm")]
|
||||||
let matrix = format_transform_matrix(frame.transform);
|
RenderOutputType::CanvasFrame { canvas_id, resolution } => {
|
||||||
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
|
|
||||||
let svg = format!(
|
let svg = format!(
|
||||||
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}" data-is-viewport="true"></div></foreignObject></svg>"#,
|
r#"<svg><foreignObject width="{}" height="{}"><div data-canvas-placeholder="{}" data-is-viewport="true"></div></foreignObject></svg>"#,
|
||||||
frame.resolution.x, frame.resolution.y, frame.surface_id.0,
|
resolution.x, resolution.y, canvas_id,
|
||||||
);
|
);
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||||
use glam::{DAffine2, UVec2};
|
use glam::{DAffine2, UVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
||||||
|
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
|
||||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||||
use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||||
use graphene_std::bounds::RenderBoundingBox;
|
use graphene_std::bounds::RenderBoundingBox;
|
||||||
use graphene_std::memo::IORecord;
|
use graphene_std::memo::IORecord;
|
||||||
use graphene_std::ops::Convert;
|
use graphene_std::ops::Convert;
|
||||||
|
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
|
||||||
|
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
|
||||||
use graphene_std::raster_types::Raster;
|
use graphene_std::raster_types::Raster;
|
||||||
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment};
|
||||||
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
|
|
||||||
use graphene_std::table::{Table, TableRow};
|
use graphene_std::table::{Table, TableRow};
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
use graphene_std::transform::RenderQuality;
|
use graphene_std::transform::RenderQuality;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::vector::style::RenderMode;
|
use graphene_std::vector::style::RenderMode;
|
||||||
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
|
|
||||||
use graphene_std::{Artboard, Context, Graphic};
|
use graphene_std::{Artboard, Context, Graphic};
|
||||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||||
use interpreted_executor::util::wrap_network_in_scope;
|
use interpreted_executor::util::wrap_network_in_scope;
|
||||||
|
|
@ -28,7 +28,7 @@ use std::sync::Arc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`.
|
/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`.
|
||||||
/// Some of these fields are put into a [`WasmEditorApi`] which is passed to the final compiled graph network upon each execution.
|
/// Some of these fields are put into a [`PlatformEditorApi`] which is passed to the final compiled graph network upon each execution.
|
||||||
/// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor.
|
/// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor.
|
||||||
pub struct NodeRuntime {
|
pub struct NodeRuntime {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -41,7 +41,7 @@ pub struct NodeRuntime {
|
||||||
old_graph: Option<NodeNetwork>,
|
old_graph: Option<NodeNetwork>,
|
||||||
update_thumbnails: bool,
|
update_thumbnails: bool,
|
||||||
|
|
||||||
editor_api: Arc<WasmEditorApi>,
|
editor_api: Arc<PlatformEditorApi>,
|
||||||
node_graph_errors: GraphErrors,
|
node_graph_errors: GraphErrors,
|
||||||
monitor_nodes: Vec<Vec<NodeId>>,
|
monitor_nodes: Vec<Vec<NodeId>>,
|
||||||
|
|
||||||
|
|
@ -57,10 +57,10 @@ pub struct NodeRuntime {
|
||||||
vector_modify: HashMap<NodeId, Vector>,
|
vector_modify: HashMap<NodeId, Vector>,
|
||||||
|
|
||||||
/// Cached surface for Wasm viewport rendering (reused across frames)
|
/// Cached surface for Wasm viewport rendering (reused across frames)
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
|
||||||
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
|
wasm_canvas_cache: CanvasSurfaceHandle,
|
||||||
/// Currently displayed texture, the runtime keeps a reference to it to avoid the texture getting destroyed while it is still in use.
|
/// Currently displayed texture, the runtime keeps a reference to it to avoid the texture getting destroyed while it is still in use.
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
|
||||||
current_viewport_texture: Option<ImageTexture>,
|
current_viewport_texture: Option<ImageTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ impl NodeRuntime {
|
||||||
old_graph: None,
|
old_graph: None,
|
||||||
update_thumbnails: true,
|
update_thumbnails: true,
|
||||||
|
|
||||||
editor_api: WasmEditorApi {
|
editor_api: PlatformEditorApi {
|
||||||
font_cache: FontCache::default(),
|
font_cache: FontCache::default(),
|
||||||
editor_preferences: Box::new(EditorPreferences::default()),
|
editor_preferences: Box::new(EditorPreferences::default()),
|
||||||
node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),
|
node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),
|
||||||
|
|
@ -146,7 +146,7 @@ impl NodeRuntime {
|
||||||
vector_modify: Default::default(),
|
vector_modify: Default::default(),
|
||||||
inspect_state: None,
|
inspect_state: None,
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
wasm_viewport_surface: None,
|
wasm_canvas_cache: CanvasSurfaceHandle::new(),
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
current_viewport_texture: None,
|
current_viewport_texture: None,
|
||||||
}
|
}
|
||||||
|
|
@ -154,11 +154,11 @@ impl NodeRuntime {
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Option<ImageTexture> {
|
pub async fn run(&mut self) -> Option<ImageTexture> {
|
||||||
if self.editor_api.application_io.is_none() {
|
if self.editor_api.application_io.is_none() {
|
||||||
self.editor_api = WasmEditorApi {
|
self.editor_api = PlatformEditorApi {
|
||||||
#[cfg(all(not(test), target_family = "wasm"))]
|
#[cfg(all(not(test), target_family = "wasm"))]
|
||||||
application_io: Some(WasmApplicationIo::new().await.into()),
|
application_io: Some(PlatformApplicationIo::new().await.into()),
|
||||||
#[cfg(any(test, not(target_family = "wasm")))]
|
#[cfg(any(test, not(target_family = "wasm")))]
|
||||||
application_io: Some(WasmApplicationIo::new_offscreen().await.into()),
|
application_io: Some(PlatformApplicationIo::new().await.into()),
|
||||||
font_cache: self.editor_api.font_cache.clone(),
|
font_cache: self.editor_api.font_cache.clone(),
|
||||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||||
editor_preferences: Box::new(self.editor_preferences.clone()),
|
editor_preferences: Box::new(self.editor_preferences.clone()),
|
||||||
|
|
@ -208,7 +208,7 @@ impl NodeRuntime {
|
||||||
for request in requests {
|
for request in requests {
|
||||||
match request {
|
match request {
|
||||||
GraphRuntimeRequest::FontCacheUpdate(font_cache) => {
|
GraphRuntimeRequest::FontCacheUpdate(font_cache) => {
|
||||||
self.editor_api = WasmEditorApi {
|
self.editor_api = PlatformEditorApi {
|
||||||
font_cache,
|
font_cache,
|
||||||
application_io: self.editor_api.application_io.clone(),
|
application_io: self.editor_api.application_io.clone(),
|
||||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||||
|
|
@ -222,7 +222,7 @@ impl NodeRuntime {
|
||||||
}
|
}
|
||||||
GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => {
|
GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => {
|
||||||
self.editor_preferences = preferences.clone();
|
self.editor_preferences = preferences.clone();
|
||||||
self.editor_api = WasmEditorApi {
|
self.editor_api = PlatformEditorApi {
|
||||||
font_cache: self.editor_api.font_cache.clone(),
|
font_cache: self.editor_api.font_cache.clone(),
|
||||||
application_io: self.editor_api.application_io.clone(),
|
application_io: self.editor_api.application_io.clone(),
|
||||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||||
|
|
@ -280,7 +280,7 @@ impl NodeRuntime {
|
||||||
.gpu_executor()
|
.gpu_executor()
|
||||||
.expect("GPU executor should be available when we receive a texture");
|
.expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
let raster_cpu = Raster::new_gpu(image_texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
||||||
|
|
||||||
let (data, width, height) = raster_cpu.to_flat_u8();
|
let (data, width, height) = raster_cpu.to_flat_u8();
|
||||||
|
|
||||||
|
|
@ -304,7 +304,7 @@ impl NodeRuntime {
|
||||||
.gpu_executor()
|
.gpu_executor()
|
||||||
.expect("GPU executor should be available when we receive a texture");
|
.expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
let raster_cpu = Raster::new_gpu(image_texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
||||||
|
|
||||||
self.sender.send_eyedropper_preview(raster_cpu);
|
self.sender.send_eyedropper_preview(raster_cpu);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -318,83 +318,20 @@ impl NodeRuntime {
|
||||||
data: RenderOutputType::Texture(image_texture),
|
data: RenderOutputType::Texture(image_texture),
|
||||||
metadata,
|
metadata,
|
||||||
})) if !render_config.for_export => {
|
})) if !render_config.for_export => {
|
||||||
// On Wasm, for viewport rendering, blit the texture to a surface and return a CanvasFrame
|
self.current_viewport_texture = Some(image_texture.clone());
|
||||||
|
|
||||||
let app_io = self.editor_api.application_io.as_ref().unwrap();
|
let app_io = self.editor_api.application_io.as_ref().unwrap();
|
||||||
let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture");
|
let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
// Get or create the cached surface
|
self.wasm_canvas_cache.present(&image_texture, executor);
|
||||||
if self.wasm_viewport_surface.is_none() {
|
|
||||||
let surface_handle = app_io.create_window();
|
|
||||||
let wasm_surface = executor
|
|
||||||
.create_surface(graphene_std::wasm_application_io::WasmSurfaceHandle {
|
|
||||||
surface: surface_handle.surface.clone(),
|
|
||||||
window_id: surface_handle.window_id,
|
|
||||||
})
|
|
||||||
.expect("Failed to create surface");
|
|
||||||
self.wasm_viewport_surface = Some(Arc::new(wasm_surface));
|
|
||||||
}
|
|
||||||
|
|
||||||
let surface = self.wasm_viewport_surface.as_ref().unwrap();
|
|
||||||
|
|
||||||
// Use logical resolution for CSS sizing, physical resolution for the actual surface/texture
|
|
||||||
let physical_resolution = render_config.viewport.resolution;
|
|
||||||
let logical_resolution = physical_resolution.as_dvec2() / render_config.scale;
|
|
||||||
|
|
||||||
// Blit the texture to the surface
|
|
||||||
let mut encoder = executor.context.device.create_command_encoder(&vello::wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("Texture to Surface Blit"),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure the surface at physical resolution (for HiDPI displays)
|
|
||||||
let surface_inner = &surface.surface.inner;
|
|
||||||
let surface_caps = surface_inner.get_capabilities(&executor.context.adapter);
|
|
||||||
surface_inner.configure(
|
|
||||||
&executor.context.device,
|
|
||||||
&vello::wgpu::SurfaceConfiguration {
|
|
||||||
usage: vello::wgpu::TextureUsages::RENDER_ATTACHMENT | vello::wgpu::TextureUsages::COPY_DST,
|
|
||||||
format: vello::wgpu::TextureFormat::Rgba8Unorm,
|
|
||||||
width: physical_resolution.x,
|
|
||||||
height: physical_resolution.y,
|
|
||||||
present_mode: surface_caps.present_modes[0],
|
|
||||||
alpha_mode: vello::wgpu::CompositeAlphaMode::PreMultiplied,
|
|
||||||
view_formats: vec![],
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let surface_texture = surface_inner.get_current_texture().expect("Failed to get surface texture");
|
|
||||||
self.current_viewport_texture = Some(image_texture.clone());
|
|
||||||
|
|
||||||
encoder.copy_texture_to_texture(
|
|
||||||
vello::wgpu::TexelCopyTextureInfoBase {
|
|
||||||
texture: image_texture.texture.as_ref(),
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Default::default(),
|
|
||||||
aspect: Default::default(),
|
|
||||||
},
|
|
||||||
vello::wgpu::TexelCopyTextureInfoBase {
|
|
||||||
texture: &surface_texture.texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Default::default(),
|
|
||||||
aspect: Default::default(),
|
|
||||||
},
|
|
||||||
image_texture.texture.size(),
|
|
||||||
);
|
|
||||||
|
|
||||||
executor.context.queue.submit([encoder.finish()]);
|
|
||||||
surface_texture.present();
|
|
||||||
|
|
||||||
// TODO: Figure out if we can explicityl destroy the wgpu texture here to reduce the allocation pressure. We might also be able to use a texture allocation pool
|
|
||||||
|
|
||||||
let frame = graphene_std::application_io::SurfaceFrame {
|
|
||||||
surface_id: surface.window_id,
|
|
||||||
resolution: logical_resolution,
|
|
||||||
transform: glam::DAffine2::IDENTITY,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let logical_resolution = render_config.viewport.resolution.as_dvec2() / render_config.scale;
|
||||||
(
|
(
|
||||||
Ok(TaggedValue::RenderOutput(RenderOutput {
|
Ok(TaggedValue::RenderOutput(RenderOutput {
|
||||||
data: RenderOutputType::CanvasFrame(frame),
|
data: RenderOutputType::CanvasFrame {
|
||||||
|
canvas_id: self.wasm_canvas_cache.id(),
|
||||||
|
resolution: logical_resolution,
|
||||||
|
},
|
||||||
metadata,
|
metadata,
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
|
|
@ -592,10 +529,10 @@ pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
|
||||||
let mut node_runtime = NODE_RUNTIME.lock();
|
let mut node_runtime = NODE_RUNTIME.lock();
|
||||||
node_runtime.replace(runtime)
|
node_runtime.replace(runtime)
|
||||||
}
|
}
|
||||||
pub async fn replace_application_io(application_io: WasmApplicationIo) {
|
pub async fn replace_application_io(application_io: PlatformApplicationIo) {
|
||||||
let mut node_runtime = NODE_RUNTIME.lock();
|
let mut node_runtime = NODE_RUNTIME.lock();
|
||||||
if let Some(node_runtime) = &mut *node_runtime {
|
if let Some(node_runtime) = &mut *node_runtime {
|
||||||
node_runtime.editor_api = WasmEditorApi {
|
node_runtime.editor_api = PlatformEditorApi {
|
||||||
font_cache: node_runtime.editor_api.font_cache.clone(),
|
font_cache: node_runtime.editor_api.font_cache.clone(),
|
||||||
application_io: Some(application_io.into()),
|
application_io: Some(application_io.into()),
|
||||||
node_graph_message_sender: Box::new(node_runtime.sender.clone()),
|
node_graph_message_sender: Box::new(node_runtime.sender.clone()),
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
editor = { path = "../../editor", package = "graphite-editor", features = [
|
editor = { path = "../../editor", package = "graphite-editor", features = ["gpu", "wasm"] }
|
||||||
"gpu",
|
|
||||||
] }
|
|
||||||
graphene-std = { workspace = true }
|
graphene-std = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
use dyn_any::StaticType;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
mod native;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
mod wasm;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
pub type PlatformApplicationIo = native::NativeApplicationIo;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
pub type PlatformApplicationIo = wasm::WasmApplicationIo;
|
||||||
|
|
||||||
|
pub type PlatformEditorApi = graphene_application_io::EditorApi<PlatformApplicationIo>;
|
||||||
|
|
||||||
|
static WGPU_AVAILABLE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
|
||||||
|
|
||||||
|
/// Returns:
|
||||||
|
/// - `None` if the availability of WGPU has not been determined yet
|
||||||
|
/// - `Some(true)` if WGPU is available
|
||||||
|
/// - `Some(false)` if WGPU is not available
|
||||||
|
pub fn wgpu_available() -> Option<bool> {
|
||||||
|
match WGPU_AVAILABLE.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
|
-1 => None,
|
||||||
|
0 => Some(false),
|
||||||
|
_ => Some(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_wgpu_available(available: bool) {
|
||||||
|
WGPU_AVAILABLE.store(available as i8, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct EditorPreferences {
|
||||||
|
/// Maximum render region size in pixels along one dimension of the square area.
|
||||||
|
pub max_render_region_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphene_application_io::GetEditorPreferences for EditorPreferences {
|
||||||
|
fn max_render_region_area(&self) -> u32 {
|
||||||
|
let size = self.max_render_region_size.min(u32::MAX.isqrt());
|
||||||
|
size.pow(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EditorPreferences {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { max_render_region_size: 1280 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl StaticType for EditorPreferences {
|
||||||
|
type Static = EditorPreferences;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
use dyn_any::StaticType;
|
||||||
|
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use wgpu_executor::WgpuExecutor;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct NativeApplicationIo {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub(crate) gpu_executor: Option<WgpuExecutor>,
|
||||||
|
pub resources: HashMap<String, Arc<[u8]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NativeApplicationIo {
|
||||||
|
pub async fn new() -> Self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let executor = WgpuExecutor::new().await;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
let wgpu_available = false;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let wgpu_available = executor.is_some();
|
||||||
|
super::set_wgpu_available(wgpu_available);
|
||||||
|
|
||||||
|
let mut io = Self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
gpu_executor: executor,
|
||||||
|
resources: HashMap::new(),
|
||||||
|
};
|
||||||
|
io.resources.insert("null".to_string(), Arc::from(include_bytes!("../null.png").to_vec()));
|
||||||
|
|
||||||
|
io
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub fn new_with_context(context: wgpu_executor::WgpuContext) -> Self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let executor = WgpuExecutor::with_context(context);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
let wgpu_available = false;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let wgpu_available = executor.is_some();
|
||||||
|
super::set_wgpu_available(wgpu_available);
|
||||||
|
|
||||||
|
let mut io = Self {
|
||||||
|
gpu_executor: executor,
|
||||||
|
resources: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
io.resources.insert("null".to_string(), Arc::from(include_bytes!("../null.png").to_vec()));
|
||||||
|
|
||||||
|
io
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationIo for NativeApplicationIo {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
type Executor = WgpuExecutor;
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
type Executor = ();
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||||
|
self.gpu_executor.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_resource(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError> {
|
||||||
|
let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;
|
||||||
|
log::trace!("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 ResourceFuture)
|
||||||
|
}
|
||||||
|
"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 ResourceFuture)
|
||||||
|
}
|
||||||
|
"graphite" => {
|
||||||
|
let path = url.path();
|
||||||
|
let path = path.to_owned();
|
||||||
|
log::trace!("Loading local resource: {path}");
|
||||||
|
let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();
|
||||||
|
Ok(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture)
|
||||||
|
}
|
||||||
|
_ => Err(ApplicationError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl StaticType for NativeApplicationIo {
|
||||||
|
type Static = NativeApplicationIo;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
use dyn_any::StaticType;
|
||||||
|
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use wgpu_executor::WgpuExecutor;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WasmApplicationIo {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub(crate) gpu_executor: Option<WgpuExecutor>,
|
||||||
|
pub resources: HashMap<String, Arc<[u8]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmApplicationIo {
|
||||||
|
pub async fn new() -> Self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) {
|
||||||
|
let request_adapter = || {
|
||||||
|
let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?;
|
||||||
|
let function = request_adapter.dyn_ref::<js_sys::Function>()?;
|
||||||
|
function.call0(&gpu).ok()
|
||||||
|
};
|
||||||
|
let result = request_adapter();
|
||||||
|
match result {
|
||||||
|
None => None,
|
||||||
|
Some(_) => WgpuExecutor::new().await,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
let wgpu_available = false;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
let wgpu_available = executor.is_some();
|
||||||
|
super::set_wgpu_available(wgpu_available);
|
||||||
|
|
||||||
|
let mut io = Self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
gpu_executor: executor,
|
||||||
|
resources: HashMap::new(),
|
||||||
|
};
|
||||||
|
io.resources.insert("null".to_string(), Arc::from(include_bytes!("../null.png").to_vec()));
|
||||||
|
|
||||||
|
io
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationIo for WasmApplicationIo {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
type Executor = WgpuExecutor;
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
type Executor = ();
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||||
|
self.gpu_executor.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_resource(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError> {
|
||||||
|
let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;
|
||||||
|
log::trace!("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 ResourceFuture)
|
||||||
|
}
|
||||||
|
"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 ResourceFuture)
|
||||||
|
}
|
||||||
|
"graphite" => {
|
||||||
|
let path = url.path();
|
||||||
|
let path = path.to_owned();
|
||||||
|
log::trace!("Loading local resource: {path}");
|
||||||
|
let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();
|
||||||
|
Ok(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture)
|
||||||
|
}
|
||||||
|
_ => Err(ApplicationError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl StaticType for WasmApplicationIo {
|
||||||
|
type Static = WasmApplicationIo;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::DocumentNode;
|
use super::DocumentNode;
|
||||||
|
use crate::application_io::PlatformEditorApi;
|
||||||
use crate::proto::{Any as DAny, FutureAny};
|
use crate::proto::{Any as DAny, FutureAny};
|
||||||
use crate::wasm_application_io::WasmEditorApi;
|
|
||||||
use brush_nodes::brush_cache::BrushCache;
|
use brush_nodes::brush_cache::BrushCache;
|
||||||
use brush_nodes::brush_stroke::BrushStroke;
|
use brush_nodes::brush_stroke::BrushStroke;
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
|
|
@ -10,7 +10,6 @@ use dyn_any::DynAny;
|
||||||
pub use dyn_any::StaticType;
|
pub use dyn_any::StaticType;
|
||||||
use glam::{Affine2, Vec2};
|
use glam::{Affine2, Vec2};
|
||||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
use graphene_application_io::{ImageTexture, SurfaceFrame};
|
|
||||||
use graphic_types::Artboard;
|
use graphic_types::Artboard;
|
||||||
use graphic_types::Graphic;
|
use graphic_types::Graphic;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
|
|
@ -40,9 +39,8 @@ macro_rules! tagged_value {
|
||||||
None,
|
None,
|
||||||
$( $(#[$meta] ) *$identifier( $ty ), )*
|
$( $(#[$meta] ) *$identifier( $ty ), )*
|
||||||
RenderOutput(RenderOutput),
|
RenderOutput(RenderOutput),
|
||||||
SurfaceFrame(SurfaceFrame),
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
EditorApi(Arc<WasmEditorApi>)
|
EditorApi(Arc<PlatformEditorApi>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
||||||
|
|
@ -54,7 +52,6 @@ macro_rules! tagged_value {
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
$( Self::$identifier(x) => {x.hash(state)}),*
|
$( Self::$identifier(x) => {x.hash(state)}),*
|
||||||
Self::RenderOutput(x) => x.hash(state),
|
Self::RenderOutput(x) => x.hash(state),
|
||||||
Self::SurfaceFrame(x) => x.hash(state),
|
|
||||||
Self::EditorApi(x) => x.hash(state),
|
Self::EditorApi(x) => x.hash(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +63,6 @@ macro_rules! tagged_value {
|
||||||
Self::None => Box::new(()),
|
Self::None => Box::new(()),
|
||||||
$( Self::$identifier(x) => Box::new(x), )*
|
$( Self::$identifier(x) => Box::new(x), )*
|
||||||
Self::RenderOutput(x) => Box::new(x),
|
Self::RenderOutput(x) => Box::new(x),
|
||||||
Self::SurfaceFrame(x) => Box::new(x),
|
|
||||||
Self::EditorApi(x) => Box::new(x),
|
Self::EditorApi(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +72,6 @@ macro_rules! tagged_value {
|
||||||
Self::None => Arc::new(()),
|
Self::None => Arc::new(()),
|
||||||
$( Self::$identifier(x) => Arc::new(x), )*
|
$( Self::$identifier(x) => Arc::new(x), )*
|
||||||
Self::RenderOutput(x) => Arc::new(x),
|
Self::RenderOutput(x) => Arc::new(x),
|
||||||
Self::SurfaceFrame(x) => Arc::new(x),
|
|
||||||
Self::EditorApi(x) => Arc::new(x),
|
Self::EditorApi(x) => Arc::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +81,7 @@ macro_rules! tagged_value {
|
||||||
Self::None => concrete!(()),
|
Self::None => concrete!(()),
|
||||||
$( Self::$identifier(_) => concrete!($ty), )*
|
$( Self::$identifier(_) => concrete!($ty), )*
|
||||||
Self::RenderOutput(_) => concrete!(RenderOutput),
|
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||||
Self::SurfaceFrame(_) => concrete!(SurfaceFrame),
|
Self::EditorApi(_) => concrete!(&PlatformEditorApi)
|
||||||
Self::EditorApi(_) => concrete!(&WasmEditorApi)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Attempts to downcast the dynamic type to a tagged value
|
/// Attempts to downcast the dynamic type to a tagged value
|
||||||
|
|
@ -99,8 +93,6 @@ macro_rules! tagged_value {
|
||||||
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
||||||
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
||||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||||
x if x == TypeId::of::<SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
|
||||||
|
|
||||||
|
|
||||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +105,7 @@ macro_rules! tagged_value {
|
||||||
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
||||||
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*
|
||||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
||||||
x if x == TypeId::of::<SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(SurfaceFrame::clone(input.downcast_ref().unwrap()))),
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))),
|
||||||
_ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`
|
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`
|
||||||
|
|
@ -148,8 +139,7 @@ macro_rules! tagged_value {
|
||||||
Self::None => "()".to_string(),
|
Self::None => "()".to_string(),
|
||||||
$( Self::$identifier(x) => format!("{:?}", x), )*
|
$( Self::$identifier(x) => format!("{:?}", x), )*
|
||||||
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
||||||
Self::SurfaceFrame(_) => "SurfaceFrame".to_string(),
|
Self::EditorApi(_) => "PlatformEditorApi".to_string(),
|
||||||
Self::EditorApi(_) => "WasmEditorApi".to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -482,11 +472,10 @@ pub struct RenderOutput {
|
||||||
pub metadata: RenderMetadata,
|
pub metadata: RenderMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum RenderOutputType {
|
pub enum RenderOutputType {
|
||||||
CanvasFrame(SurfaceFrame),
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
Texture(ImageTexture),
|
Texture(graphene_application_io::ImageTexture),
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
Buffer {
|
Buffer {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
|
@ -497,8 +486,36 @@ pub enum RenderOutputType {
|
||||||
svg: String,
|
svg: String,
|
||||||
image_data: Vec<(u64, Image<Color>)>,
|
image_data: Vec<(u64, Image<Color>)>,
|
||||||
},
|
},
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
CanvasFrame {
|
||||||
|
canvas_id: u64,
|
||||||
|
resolution: DVec2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for RenderOutputType {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Self::Texture(texture) => {
|
||||||
|
texture.hash(state);
|
||||||
|
}
|
||||||
|
Self::Buffer { data, width, height } => {
|
||||||
|
data.hash(state);
|
||||||
|
width.hash(state);
|
||||||
|
height.hash(state);
|
||||||
|
}
|
||||||
|
Self::Svg { svg, image_data } => {
|
||||||
|
svg.hash(state);
|
||||||
|
image_data.hash(state);
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
Self::CanvasFrame { canvas_id, resolution } => {
|
||||||
|
canvas_id.hash(state);
|
||||||
|
resolution.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Hash for RenderOutput {
|
impl Hash for RenderOutput {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.data.hash(state)
|
self.data.hash(state)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ extern crate core_types;
|
||||||
|
|
||||||
pub use core_types::{ProtoNodeIdentifier, Type, TypeDescriptor, concrete, generic};
|
pub use core_types::{ProtoNodeIdentifier, Type, TypeDescriptor, concrete, generic};
|
||||||
|
|
||||||
|
pub mod application_io;
|
||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod graphene_compiler;
|
pub mod graphene_compiler;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
#[cfg(feature = "loading")]
|
#[cfg(feature = "loading")]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod wasm_application_io;
|
|
||||||
|
|
|
||||||
|
|
@ -1,361 +0,0 @@
|
||||||
use dyn_any::StaticType;
|
|
||||||
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use js_sys::{Object, Reflect};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use std::sync::atomic::AtomicU64;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use web_sys::window;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
use wgpu_executor::WgpuExecutor;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct WindowWrapper {
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
window: SurfaceHandle<HtmlCanvasElement>,
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
window: SurfaceHandle<Arc<dyn winit::window::Window>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
impl Drop for WindowWrapper {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let window = window().expect("should have a window in this context");
|
|
||||||
let window = Object::from(window);
|
|
||||||
|
|
||||||
let image_canvases_key = JsValue::from_str("imageCanvases");
|
|
||||||
|
|
||||||
let wrapper = || {
|
|
||||||
if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {
|
|
||||||
// Convert key and value to JsValue
|
|
||||||
let js_key = JsValue::from_str(self.window.window_id.to_string().as_str());
|
|
||||||
|
|
||||||
// Use Reflect API to set property
|
|
||||||
Reflect::delete_property(&canvases.into(), &js_key)?;
|
|
||||||
}
|
|
||||||
Ok::<_, JsValue>(())
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper().expect("should be able to set canvas in global scope")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
unsafe impl Sync for WindowWrapper {}
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
unsafe impl Send for WindowWrapper {}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct WasmApplicationIo {
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
ids: AtomicU64,
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
pub(crate) gpu_executor: Option<WgpuExecutor>,
|
|
||||||
windows: Vec<WindowWrapper>,
|
|
||||||
pub resources: HashMap<String, Arc<[u8]>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
static WGPU_AVAILABLE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
|
|
||||||
|
|
||||||
/// Returns:
|
|
||||||
/// - `None` if the availability of WGPU has not been determined yet
|
|
||||||
/// - `Some(true)` if WGPU is available
|
|
||||||
/// - `Some(false)` if WGPU is not available
|
|
||||||
pub fn wgpu_available() -> Option<bool> {
|
|
||||||
match WGPU_AVAILABLE.load(Ordering::SeqCst) {
|
|
||||||
-1 => None,
|
|
||||||
0 => Some(false),
|
|
||||||
_ => Some(true),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WasmApplicationIo {
|
|
||||||
pub async fn new() -> Self {
|
|
||||||
#[cfg(all(feature = "wgpu", target_family = "wasm"))]
|
|
||||||
let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) {
|
|
||||||
let request_adapter = || {
|
|
||||||
let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?;
|
|
||||||
let function = request_adapter.dyn_ref::<js_sys::Function>()?;
|
|
||||||
Some(function.call0(&gpu).ok())
|
|
||||||
};
|
|
||||||
let result = request_adapter();
|
|
||||||
match result {
|
|
||||||
None => None,
|
|
||||||
Some(_) => WgpuExecutor::new().await,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(all(feature = "wgpu", not(target_family = "wasm")))]
|
|
||||||
let executor = WgpuExecutor::new().await;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
let wgpu_available = false;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
let wgpu_available = executor.is_some();
|
|
||||||
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
|
|
||||||
|
|
||||||
let mut io = Self {
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
ids: AtomicU64::new(0),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
gpu_executor: executor,
|
|
||||||
windows: Vec::new(),
|
|
||||||
resources: HashMap::new(),
|
|
||||||
};
|
|
||||||
let window = io.create_window();
|
|
||||||
io.windows.push(WindowWrapper { window });
|
|
||||||
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
|
|
||||||
|
|
||||||
io
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_offscreen() -> Self {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
let executor = WgpuExecutor::new().await;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
let wgpu_available = false;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
let wgpu_available = executor.is_some();
|
|
||||||
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
|
|
||||||
|
|
||||||
let mut io = Self {
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
ids: AtomicU64::new(0),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
gpu_executor: executor,
|
|
||||||
windows: Vec::new(),
|
|
||||||
resources: HashMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
|
|
||||||
|
|
||||||
io
|
|
||||||
}
|
|
||||||
#[cfg(all(not(target_family = "wasm"), feature = "wgpu"))]
|
|
||||||
pub fn new_with_context(context: wgpu_executor::WgpuContext) -> Self {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
let executor = WgpuExecutor::with_context(context);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
let wgpu_available = false;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
let wgpu_available = executor.is_some();
|
|
||||||
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
|
|
||||||
|
|
||||||
let mut io = Self {
|
|
||||||
gpu_executor: executor,
|
|
||||||
windows: Vec::new(),
|
|
||||||
resources: HashMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
|
|
||||||
|
|
||||||
io
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl StaticType for WasmApplicationIo {
|
|
||||||
type Static = WasmApplicationIo;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo {
|
|
||||||
fn from(editor_api: &'a WasmEditorApi) -> Self {
|
|
||||||
editor_api.application_io.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
|
|
||||||
fn from(app_io: &'a WasmApplicationIo) -> Self {
|
|
||||||
app_io.gpu_executor.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>;
|
|
||||||
|
|
||||||
impl ApplicationIo for WasmApplicationIo {
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
type Surface = HtmlCanvasElement;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
type Surface = Arc<dyn winit::window::Window>;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
type Executor = WgpuExecutor;
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
type Executor = ();
|
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
fn create_window(&self) -> SurfaceHandle<Self::Surface> {
|
|
||||||
let wrapper = || {
|
|
||||||
let document = window().expect("should have a window in this context").document().expect("window should have a document");
|
|
||||||
|
|
||||||
let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::<HtmlCanvasElement>()?;
|
|
||||||
let id = self.ids.fetch_add(1, Ordering::SeqCst);
|
|
||||||
// store the canvas in the global scope so it doesn't get garbage collected
|
|
||||||
let window = window().expect("should have a window in this context");
|
|
||||||
let window = Object::from(window);
|
|
||||||
|
|
||||||
let image_canvases_key = JsValue::from_str("imageCanvases");
|
|
||||||
|
|
||||||
let mut canvases = Reflect::get(&window, &image_canvases_key);
|
|
||||||
if canvases.is_err() {
|
|
||||||
Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap();
|
|
||||||
canvases = Reflect::get(&window, &image_canvases_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert key and value to JsValue
|
|
||||||
let js_key = JsValue::from_str(id.to_string().as_str());
|
|
||||||
let js_value = JsValue::from(canvas.clone());
|
|
||||||
|
|
||||||
let canvases = Object::from(canvases.unwrap());
|
|
||||||
|
|
||||||
// Use Reflect API to set property
|
|
||||||
Reflect::set(&canvases, &js_key, &js_value)?;
|
|
||||||
Ok::<_, JsValue>(SurfaceHandle {
|
|
||||||
window_id: SurfaceId(id),
|
|
||||||
surface: canvas,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper().expect("should be able to set canvas in global scope")
|
|
||||||
}
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
fn create_window(&self) -> SurfaceHandle<Self::Surface> {
|
|
||||||
todo!("winit api changed, calling create_window on EventLoop is deprecated");
|
|
||||||
|
|
||||||
// log::trace!("Spawning window");
|
|
||||||
|
|
||||||
// #[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
|
||||||
// use winit::platform::wayland::EventLoopBuilderExtWayland;
|
|
||||||
|
|
||||||
// #[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
|
||||||
// let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap();
|
|
||||||
// #[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))]
|
|
||||||
// let event_loop = winit::event_loop::EventLoop::new().unwrap();
|
|
||||||
|
|
||||||
// let window = event_loop
|
|
||||||
// .create_window(
|
|
||||||
// winit::window::WindowAttributes::default()
|
|
||||||
// .with_title("Graphite")
|
|
||||||
// .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)),
|
|
||||||
// )
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// SurfaceHandle {
|
|
||||||
// window_id: SurfaceId(window.id().into()),
|
|
||||||
// surface: Arc::new(window),
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
fn destroy_window(&self, surface_id: SurfaceId) {
|
|
||||||
let window = window().expect("should have a window in this context");
|
|
||||||
let window = Object::from(window);
|
|
||||||
|
|
||||||
let image_canvases_key = JsValue::from_str("imageCanvases");
|
|
||||||
|
|
||||||
let wrapper = || {
|
|
||||||
if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {
|
|
||||||
// Convert key and value to JsValue
|
|
||||||
let js_key = JsValue::from_str(surface_id.0.to_string().as_str());
|
|
||||||
|
|
||||||
// Use Reflect API to set property
|
|
||||||
Reflect::delete_property(&canvases.into(), &js_key)?;
|
|
||||||
}
|
|
||||||
Ok::<_, JsValue>(())
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper().expect("should be able to set canvas in global scope")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
fn destroy_window(&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<ResourceFuture, ApplicationError> {
|
|
||||||
let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;
|
|
||||||
log::trace!("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 ResourceFuture)
|
|
||||||
}
|
|
||||||
"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 ResourceFuture)
|
|
||||||
}
|
|
||||||
"graphite" => {
|
|
||||||
let path = url.path();
|
|
||||||
let path = path.to_owned();
|
|
||||||
log::trace!("Loading local resource: {path}");
|
|
||||||
let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();
|
|
||||||
Ok(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture)
|
|
||||||
}
|
|
||||||
_ => Err(ApplicationError::NotFound),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window(&self) -> Option<SurfaceHandle<Self::Surface>> {
|
|
||||||
self.windows.first().map(|wrapper| wrapper.window.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
pub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame<wgpu_executor::Window>;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct EditorPreferences {
|
|
||||||
/// Maximum render region size in pixels along one dimension of the square area.
|
|
||||||
pub max_render_region_size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl graphene_application_io::GetEditorPreferences for EditorPreferences {
|
|
||||||
fn max_render_region_area(&self) -> u32 {
|
|
||||||
let size = self.max_render_region_size.min(u32::MAX.isqrt());
|
|
||||||
size.pow(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EditorPreferences {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { max_render_region_size: 1280 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl StaticType for EditorPreferences {
|
|
||||||
type Static = EditorPreferences;
|
|
||||||
}
|
|
||||||
|
|
@ -3,14 +3,14 @@ mod export;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
use graph_craft::application_io::EditorPreferences;
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
use graph_craft::proto::ProtoNetwork;
|
use graph_craft::proto::ProtoNetwork;
|
||||||
use graph_craft::util::load_network;
|
use graph_craft::util::load_network;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
|
||||||
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
|
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
|
||||||
|
use graphene_std::application_io::{PlatformEditorApi, WasmApplicationIo};
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
|
||||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||||
use interpreted_executor::util::wrap_network_in_scope;
|
use interpreted_executor::util::wrap_network_in_scope;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
@ -121,7 +121,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let document_string = std::fs::read_to_string(document_path).expect("Failed to read document");
|
let document_string = std::fs::read_to_string(document_path).expect("Failed to read document");
|
||||||
|
|
||||||
log::info!("Creating GPU context");
|
log::info!("Creating GPU context");
|
||||||
let mut application_io = block_on(WasmApplicationIo::new_offscreen());
|
let mut application_io = block_on(WasmApplicationIo::new());
|
||||||
|
|
||||||
if let Command::Export { image: Some(ref image_path), .. } = app.command {
|
if let Command::Export { image: Some(ref image_path), .. } = app.command {
|
||||||
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
|
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
|
||||||
|
|
@ -140,7 +140,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let preferences = EditorPreferences {
|
let preferences = EditorPreferences {
|
||||||
max_render_region_size: EditorPreferences::default().max_render_region_size,
|
max_render_region_size: EditorPreferences::default().max_render_region_size,
|
||||||
};
|
};
|
||||||
let editor_api = Arc::new(WasmEditorApi {
|
let editor_api = Arc::new(PlatformEditorApi {
|
||||||
font_cache: FontCache::default(),
|
font_cache: FontCache::default(),
|
||||||
application_io: Some(application_io_for_api),
|
application_io: Some(application_io_for_api),
|
||||||
node_graph_message_sender: Box::new(UpdateLogger {}),
|
node_graph_message_sender: Box::new(UpdateLogger {}),
|
||||||
|
|
@ -247,7 +247,7 @@ fn fix_nodes(network: &mut NodeNetwork) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<ProtoNetwork, Box<dyn Error>> {
|
fn compile_graph(document_string: String, editor_api: Arc<PlatformEditorApi>) -> Result<ProtoNetwork, Box<dyn Error>> {
|
||||||
let mut network = load_network(&document_string);
|
let mut network = load_network(&document_string);
|
||||||
fix_nodes(&mut network);
|
fix_nodes(&mut network);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ authors.workspace = true
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gpu = ["graphene-std/gpu", "graphene-std/wgpu"]
|
gpu = ["graphene-std/gpu", "graphene-std/wgpu"]
|
||||||
|
wasm = ["graphene-std/wasm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
|
use graph_craft::application_io::PlatformEditorApi;
|
||||||
use graph_craft::document::DocumentNode;
|
use graph_craft::document::DocumentNode;
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
||||||
use graphene_std::any::DynAnyNode;
|
use graphene_std::any::DynAnyNode;
|
||||||
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
|
use graphene_std::application_io::ImageTexture;
|
||||||
use graphene_std::brush::brush_cache::BrushCache;
|
use graphene_std::brush::brush_cache::BrushCache;
|
||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::gradient::GradientStops;
|
use graphene_std::gradient::GradientStops;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
use graphene_std::platform_application_io::canvas_utils::CanvasHandle;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use graphene_std::raster::GPU;
|
use graphene_std::raster::GPU;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
|
|
@ -18,17 +21,11 @@ use graphene_std::table::Table;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::wasm_application_io::WasmEditorApi;
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
use graphene_std::wasm_application_io::WasmSurfaceHandle;
|
|
||||||
use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
|
use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
|
||||||
use node_registry_macros::{async_node, convert_node, into_node};
|
use node_registry_macros::{async_node, convert_node, into_node};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
use wgpu_executor::WgpuExecutor;
|
use wgpu_executor::WgpuExecutor;
|
||||||
use wgpu_executor::{WgpuSurface, WindowHandle};
|
|
||||||
|
|
||||||
// TODO: turn into hashmap
|
// TODO: turn into hashmap
|
||||||
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
|
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
|
||||||
|
|
@ -47,7 +44,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
convert_node!(from: Table<Raster<GPU>>, to: Table<Graphic>),
|
convert_node!(from: Table<Raster<GPU>>, to: Table<Graphic>),
|
||||||
// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),
|
// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
|
into_node!(from: &PlatformEditorApi, to: &WgpuExecutor),
|
||||||
convert_node!(from: DVec2, to: DVec2),
|
convert_node!(from: DVec2, to: DVec2),
|
||||||
convert_node!(from: String, to: String),
|
convert_node!(from: String, to: String),
|
||||||
convert_node!(from: bool, to: String),
|
convert_node!(from: bool, to: String),
|
||||||
|
|
@ -138,14 +135,11 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
||||||
// Context nullification
|
// Context nullification
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi, Context => graphene_std::ContextFeatures]),
|
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi, Context => graphene_std::ContextFeatures]),
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>, Context => graphene_std::ContextFeatures]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]),
|
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]),
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),
|
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WgpuSurface, Context => graphene_std::ContextFeatures]),
|
#[cfg(target_family = "wasm")]
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<WgpuSurface>, Context => graphene_std::ContextFeatures]),
|
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]),
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WindowHandle, Context => graphene_std::ContextFeatures]),
|
|
||||||
// ==========
|
// ==========
|
||||||
// MEMO NODES
|
// MEMO NODES
|
||||||
// ==========
|
// ==========
|
||||||
|
|
@ -163,11 +157,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f32>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f32>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<String>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<String>]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(target_family = "wasm")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => SurfaceFrame]),
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]),
|
||||||
|
|
@ -177,9 +168,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]),
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
|
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
use graph_craft::ProtoNodeIdentifier;
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
|
use graph_craft::application_io::PlatformEditorApi;
|
||||||
use graph_craft::concrete;
|
use graph_craft::concrete;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||||
use graph_craft::generic;
|
use graph_craft::generic;
|
||||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
|
||||||
use graphene_std::Context;
|
use graphene_std::Context;
|
||||||
use graphene_std::ContextFeatures;
|
use graphene_std::ContextFeatures;
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu_executor::WgpuExecutor;
|
use wgpu_executor::WgpuExecutor;
|
||||||
|
|
||||||
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
|
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformEditorApi>) -> NodeNetwork {
|
||||||
network.generate_node_paths(&[]);
|
network.generate_node_paths(&[]);
|
||||||
|
|
||||||
let inner_network = DocumentNode {
|
let inner_network = DocumentNode {
|
||||||
|
|
@ -102,7 +102,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let mut scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))];
|
let mut scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(&PlatformEditorApi)))];
|
||||||
|
|
||||||
if cfg!(feature = "gpu") {
|
if cfg!(feature = "gpu") {
|
||||||
nodes.push(DocumentNode {
|
nodes.push(DocumentNode {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::DVec2;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
@ -11,145 +11,36 @@ use std::time::Duration;
|
||||||
use text_nodes::FontCache;
|
use text_nodes::FontCache;
|
||||||
use vector_types::vector::style::RenderMode;
|
use vector_types::vector::style::RenderMode;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct SurfaceId(pub u64);
|
|
||||||
|
|
||||||
impl std::fmt::Display for SurfaceId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}", self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct SurfaceFrame {
|
|
||||||
pub surface_id: SurfaceId,
|
|
||||||
/// Logical resolution in CSS pixels (used for foreignObject dimensions)
|
|
||||||
pub resolution: DVec2,
|
|
||||||
pub transform: DAffine2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for SurfaceFrame {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.surface_id.hash(state);
|
|
||||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl StaticType for SurfaceFrame {
|
|
||||||
type Static = SurfaceFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Size {
|
|
||||||
fn size(&self) -> UVec2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
impl Size for web_sys::HtmlCanvasElement {
|
|
||||||
fn size(&self) -> UVec2 {
|
|
||||||
UVec2::new(self.width(), self.height())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, DynAny)]
|
|
||||||
pub struct ImageTexture {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
pub texture: Arc<wgpu::Texture>,
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
pub texture: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> serde::Deserialize<'a> for ImageTexture {
|
|
||||||
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'a>,
|
|
||||||
{
|
|
||||||
unimplemented!("attempted to serialize a texture")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for ImageTexture {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.texture.hash(state);
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
fn hash<H: Hasher>(&self, _state: &mut H) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for ImageTexture {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
{
|
|
||||||
self.texture == other.texture
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
{
|
|
||||||
self.texture == other.texture
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
impl Size for ImageTexture {
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]
|
||||||
fn size(&self) -> UVec2 {
|
pub struct ImageTexture(Arc<wgpu::Texture>);
|
||||||
UVec2::new(self.texture.width(), self.texture.height())
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl AsRef<wgpu::Texture> for ImageTexture {
|
||||||
|
fn as_ref(&self) -> &wgpu::Texture {
|
||||||
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
impl<S: Size> From<SurfaceHandleFrame<S>> for SurfaceFrame {
|
impl From<wgpu::Texture> for ImageTexture {
|
||||||
fn from(x: SurfaceHandleFrame<S>) -> Self {
|
fn from(texture: wgpu::Texture) -> Self {
|
||||||
let size = x.surface_handle.surface.size();
|
Self(Arc::new(texture))
|
||||||
Self {
|
|
||||||
surface_id: x.surface_handle.window_id,
|
|
||||||
transform: x.transform,
|
|
||||||
resolution: size.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
impl From<Arc<wgpu::Texture>> for ImageTexture {
|
||||||
pub struct SurfaceHandle<Surface> {
|
fn from(texture: Arc<wgpu::Texture>) -> Self {
|
||||||
pub window_id: SurfaceId,
|
Self(texture)
|
||||||
pub surface: Surface,
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[cfg(target_family = "wasm")]
|
|
||||||
// unsafe impl<T: dyn_any::WasmNotSend> Send for SurfaceHandle<T> {}
|
|
||||||
// #[cfg(target_family = "wasm")]
|
|
||||||
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
|
|
||||||
|
|
||||||
impl<S: Size> Size for SurfaceHandle<S> {
|
|
||||||
fn size(&self) -> UVec2 {
|
|
||||||
self.surface.size()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
|
impl From<ImageTexture> for Arc<wgpu::Texture> {
|
||||||
type Static = SurfaceHandle<T>;
|
fn from(image_texture: ImageTexture) -> Self {
|
||||||
}
|
image_texture.0
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct SurfaceHandleFrame<Surface> {
|
|
||||||
pub surface_handle: Arc<SurfaceHandle<Surface>>,
|
|
||||||
pub transform: DAffine2,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {
|
|
||||||
type Static = SurfaceHandleFrame<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub type WasmSurfaceHandle = SurfaceHandle<web_sys::HtmlCanvasElement>;
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<web_sys::HtmlCanvasElement>;
|
|
||||||
|
|
||||||
// TODO: think about how to automatically clean up memory
|
|
||||||
/*
|
|
||||||
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.application_io.destroy_surface(self.surface_id)
|
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]
|
||||||
|
pub struct ImageTexture;
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>;
|
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>;
|
||||||
|
|
@ -157,11 +48,7 @@ pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, Applicat
|
||||||
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>;
|
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>;
|
||||||
|
|
||||||
pub trait ApplicationIo {
|
pub trait ApplicationIo {
|
||||||
type Surface;
|
|
||||||
type Executor;
|
type Executor;
|
||||||
fn window(&self) -> Option<SurfaceHandle<Self::Surface>>;
|
|
||||||
fn create_window(&self) -> SurfaceHandle<Self::Surface>;
|
|
||||||
fn destroy_window(&self, surface_id: SurfaceId);
|
|
||||||
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
fn gpu_executor(&self) -> Option<&Self::Executor> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -169,21 +56,8 @@ pub trait ApplicationIo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ApplicationIo> ApplicationIo for &T {
|
impl<T: ApplicationIo> ApplicationIo for &T {
|
||||||
type Surface = T::Surface;
|
|
||||||
type Executor = T::Executor;
|
type Executor = T::Executor;
|
||||||
|
|
||||||
fn window(&self) -> Option<SurfaceHandle<Self::Surface>> {
|
|
||||||
(**self).window()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_window(&self) -> SurfaceHandle<T::Surface> {
|
|
||||||
(**self).create_window()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn destroy_window(&self, surface_id: SurfaceId) {
|
|
||||||
(**self).destroy_window(surface_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gpu_executor(&self) -> Option<&T::Executor> {
|
fn gpu_executor(&self) -> Option<&T::Executor> {
|
||||||
(**self).gpu_executor()
|
(**self).gpu_executor()
|
||||||
}
|
}
|
||||||
|
|
@ -260,12 +134,12 @@ impl GetEditorPreferences for DummyPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorApi<Io> {
|
pub struct EditorApi<Io> {
|
||||||
/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].
|
/// Font data (for rendering text) made available to the graph through the `PlatformEditorApi`.
|
||||||
pub font_cache: FontCache,
|
pub font_cache: FontCache,
|
||||||
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
|
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
|
||||||
pub application_io: Option<Arc<Io>>,
|
pub application_io: Option<Arc<Io>>,
|
||||||
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
|
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
|
||||||
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
|
/// Editor preferences made available to the graph through the `PlatformEditorApi`.
|
||||||
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
|
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "graphene-canvas-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
description = "graphene canvas utilities"
|
||||||
|
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
wgpu = ["dep:wgpu", "dep:wgpu-executor"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Local dependencies
|
||||||
|
dyn-any = { workspace = true }
|
||||||
|
core-types = { workspace = true }
|
||||||
|
vector-types = { workspace = true }
|
||||||
|
text-nodes = { workspace = true }
|
||||||
|
graphene-application-io = { workspace = true }
|
||||||
|
|
||||||
|
# Workspace dependencies
|
||||||
|
web-sys = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
|
||||||
|
# Optional workspace dependencies
|
||||||
|
wgpu = { workspace = true, optional = true }
|
||||||
|
wgpu-executor = { workspace = true, optional = true }
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
//! A collection of utilities for working with HTML canvases.
|
||||||
|
//! This library is designed to be used in a WebAssembly context.
|
||||||
|
//! It doesn't expose any functionality when compiled for non-WebAssembly targets
|
||||||
|
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
mod wasm;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
pub use wasm::*;
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
use dyn_any::DynAny;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use graphene_application_io::ImageTexture;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use web_sys::js_sys::{Object, Reflect};
|
||||||
|
use web_sys::wasm_bindgen::{JsCast, JsValue};
|
||||||
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, window};
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use wgpu_executor::WgpuExecutor;
|
||||||
|
|
||||||
|
const CANVASES_OBJECT_KEY: &str = "imageCanvases";
|
||||||
|
|
||||||
|
pub type CanvasId = u64;
|
||||||
|
|
||||||
|
static CANVAS_IDS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub trait Canvas {
|
||||||
|
fn id(&mut self) -> CanvasId;
|
||||||
|
fn context(&mut self) -> CanvasRenderingContext2d;
|
||||||
|
fn set_resolution(&mut self, resolution: glam::UVec2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub trait CanvasSurface: Canvas {
|
||||||
|
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, DynAny)]
|
||||||
|
pub struct CanvasHandle(Option<Arc<CanvasImpl>>);
|
||||||
|
impl CanvasHandle {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(None)
|
||||||
|
}
|
||||||
|
fn get(&mut self) -> &CanvasImpl {
|
||||||
|
if self.0.is_none() {
|
||||||
|
self.0 = Some(Arc::new(CanvasImpl::new()));
|
||||||
|
}
|
||||||
|
self.0.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Canvas for CanvasHandle {
|
||||||
|
fn id(&mut self) -> CanvasId {
|
||||||
|
self.get().canvas_id
|
||||||
|
}
|
||||||
|
fn context(&mut self) -> CanvasRenderingContext2d {
|
||||||
|
self.get().context()
|
||||||
|
}
|
||||||
|
fn set_resolution(&mut self, resolution: glam::UVec2) {
|
||||||
|
self.get().set_resolution(resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub struct CanvasSurfaceHandle(CanvasHandle, Option<Arc<wgpu::Surface<'static>>>);
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl CanvasSurfaceHandle {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(CanvasHandle::new(), None)
|
||||||
|
}
|
||||||
|
fn surface(&mut self, executor: &WgpuExecutor) -> &wgpu::Surface<'_> {
|
||||||
|
if self.1.is_none() {
|
||||||
|
let canvas = self.0.get().canvas.clone();
|
||||||
|
let surface = executor
|
||||||
|
.context
|
||||||
|
.instance
|
||||||
|
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
|
||||||
|
.expect("Failed to create surface from canvas");
|
||||||
|
self.1 = Some(Arc::new(surface));
|
||||||
|
}
|
||||||
|
self.1.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl Canvas for CanvasSurfaceHandle {
|
||||||
|
fn id(&mut self) -> CanvasId {
|
||||||
|
self.0.id()
|
||||||
|
}
|
||||||
|
fn context(&mut self) -> CanvasRenderingContext2d {
|
||||||
|
self.0.context()
|
||||||
|
}
|
||||||
|
fn set_resolution(&mut self, resolution: glam::UVec2) {
|
||||||
|
self.0.set_resolution(resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl CanvasSurface for CanvasSurfaceHandle {
|
||||||
|
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) {
|
||||||
|
let source_texture: &wgpu::Texture = image_texture.as_ref();
|
||||||
|
|
||||||
|
let surface = self.surface(executor);
|
||||||
|
|
||||||
|
// Blit the texture to the surface
|
||||||
|
let mut encoder = executor.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("Texture to Surface Blit"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let size = source_texture.size();
|
||||||
|
|
||||||
|
// Configure the surface at physical resolution (for HiDPI displays)
|
||||||
|
let surface_caps = surface.get_capabilities(&executor.context.adapter);
|
||||||
|
surface.configure(
|
||||||
|
&executor.context.device,
|
||||||
|
&wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: surface_caps.present_modes[0],
|
||||||
|
alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let surface_texture = surface.get_current_texture().expect("Failed to get surface texture");
|
||||||
|
|
||||||
|
encoder.copy_texture_to_texture(
|
||||||
|
wgpu::TexelCopyTextureInfoBase {
|
||||||
|
texture: source_texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Default::default(),
|
||||||
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
wgpu::TexelCopyTextureInfoBase {
|
||||||
|
texture: &surface_texture.texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Default::default(),
|
||||||
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
source_texture.size(),
|
||||||
|
);
|
||||||
|
|
||||||
|
executor.context.queue.submit([encoder.finish()]);
|
||||||
|
surface_texture.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wgpu surface backed by an HTML canvas element.
|
||||||
|
/// Holds a reference to the canvas to prevent garbage collection.
|
||||||
|
pub struct CanvasImpl {
|
||||||
|
canvas_id: u64,
|
||||||
|
canvas: HtmlCanvasElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasImpl {
|
||||||
|
fn new() -> Self {
|
||||||
|
let document = window().expect("should have a window in this context").document().expect("window should have a document");
|
||||||
|
|
||||||
|
let canvas: HtmlCanvasElement = document.create_element("canvas").unwrap().dyn_into::<HtmlCanvasElement>().unwrap();
|
||||||
|
let canvas_id = CANVAS_IDS.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Store the canvas in the global scope so it doesn't get garbage collected
|
||||||
|
let window = window().expect("should have a window in this context");
|
||||||
|
let window_obj = Object::from(window);
|
||||||
|
|
||||||
|
let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY);
|
||||||
|
|
||||||
|
let mut canvases = Reflect::get(&window_obj, &image_canvases_key);
|
||||||
|
if canvases.is_err() || canvases.as_ref().map_or(false, |v| v.is_undefined() || v.is_null()) {
|
||||||
|
Reflect::set(&window_obj.clone(), &image_canvases_key, &Object::new()).unwrap();
|
||||||
|
canvases = Reflect::get(&window_obj, &image_canvases_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert key and value to JsValue
|
||||||
|
let js_key = JsValue::from_str(canvas_id.to_string().as_str());
|
||||||
|
let js_value = JsValue::from(canvas.clone());
|
||||||
|
|
||||||
|
let canvases = Object::from(canvases.unwrap());
|
||||||
|
|
||||||
|
// Use Reflect API to set property
|
||||||
|
Reflect::set(&canvases, &js_key, &js_value).unwrap();
|
||||||
|
|
||||||
|
Self { canvas_id, canvas }
|
||||||
|
}
|
||||||
|
fn context(&self) -> CanvasRenderingContext2d {
|
||||||
|
self.canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.expect("Failed to get 2D context from canvas")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<CanvasRenderingContext2d>()
|
||||||
|
.expect("Failed to cast context to CanvasRenderingContext2d")
|
||||||
|
}
|
||||||
|
fn set_resolution(&self, resolution: glam::UVec2) {
|
||||||
|
self.canvas.set_width(resolution.x);
|
||||||
|
self.canvas.set_height(resolution.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CanvasImpl {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let canvas_id = self.canvas_id;
|
||||||
|
let window = window().expect("should have a window in this context");
|
||||||
|
let window_obj = Object::from(window);
|
||||||
|
|
||||||
|
let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY);
|
||||||
|
|
||||||
|
if let Ok(canvases) = Reflect::get(&window_obj, &image_canvases_key) {
|
||||||
|
let canvases = Object::from(canvases);
|
||||||
|
let js_key = JsValue::from_str(canvas_id.to_string().as_str());
|
||||||
|
Reflect::delete_property(&canvases, &js_key).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: WASM is single-threaded, so Send/Sync are safe
|
||||||
|
unsafe impl Send for CanvasImpl {}
|
||||||
|
unsafe impl Sync for CanvasImpl {}
|
||||||
|
|
@ -20,6 +20,5 @@ anyhow = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
winit = { workspace = true }
|
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,10 @@ use crate::resample::Resampler;
|
||||||
use crate::shader_runtime::ShaderRuntime;
|
use crate::shader_runtime::ShaderRuntime;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use dyn_any::StaticType;
|
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
use glam::UVec2;
|
use glam::UVec2;
|
||||||
use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId};
|
use graphene_application_io::{ApplicationIo, EditorApi};
|
||||||
use std::sync::Arc;
|
|
||||||
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
|
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
|
||||||
use wgpu::util::TextureBlitter;
|
|
||||||
use wgpu::{Origin3d, TextureAspect};
|
use wgpu::{Origin3d, TextureAspect};
|
||||||
|
|
||||||
pub use context::Context as WgpuContext;
|
pub use context::Context as WgpuContext;
|
||||||
|
|
@ -42,15 +39,6 @@ impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type WgpuSurface = Arc<SurfaceHandle<Surface>>;
|
|
||||||
pub type WgpuWindow = Arc<SurfaceHandle<WindowHandle>>;
|
|
||||||
|
|
||||||
pub struct Surface {
|
|
||||||
pub inner: wgpu::Surface<'static>,
|
|
||||||
pub target_texture: Mutex<Option<TargetTexture>>,
|
|
||||||
pub blitter: TextureBlitter,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TargetTexture {
|
pub struct TargetTexture {
|
||||||
texture: wgpu::Texture,
|
texture: wgpu::Texture,
|
||||||
|
|
@ -103,15 +91,6 @@ impl TargetTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
pub type Window = web_sys::HtmlCanvasElement;
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
pub type Window = Arc<dyn winit::window::Window>;
|
|
||||||
|
|
||||||
unsafe impl StaticType for Surface {
|
|
||||||
type Static = Surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
||||||
|
|
||||||
impl WgpuExecutor {
|
impl WgpuExecutor {
|
||||||
|
|
@ -160,29 +139,6 @@ impl WgpuExecutor {
|
||||||
pub fn resample_texture(&self, source: &wgpu::Texture, target_size: UVec2, transform: &glam::DAffine2) -> wgpu::Texture {
|
pub fn resample_texture(&self, source: &wgpu::Texture, target_size: UVec2, transform: &glam::DAffine2) -> wgpu::Texture {
|
||||||
self.resampler.resample(&self.context, source, target_size, transform)
|
self.resampler.resample(&self.context, source, target_size, transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
|
|
||||||
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;
|
|
||||||
self.create_surface_inner(surface, canvas.window_id)
|
|
||||||
}
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
pub fn create_surface(&self, window: SurfaceHandle<Window>) -> Result<SurfaceHandle<Surface>> {
|
|
||||||
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?;
|
|
||||||
self.create_surface_inner(surface, window.window_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_surface_inner(&self, surface: wgpu::Surface<'static>, window_id: SurfaceId) -> Result<SurfaceHandle<Surface>> {
|
|
||||||
let blitter = TextureBlitter::new(&self.context.device, VELLO_SURFACE_FORMAT);
|
|
||||||
Ok(SurfaceHandle {
|
|
||||||
window_id,
|
|
||||||
surface: Surface {
|
|
||||||
inner: surface,
|
|
||||||
target_texture: Mutex::new(None),
|
|
||||||
blitter,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WgpuExecutor {
|
impl WgpuExecutor {
|
||||||
|
|
@ -213,5 +169,3 @@ impl WgpuExecutor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
|
||||||
|
|
|
||||||
|
|
@ -1271,7 +1271,7 @@ mod tests {
|
||||||
fn test_async_node() {
|
fn test_async_node() {
|
||||||
let attr = quote!(category("IO"));
|
let attr = quote!(category("IO"));
|
||||||
let input = quote!(
|
let input = quote!(
|
||||||
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> Table<Raster<CPU>> {
|
async fn load_image(api: &PlatformEditorApi, #[expose] path: String) -> Table<Raster<CPU>> {
|
||||||
// Implementation details...
|
// Implementation details...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -1296,7 +1296,7 @@ mod tests {
|
||||||
where_clause: None,
|
where_clause: None,
|
||||||
input: Input {
|
input: Input {
|
||||||
pat_ident: pat_ident("api"),
|
pat_ident: pat_ident("api"),
|
||||||
ty: parse_quote!(&WasmEditorApi),
|
ty: parse_quote!(&PlatformEditorApi),
|
||||||
implementations: Punctuated::new(),
|
implementations: Punctuated::new(),
|
||||||
context_features: vec![],
|
context_features: vec![],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ authors = ["Graphite Authors <contact@graphite.art>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wasm", "wgpu"]
|
default = ["wgpu"]
|
||||||
gpu = []
|
gpu = []
|
||||||
wgpu = ["gpu", "graph-craft/wgpu", "graphene-application-io/wgpu"]
|
wgpu = ["gpu", "graph-craft/wgpu", "graphene-application-io/wgpu", "graphene-canvas-utils?/wgpu"]
|
||||||
wasm = [
|
wasm = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
|
@ -24,6 +24,7 @@ wasm = [
|
||||||
"vector-nodes/wasm",
|
"vector-nodes/wasm",
|
||||||
"graphene-core/wasm",
|
"graphene-core/wasm",
|
||||||
"graph-craft/wasm",
|
"graph-craft/wasm",
|
||||||
|
"dep:graphene-canvas-utils"
|
||||||
]
|
]
|
||||||
image-compare = []
|
image-compare = []
|
||||||
vello = ["gpu"]
|
vello = ["gpu"]
|
||||||
|
|
@ -61,6 +62,9 @@ image = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
|
|
||||||
|
# Optional local dependencies
|
||||||
|
graphene-canvas-utils = { workspace = true, optional = true }
|
||||||
|
|
||||||
# Optional workspace dependencies
|
# Optional workspace dependencies
|
||||||
wasm-bindgen = { workspace = true, optional = true }
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
wasm-bindgen-futures = { workspace = true, optional = true }
|
wasm-bindgen-futures = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
pub mod any;
|
pub mod any;
|
||||||
pub mod pixel_preview;
|
pub mod pixel_preview;
|
||||||
|
pub mod platform_application_io;
|
||||||
pub mod render_cache;
|
pub mod render_cache;
|
||||||
pub mod render_node;
|
pub mod render_node;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub mod wasm_application_io;
|
|
||||||
pub use blending_nodes;
|
pub use blending_nodes;
|
||||||
pub use brush_nodes as brush;
|
pub use brush_nodes as brush;
|
||||||
pub use core_types::*;
|
pub use core_types::*;
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ use crate::render_node::RenderOutputType;
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
|
use graph_craft::application_io::PlatformEditorApi;
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
use graphene_application_io::ApplicationIo;
|
||||||
use graphene_application_io::{ApplicationIo, ImageTexture};
|
|
||||||
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
|
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
|
||||||
use vector_types::vector::style::RenderMode;
|
use vector_types::vector::style::RenderMode;
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn pixel_preview<'a: 'n>(
|
pub async fn pixel_preview<'a: 'n>(
|
||||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
||||||
editor_api: &'a WasmEditorApi,
|
editor_api: &'a PlatformEditorApi,
|
||||||
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
|
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
|
||||||
) -> RenderOutput {
|
) -> RenderOutput {
|
||||||
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else {
|
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else {
|
||||||
|
|
@ -59,9 +59,9 @@ pub async fn pixel_preview<'a: 'n>(
|
||||||
let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution);
|
let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution);
|
||||||
|
|
||||||
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
||||||
let resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform);
|
let resampled = exec.resample_texture(source_texture.as_ref(), physical_resolution, &transform);
|
||||||
|
|
||||||
result.data = RenderOutputType::Texture(ImageTexture { texture: resampled.into() });
|
result.data = RenderOutputType::Texture(resampled.into());
|
||||||
|
|
||||||
result
|
result
|
||||||
.metadata
|
.metadata
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
|
use canvas_utils::{Canvas, CanvasHandle};
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
use core_types::WasmNotSend;
|
use core_types::WasmNotSend;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use core_types::math::bbox::Bbox;
|
use core_types::math::bbox::Bbox;
|
||||||
|
|
@ -8,10 +10,12 @@ use core_types::table::Table;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::{Color, Ctx};
|
use core_types::{Color, Ctx};
|
||||||
|
pub use graph_craft::application_io::*;
|
||||||
pub use graph_craft::document::value::RenderOutputType;
|
pub use graph_craft::document::value::RenderOutputType;
|
||||||
pub use graph_craft::wasm_application_io::*;
|
|
||||||
use graphene_application_io::ApplicationIo;
|
use graphene_application_io::ApplicationIo;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
|
pub use graphene_canvas_utils as canvas_utils;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
use graphic_types::Graphic;
|
use graphic_types::Graphic;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
|
|
@ -22,17 +26,6 @@ use graphic_types::vector_types::gradient::GradientStops;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender};
|
use rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
|
||||||
|
|
||||||
/// Allocates GPU memory and a rendering context for vector-to-raster conversion.
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
#[node_macro::node(category(""))]
|
|
||||||
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
|
||||||
Arc::new(editor.application_io.as_ref().unwrap().create_window())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_headers(headers: &str) -> reqwest::header::HeaderMap {
|
fn parse_headers(headers: &str) -> reqwest::header::HeaderMap {
|
||||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||||
|
|
@ -132,7 +125,7 @@ fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
||||||
|
|
||||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||||
#[node_macro::node(category("Web Request"))]
|
#[node_macro::node(category("Web Request"))]
|
||||||
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor_resources: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor_resources: &'a PlatformEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||||
let Some(api) = editor_resources.application_io.as_ref() else {
|
let Some(api) = editor_resources.application_io.as_ref() else {
|
||||||
return Arc::from(include_bytes!("../../../graph-craft/src/null.png").to_vec());
|
return Arc::from(include_bytes!("../../../graph-craft/src/null.png").to_vec());
|
||||||
};
|
};
|
||||||
|
|
@ -168,6 +161,12 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {
|
||||||
Table::new_from_element(Raster::new_cpu(image))
|
Table::new_from_element(Raster::new_cpu(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
#[node_macro::node(category(""))]
|
||||||
|
async fn create_canvas(_: impl Ctx) -> CanvasHandle {
|
||||||
|
CanvasHandle::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders a view of the input graphic within an area defined by the *Footprint*.
|
/// Renders a view of the input graphic within an area defined by the *Footprint*.
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
|
|
@ -182,7 +181,7 @@ async fn rasterize<T: WasmNotSend + 'n>(
|
||||||
)]
|
)]
|
||||||
mut data: Table<T>,
|
mut data: Table<T>,
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
surface_handle: Arc<graphene_application_io::SurfaceHandle<HtmlCanvasElement>>,
|
mut canvas: CanvasHandle,
|
||||||
) -> Table<Raster<CPU>>
|
) -> Table<Raster<CPU>>
|
||||||
where
|
where
|
||||||
Table<T>: Render,
|
Table<T>: Render,
|
||||||
|
|
@ -211,11 +210,8 @@ where
|
||||||
render.format_svg(glam::DVec2::ZERO, size);
|
render.format_svg(glam::DVec2::ZERO, size);
|
||||||
let svg_string = render.svg.to_svg_string();
|
let svg_string = render.svg.to_svg_string();
|
||||||
|
|
||||||
let canvas = &surface_handle.surface;
|
canvas.set_resolution(resolution);
|
||||||
canvas.set_width(resolution.x);
|
let context = canvas.context();
|
||||||
canvas.set_height(resolution.y);
|
|
||||||
|
|
||||||
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
|
|
||||||
|
|
||||||
let preamble = "data:image/svg+xml;base64,";
|
let preamble = "data:image/svg+xml;base64,";
|
||||||
let mut base64_string = String::with_capacity(preamble.len() + svg_string.len() * 4);
|
let mut base64_string = String::with_capacity(preamble.len() + svg_string.len() * 4);
|
||||||
|
|
@ -4,9 +4,9 @@ use core_types::math::bbox::AxisAlignedBbox;
|
||||||
use core_types::transform::{Footprint, RenderQuality, Transform};
|
use core_types::transform::{Footprint, RenderQuality, Transform};
|
||||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
|
use graph_craft::application_io::PlatformEditorApi;
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
use graphene_application_io::ApplicationIo;
|
||||||
use graphene_application_io::{ApplicationIo, ImageTexture};
|
|
||||||
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
|
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
@ -374,7 +374,7 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut Ha
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn render_output_cache<'a: 'n>(
|
pub async fn render_output_cache<'a: 'n>(
|
||||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
|
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
|
||||||
editor_api: &'a WasmEditorApi,
|
editor_api: &'a PlatformEditorApi,
|
||||||
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
|
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
|
||||||
#[data] tile_cache: TileCache,
|
#[data] tile_cache: TileCache,
|
||||||
) -> RenderOutput {
|
) -> RenderOutput {
|
||||||
|
|
@ -460,7 +460,7 @@ pub async fn render_output_cache<'a: 'n>(
|
||||||
let combined_metadata = composite_cached_regions(&all_regions, output_texture.as_ref(), &device_origin_offset, &footprint.transform, exec);
|
let combined_metadata = composite_cached_regions(&all_regions, output_texture.as_ref(), &device_origin_offset, &footprint.transform, exec);
|
||||||
|
|
||||||
RenderOutput {
|
RenderOutput {
|
||||||
data: RenderOutputType::Texture(ImageTexture { texture: output_texture }),
|
data: RenderOutputType::Texture(output_texture.into()),
|
||||||
metadata: combined_metadata,
|
metadata: combined_metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +506,7 @@ where
|
||||||
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
|
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
|
||||||
|
|
||||||
CachedRegion {
|
CachedRegion {
|
||||||
texture: rendered_texture.texture.as_ref().clone(),
|
texture: rendered_texture.as_ref().clone(),
|
||||||
texture_size: region_pixel_size,
|
texture_size: region_pixel_size,
|
||||||
tiles: region.tiles.clone(),
|
tiles: region.tiles.clone(),
|
||||||
metadata: result.metadata,
|
metadata: result.metadata,
|
||||||
|
|
@ -558,7 +558,7 @@ fn composite_cached_regions(
|
||||||
aspect: wgpu::TextureAspect::All,
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: &output_texture,
|
texture: output_texture,
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: wgpu::Origin3d { x: dst_x, y: dst_y, z: 0 },
|
origin: wgpu::Origin3d { x: dst_x, y: dst_y, z: 0 },
|
||||||
aspect: wgpu::TextureAspect::All,
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use core_types::table::Table;
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
|
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
|
||||||
use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};
|
use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};
|
||||||
|
pub use graph_craft::application_io::*;
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
pub use graph_craft::document::value::RenderOutputType;
|
pub use graph_craft::document::value::RenderOutputType;
|
||||||
pub use graph_craft::wasm_application_io::*;
|
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
|
||||||
use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig};
|
|
||||||
use graphic_types::raster_types::Image;
|
use graphic_types::raster_types::Image;
|
||||||
use graphic_types::raster_types::{CPU, Raster};
|
use graphic_types::raster_types::{CPU, Raster};
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Artboard, Graphic, Vector};
|
||||||
|
|
@ -124,7 +124,7 @@ async fn create_context<'a: 'n>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a WasmEditorApi, data: RenderIntermediate) -> RenderOutput {
|
async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderIntermediate) -> RenderOutput {
|
||||||
let footprint = ctx.footprint();
|
let footprint = ctx.footprint();
|
||||||
let render_params = ctx
|
let render_params = ctx
|
||||||
.vararg(0)
|
.vararg(0)
|
||||||
|
|
@ -202,7 +202,7 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
|
||||||
.expect("Failed to render Vello scene"),
|
.expect("Failed to render Vello scene"),
|
||||||
);
|
);
|
||||||
|
|
||||||
RenderOutputType::Texture(ImageTexture { texture })
|
RenderOutputType::Texture(texture.into())
|
||||||
}
|
}
|
||||||
_ => unreachable!("Render node did not receive its requested data type"),
|
_ => unreachable!("Render node did not receive its requested data type"),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use core_types::{Ctx, table::Table};
|
use core_types::{Ctx, table::Table};
|
||||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
use graph_craft::application_io::PlatformEditorApi;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
pub use text_nodes::*;
|
pub use text_nodes::*;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ fn text<'i: 'n>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The Graphite editor's source for global font resources.
|
/// The Graphite editor's source for global font resources.
|
||||||
#[scope("editor-api")]
|
#[scope("editor-api")]
|
||||||
editor_resources: &'i WasmEditorApi,
|
editor_resources: &'i PlatformEditorApi,
|
||||||
/// The text content to be drawn.
|
/// The text content to be drawn.
|
||||||
#[widget(ParsedWidgetOverride::Custom = "text_area")]
|
#[widget(ParsedWidgetOverride::Custom = "text_area")]
|
||||||
#[default("Lorem ipsum")]
|
#[default("Lorem ipsum")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue