mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Update Tauri to v2 and execute only the node graph in native (#2362)
* Migrate tauri app to v2 * Move flake files to sub directory * Remove unused plugins * Backport some of the tauri code * Implement async node graph execution Only move node runtime to native code * Always use gpu feature for tauri * Fix serialization * Add logging filters * Enable native window rendering with vello * Cleanup * Remove unused editor instance * Remove changes from vite config * Remove warnings * Remove unused files * Fix most tests * Cleanup * Apply frontend lint * Readd flake.nix * Fix tests using --all-features * Code review * Enable all backends * Fix monitor node downcast types * Change debug log to a warning * Disable shader passthrough * Cleanup unused imports * Remove warning * Update project setup instructions --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -30,61 +30,6 @@ fn return_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum NodeInputVersions {
|
||||
OldNodeInput(OldNodeInput),
|
||||
NodeInput(NodeInput),
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum OldNodeInput {
|
||||
/// A reference to another node in the same network from which this node can receive its input.
|
||||
Node { node_id: NodeId, output_index: usize, lambda: bool },
|
||||
|
||||
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
|
||||
Value { tagged_value: TaggedValue, exposed: bool },
|
||||
|
||||
/// Input that is provided by the parent network to this document node, instead of from a hardcoded value or another node within the same network.
|
||||
Network(Type),
|
||||
|
||||
/// A Rust source code string. Allows us to insert literal Rust code. Only used for GPU compilation.
|
||||
/// We can use this whenever we spin up Rustc. Sort of like inline assembly, but because our language is Rust, it acts as inline Rust.
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
#[cfg(feature = "serde")]
|
||||
fn deserialize_inputs<'de, D>(deserializer: D) -> Result<Vec<NodeInput>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
let input_versions = Vec::<NodeInputVersions>::deserialize(deserializer)?;
|
||||
|
||||
let inputs = input_versions
|
||||
.into_iter()
|
||||
.map(|old_input| {
|
||||
let old_input = match old_input {
|
||||
NodeInputVersions::OldNodeInput(old_input) => old_input,
|
||||
NodeInputVersions::NodeInput(node_input) => return node_input,
|
||||
};
|
||||
match old_input {
|
||||
OldNodeInput::Node { node_id, output_index, .. } => NodeInput::node(node_id, output_index),
|
||||
OldNodeInput::Value { tagged_value, exposed } => NodeInput::value(tagged_value, exposed),
|
||||
OldNodeInput::Network(network_type) => NodeInput::network(network_type, 0),
|
||||
OldNodeInput::Inline(inline) => NodeInput::Inline(inline),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(inputs)
|
||||
}
|
||||
|
||||
/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].
|
||||
/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.
|
||||
/// But we will want to change it in the future so it merely references its definition.
|
||||
@@ -99,7 +44,7 @@ pub struct DocumentNode {
|
||||
/// In the root network, it is resolved when evaluating the borrow tree.
|
||||
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input)
|
||||
/// by using network.update_click_target(node_id).
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_inputs"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(alias = "outputs"))]
|
||||
pub inputs: Vec<NodeInput>,
|
||||
/// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs.
|
||||
/// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph.
|
||||
@@ -635,7 +580,7 @@ pub struct OldDocumentNode {
|
||||
///
|
||||
/// In the root network, it is resolved when evaluating the borrow tree.
|
||||
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_inputs"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(alias = "outputs"))]
|
||||
pub inputs: Vec<NodeInput>,
|
||||
pub manual_composition: Option<Type>,
|
||||
// TODO: Remove once this references its definition instead (see above TODO).
|
||||
@@ -745,7 +690,8 @@ fn default_export_metadata() -> (NodeId, IVec2) {
|
||||
pub struct NodeNetwork {
|
||||
/// The list of data outputs that are exported from this network to the parent network.
|
||||
/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.
|
||||
#[cfg_attr(feature = "serde", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] // TODO: Eventually remove this alias document upgrade code
|
||||
// TODO: Eventually remove this alias document upgrade code
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(alias = "outputs", deserialize_with = "deserialize_exports"))]
|
||||
pub exports: Vec<NodeInput>,
|
||||
// TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.
|
||||
// pub import_types: Vec<Type>,
|
||||
|
||||
@@ -49,8 +49,8 @@ macro_rules! tagged_value {
|
||||
}
|
||||
}
|
||||
impl<'a> TaggedValue {
|
||||
/// Converts to a Box<dyn DynAny> - this isn't very neat but I'm not sure of a better approach
|
||||
pub fn to_any(self) -> DAny<'a> {
|
||||
/// Converts to a Box<dyn DynAny>
|
||||
pub fn to_dynany(self) -> DAny<'a> {
|
||||
match self {
|
||||
Self::None => Box::new(()),
|
||||
$( Self::$identifier(x) => Box::new(x), )*
|
||||
@@ -59,6 +59,16 @@ macro_rules! tagged_value {
|
||||
Self::EditorApi(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
|
||||
pub fn to_any(self) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||
match self {
|
||||
Self::None => Arc::new(()),
|
||||
$( Self::$identifier(x) => Arc::new(x), )*
|
||||
Self::RenderOutput(x) => Arc::new(x),
|
||||
Self::SurfaceFrame(x) => Arc::new(x),
|
||||
Self::EditorApi(x) => Arc::new(x),
|
||||
}
|
||||
}
|
||||
/// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
@@ -84,6 +94,18 @@ macro_rules! tagged_value {
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
}
|
||||
/// Attempts to downcast the dynamic type to a tagged value
|
||||
pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result<Self, String> {
|
||||
use std::any::TypeId;
|
||||
|
||||
match input.type_id() {
|
||||
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::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(graphene_core::SurfaceFrame::clone(input.downcast_ref().unwrap()))),
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))),
|
||||
}
|
||||
}
|
||||
pub fn from_type(input: &Type) -> Option<Self> {
|
||||
match input {
|
||||
Type::Generic(_) => {
|
||||
@@ -135,16 +157,16 @@ macro_rules! tagged_value {
|
||||
|
||||
tagged_value! {
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))]
|
||||
ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>),
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))]
|
||||
VectorData(graphene_core::vector::VectorDataTable),
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(deserialize_with = "graphene_core::migrate_graphic_group"))]
|
||||
GraphicGroup(graphene_core::GraphicGroupTable),
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_artboard_group"))]
|
||||
#[cfg_attr(all(feature = "serde", target_arch = "wasm32"), serde(deserialize_with = "graphene_core::migrate_artboard_group"))]
|
||||
ArtboardGroup(graphene_core::ArtboardGroupTable),
|
||||
GraphicElement(graphene_core::GraphicElement),
|
||||
Artboard(graphene_core::Artboard),
|
||||
@@ -332,7 +354,7 @@ impl<'input> Node<'input, DAny<'input>> for UpcastNode {
|
||||
type Output = FutureAny<'input>;
|
||||
|
||||
fn eval(&'input self, _: DAny<'input>) -> Self::Output {
|
||||
Box::pin(async move { self.value.clone().into_inner().to_any() })
|
||||
Box::pin(async move { self.value.clone().into_inner().to_dynany() })
|
||||
}
|
||||
}
|
||||
impl UpcastNode {
|
||||
|
||||
@@ -532,7 +532,7 @@ impl ProtoNetwork {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphErrorType {
|
||||
NodeNotFound(NodeId),
|
||||
InputNodeNotFound(NodeId),
|
||||
@@ -571,7 +571,7 @@ impl core::fmt::Debug for GraphErrorType {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphError {
|
||||
pub node_path: Vec<NodeId>,
|
||||
pub identifier: Cow<'static, str>,
|
||||
|
||||
@@ -68,6 +68,14 @@ pub struct WasmApplicationIo {
|
||||
static WGPU_AVAILABLE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
|
||||
|
||||
pub fn wgpu_available() -> Option<bool> {
|
||||
// Always enable wgpu when running with Tauri
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(window) = web_sys::window() {
|
||||
if js_sys::Reflect::get(&window, &wasm_bindgen::JsValue::from_str("__TAURI__")).is_ok() {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
||||
match WGPU_AVAILABLE.load(::std::sync::atomic::Ordering::SeqCst) {
|
||||
-1 => None,
|
||||
0 => Some(false),
|
||||
@@ -92,9 +100,32 @@ impl WasmApplicationIo {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let executor = WgpuExecutor::new().await;
|
||||
WGPU_AVAILABLE.store(executor.is_some() as i8, ::std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
let mut io = Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
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 {
|
||||
let executor = WgpuExecutor::new().await;
|
||||
|
||||
WGPU_AVAILABLE.store(executor.is_some() as i8, ::std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
// Always enable wgpu when running with Tauri
|
||||
let mut io = Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
ids: AtomicU64::new(0),
|
||||
@@ -103,12 +134,9 @@ impl WasmApplicationIo {
|
||||
windows: Vec::new(),
|
||||
resources: HashMap::new(),
|
||||
};
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -178,19 +206,22 @@ impl ApplicationIo for WasmApplicationIo {
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn create_window(&self) -> SurfaceHandle<Self::Surface> {
|
||||
#[cfg(feature = "wayland")]
|
||||
log::trace!("Spawning window");
|
||||
|
||||
#[cfg(not(test))]
|
||||
use winit::platform::wayland::EventLoopBuilderExtWayland;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
#[cfg(not(test))]
|
||||
let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap();
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
|
||||
#[cfg(test)]
|
||||
let event_loop = winit::event_loop::EventLoop::new().unwrap();
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title("Graphite")
|
||||
.with_inner_size(winit::dpi::PhysicalSize::new(800, 600))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
// self.windows.lock().as_mut().unwrap().push(window.clone());
|
||||
|
||||
SurfaceHandle {
|
||||
window_id: SurfaceId(window.id().into()),
|
||||
surface: Arc::new(window),
|
||||
@@ -271,7 +302,7 @@ impl ApplicationIo for WasmApplicationIo {
|
||||
pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
|
||||
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<wgpu_executor::Window>;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, specta::Type)]
|
||||
#[derive(Clone, Debug, PartialEq, Hash, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct EditorPreferences {
|
||||
// pub imaginate_hostname: String,
|
||||
@@ -287,6 +318,18 @@ impl graphene_core::application_io::GetEditorPreferences for EditorPreferences {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EditorPreferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// imaginate_hostname: "http://localhost:7860/".into(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_vello: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use_vello: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl dyn_any::StaticType for EditorPreferences {
|
||||
type Static = EditorPreferences;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user