Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/src/render/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub(crate) struct RenderState {
impl RenderState {
pub(crate) fn new(window: &Window, context: WgpuContext, present_mode: Option<PresentMode>) -> Self {
let size = window.surface_size();
let surface = window.create_surface(context.instance.clone());
let surface = window.create_surface(&context.instance);

let surface_caps = surface.get_capabilities(&context.adapter);
let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl Window {
self.winit_window.request_redraw();
}

pub(crate) fn create_surface(&self, instance: Arc<wgpu::Instance>) -> wgpu::Surface<'static> {
pub(crate) fn create_surface(&self, instance: &wgpu::Instance) -> wgpu::Surface<'static> {
instance.create_surface(self.winit_window.clone()).unwrap()
}

Expand Down
8 changes: 4 additions & 4 deletions editor/src/node_graph_executor/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,13 @@ impl NodeRuntime {
None
}

async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
if let Err(e) = self.preprocessor.expand_network(&mut graph, &self.resources) {
async fn update_network(&mut self, graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
let mut scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());

if let Err(e) = self.preprocessor.preprocess(&mut scoped_network, &self.resources) {
return Err((ResolvedDocumentNodeTypesDelta::default(), e.to_string()));
}

let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());

// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");

Expand Down
2 changes: 1 addition & 1 deletion node-graph/graph-craft/src/application_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use graphene_application_io::ApplicationIo;
#[derive(Default)]
pub struct PlatformApplicationIo {
#[cfg(feature = "wgpu")]
pub(crate) gpu_executor: Option<WgpuExecutor>,
gpu_executor: Option<WgpuExecutor>,
resources: Option<Box<dyn resource::LoadResource>>,
}

Expand Down
86 changes: 55 additions & 31 deletions node-graph/graph-craft/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,59 @@ impl NodeNetwork {
}
}

// Functions for resolving scope inputs
impl NodeNetwork {
pub fn resolve_scope_inputs(&mut self) {
let mut leftover = Vec::new();
self.resolve_scope_inputs_with(None, &mut leftover);
assert!(leftover.is_empty(), "Unresolved scope keys at top level: {leftover:?}");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Top-level scope resolution now panics on unresolved keys. This can crash graph compilation instead of failing gracefully with a recoverable error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/graph-craft/src/document.rs, line 663:

<comment>Top-level scope resolution now panics on unresolved keys. This can crash graph compilation instead of failing gracefully with a recoverable error.</comment>

<file context>
@@ -655,6 +655,59 @@ impl NodeNetwork {
+	pub fn resolve_scope_inputs(&mut self) {
+		let mut leftover = Vec::new();
+		self.resolve_scope_inputs_with(None, &mut leftover);
+		assert!(leftover.is_empty(), "Unresolved scope keys at top level: {leftover:?}");
+	}
+
</file context>

}

fn resolve_scope_inputs_with(&mut self, parent: Option<&ScopeChain<'_>>, parent_inputs: &mut Vec<NodeInput>) {
let chain = ScopeChain {
scopes: &self.scope_injections,
parent,
};

for node in self.nodes.values_mut() {
let DocumentNodeImplementation::Network(network) = &mut node.implementation else { continue };
network.resolve_scope_inputs_with(Some(&chain), &mut node.inputs);
}

let mut key_to_idx: FxHashMap<Cow<'static, str>, usize> = FxHashMap::default();
for node in self.nodes.values_mut() {
for input in node.inputs.iter_mut() {
let NodeInput::Scope(key) = input else { continue };
if let Some((producer_id, _)) = self.scope_injections.get(key.as_ref()) {
*input = NodeInput::node(*producer_id, 0);
continue;
}
let import_type = chain
.get(key.as_ref())
.map(|(_, t)| t.clone())
.unwrap_or_else(|| panic!("Scope key `{key}` not found in any ancestor scope_injections"));
let import_index = *key_to_idx.entry(key.clone()).or_insert_with(|| {
let index = parent_inputs.len();
parent_inputs.push(NodeInput::Scope(key.clone()));
index
});
*input = NodeInput::Import { import_type, import_index };
}
}
}
}

struct ScopeChain<'a> {
scopes: &'a FxHashMap<String, (NodeId, Type)>,
parent: Option<&'a ScopeChain<'a>>,
}

impl ScopeChain<'_> {
fn get(&self, key: &str) -> Option<&(NodeId, Type)> {
self.scopes.get(key).or_else(|| self.parent?.get(key))
}
}

/// Functions for compiling the network
impl NodeNetwork {
/// Replace all references in the graph of a node ID with a new node ID defined by the function `f`.
Expand Down Expand Up @@ -784,18 +837,6 @@ impl NodeNetwork {
are_inputs_used
}

pub fn resolve_scope_inputs(&mut self) {
for node in self.nodes.values_mut() {
for input in node.inputs.iter_mut() {
if let NodeInput::Scope(key) = input {
let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope");
// TODO use correct output index
*input = NodeInput::node(*import_id, 0);
}
}
}
}

/// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network.
pub fn flatten(&mut self, node_id: NodeId) {
self.flatten_with_fns(node_id, merge_ids, NodeId::new)
Expand Down Expand Up @@ -886,11 +927,7 @@ impl NodeNetwork {
}
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
NodeInput::Inline(_) => (),
NodeInput::Scope(ref key) => {
let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope");
// TODO use correct output index
nested_node.inputs[nested_input_index] = NodeInput::node(*import_id, 0);
}
NodeInput::Scope(_) => unreachable!("Scope inputs should have been resolved by resolve_scope_inputs_recursive before flattening"),
NodeInput::Reflection(_) => unreachable!("Reflection inputs should have been replaced with value nodes"),
}
}
Expand All @@ -906,21 +943,8 @@ impl NodeNetwork {
}
}

/// Connect all nodes that were previously connected to this node to the nodes of the inner network
fn replace_node_with_its_exports(&mut self, id: NodeId, original_location: &OriginalLocation, exports: &[NodeInput]) {
// Connect scope injections to the inner network export
self.scope_injections.values_mut().for_each(|(node_id, _ty)| {
if node_id == &id {
let Some(export) = exports.first() else {
log::error!("Inner network should have at least one export");
return;
};
if let NodeInput::Node { node_id: export_id, output_index: _ } = export {
*node_id = *export_id;
}
}
});

// Connect all nodes that were previously connected to this node to the nodes of the inner network
for (i, export) in exports.iter().enumerate() {
if let NodeInput::Node { node_id, output_index, .. } = &export {
for deps in &original_location.dependants {
Expand Down
3 changes: 2 additions & 1 deletion node-graph/graph-craft/src/graphene_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ pub struct Compiler {}

impl Compiler {
pub fn compile(&self, mut network: NodeNetwork) -> impl Iterator<Item = Result<ProtoNetwork, String>> {
network.resolve_scope_inputs();
network.generate_node_paths(&[]);
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
network.populate_dependants();
for id in node_ids {
network.flatten(id);
}
network.resolve_scope_inputs();
network.remove_redundant_passthrough_nodes();
// network.remove_dead_nodes(0);
let proto_networks = network.into_proto_networks();
Expand Down
8 changes: 4 additions & 4 deletions node-graph/graphene-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

// Get reference to wgpu executor and clone device handle
let wgpu_executor_ref = application_io_arc.gpu_executor().unwrap();
let device = wgpu_executor_ref.context.device.clone();
let device = wgpu_executor_ref.context().device.clone();

let preferences = EditorPreferences {
max_render_region_size: EditorPreferences::default().max_render_region_size,
Expand Down Expand Up @@ -242,10 +242,10 @@ fn compile_graph(document_string: String, editor_api: Arc<PlatformEditorApi>) ->
let mut network = load_network(&document_string);
fix_nodes(&mut network);

let preprocessor = preprocessor::Preprocessor::new();
preprocessor.expand_network(&mut network, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document
let mut wrapped_network = wrap_network_in_scope(network, editor_api);

let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
let preprocessor = preprocessor::Preprocessor::new();
preprocessor.preprocess(&mut wrapped_network, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document

let compiler = Compiler {};
compiler.compile_single(wrapped_network).map_err(|x| x.into())
Expand Down
6 changes: 3 additions & 3 deletions node-graph/interpreted-executor/benches/benchmark_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use interpreted_executor::dynamic_executor::DynamicExecutor;
use interpreted_executor::util::wrap_network_in_scope;

pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
let mut network = load_from_name(name);
let network = load_from_name(name);
let editor_api = std::sync::Arc::new(EditorApi::default());
let mut network = wrap_network_in_scope(network, editor_api);
let preprocessor = preprocessor::Preprocessor::new();
preprocessor.expand_network(&mut network, &ResourceRegistry::default()).unwrap();
let network = wrap_network_in_scope(network, editor_api);
preprocessor.preprocess(&mut network, &ResourceRegistry::default()).unwrap();
let proto_network = compile(network);
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();
(executor, proto_network)
Expand Down
4 changes: 4 additions & 0 deletions node-graph/interpreted-executor/src/node_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextFeatures]),
#[cfg(target_family = "wasm")]
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 => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &wgpu_executor::WgpuExecutor, Context => graphene_std::ContextFeatures]),
// ==========
// MEMO NODES
// ==========
Expand Down Expand Up @@ -285,6 +287,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &wgpu_executor::WgpuExecutor]),
];
// =============
// CONVERT NODES
Expand Down
41 changes: 17 additions & 24 deletions node-graph/interpreted-executor/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use graph_craft::ProtoNodeIdentifier;
use graph_craft::application_io::PlatformEditorApi;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
Expand All @@ -8,11 +7,8 @@ use graphene_std::Context;
use graphene_std::ContextFeatures;
use graphene_std::uuid::NodeId;
use std::sync::Arc;
use wgpu_executor::WgpuExecutor;

pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformEditorApi>) -> NodeNetwork {
network.generate_node_paths(&[]);

pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEditorApi>) -> NodeNetwork {
let inner_network = DocumentNode {
implementation: DocumentNodeImplementation::Network(network),
inputs: vec![],
Expand Down Expand Up @@ -42,7 +38,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(0), 0)],
inputs: vec![NodeInput::scope(graphene_std::platform_application_io::wgpu_executor::IDENTIFIER), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::render::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
Expand All @@ -52,7 +48,11 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(1), 0)],
inputs: vec![
NodeInput::scope(graphene_std::platform_application_io::wgpu_executor::IDENTIFIER),
NodeInput::scope("editor-api"),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_cache::render_output_cache::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
Expand All @@ -62,8 +62,8 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::pixel_preview::pixel_preview::IDENTIFIER),
inputs: vec![NodeInput::scope(graphene_std::render_pixel_preview::pixel_preview_pipeline::IDENTIFIER), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_pixel_preview::render_pixel_preview::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
inject: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
Expand All @@ -72,8 +72,11 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::render_background::IDENTIFIER),
inputs: vec![
NodeInput::scope(graphene_std::render_background::background_compositor_pipeline::IDENTIFIER),
NodeInput::node(NodeId(3), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_background::render_background::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
inject: ContextFeatures::empty(),
Expand Down Expand Up @@ -102,7 +105,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
};

// wrap the inner network in a scope
let mut nodes = vec![
let nodes = vec![
inner_network,
render_node,
DocumentNode {
Expand All @@ -111,22 +114,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
..Default::default()
},
];
let mut scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(&PlatformEditorApi)))];

if cfg!(feature = "gpu") {
nodes.push(DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
inputs: vec![NodeInput::node(NodeId(2), 0)],
..Default::default()
});
scope_injections.push(("wgpu-executor".to_string(), (NodeId(3), concrete!(&WgpuExecutor))));
}
let scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(&PlatformEditorApi)))];

NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
scope_injections: scope_injections.into_iter().collect(),
// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
generated: false,
generated: true,
}
}
12 changes: 7 additions & 5 deletions node-graph/libraries/canvas-utils/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl CanvasSurfaceHandle {
if self.1.is_none() {
let canvas = self.0.get().canvas.clone();
let surface = executor
.context
.context()
.instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
.expect("Failed to create surface from canvas");
Expand All @@ -86,21 +86,23 @@ impl Canvas for CanvasSurfaceHandle {
#[cfg(feature = "wgpu")]
impl CanvasSurface for CanvasSurfaceHandle {
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) {
let context = executor.context();

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 {
let mut encoder = 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);
let surface_caps = surface.get_capabilities(&context.adapter);
surface.configure(
&executor.context.device,
&context.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
format: wgpu::TextureFormat::Rgba8Unorm,
Expand Down Expand Up @@ -134,7 +136,7 @@ impl CanvasSurface for CanvasSurfaceHandle {
source_texture.size(),
);

executor.context.queue.submit([encoder.finish()]);
context.queue.submit([encoder.finish()]);
surface_texture.present();
}
}
Expand Down
6 changes: 6 additions & 0 deletions node-graph/libraries/core-types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ impl ProtoNodeIdentifier {
}
}

impl From<ProtoNodeIdentifier> for Cow<'static, str> {
fn from(val: ProtoNodeIdentifier) -> Self {
val.name
}
}

impl Display for ProtoNodeIdentifier {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ProtoNodeIdentifier").field(&self.name).finish()
Expand Down
Loading
Loading