mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Implement experimental WebGPU support (#1238)
* Web gpu execution MVP Ready infrastructure for wgpu experimentation Start implementing simple gpu test case Fix Extract Node not working with nested networks Convert inputs for extracted node to network inputs Fix missing cors headers Feature gate gcore to make it once again no-std compatible Add skeleton structure gpu shader Work on gpu node graph output saving Fix Get and Set nodes Fix storage nodes Fix shader construction errors -> spirv errors Add unsafe version Add once cell node Web gpu execution MVP
This commit is contained in:
committed by
Keavon Chambers
parent
4bd9fbd073
commit
0586d52f3a
@@ -72,6 +72,7 @@ impl DocumentNode {
|
||||
}
|
||||
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::ShortCircut(_))), "recieved non resolved parameter");
|
||||
@@ -82,6 +83,10 @@ impl DocumentNode {
|
||||
&args
|
||||
);
|
||||
|
||||
// If we have one parameter of the type inline, set it as the construction args
|
||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
||||
args = ConstructionArgs::Inline(inline.clone());
|
||||
}
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
@@ -176,6 +181,20 @@ pub enum NodeInput {
|
||||
/// but actually consuming the provided input instead of passing it to its predecessor.
|
||||
/// See [NodeInput] docs for more explanation.
|
||||
ShortCircut(Type),
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct InlineRust {
|
||||
pub expr: String,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl InlineRust {
|
||||
pub fn new(expr: String, ty: Type) -> Self {
|
||||
Self { expr, ty }
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeInput {
|
||||
@@ -203,6 +222,7 @@ impl NodeInput {
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network(_) => false,
|
||||
NodeInput::ShortCircut(_) => false,
|
||||
NodeInput::Inline(_) => false,
|
||||
}
|
||||
}
|
||||
pub fn ty(&self) -> Type {
|
||||
@@ -211,6 +231,7 @@ impl NodeInput {
|
||||
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
|
||||
NodeInput::Network(ty) => ty.clone(),
|
||||
NodeInput::ShortCircut(ty) => ty.clone(),
|
||||
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +246,7 @@ pub enum DocumentNodeImplementation {
|
||||
|
||||
impl Default for DocumentNodeImplementation {
|
||||
fn default() -> Self {
|
||||
Self::Unresolved(NodeIdentifier::new("graphene_cored::ops::IdNode"))
|
||||
Self::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +320,9 @@ impl NodeNetwork {
|
||||
self.inputs.iter().map(move |id| self.nodes[id].inputs.get(0).map(|i| i.ty()).unwrap_or(concrete!(())))
|
||||
}
|
||||
|
||||
/// An empty graph
|
||||
pub fn value_network(node: DocumentNode) -> Self {
|
||||
Self {
|
||||
inputs: vec![0],
|
||||
inputs: node.inputs.iter().filter(|input| matches!(input, NodeInput::Network(_))).map(|_| 0).collect(),
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(0, node)].into_iter().collect(),
|
||||
disabled: vec![],
|
||||
@@ -754,6 +774,7 @@ impl NodeNetwork {
|
||||
}
|
||||
NodeInput::ShortCircut(_) => (),
|
||||
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||
NodeInput::Inline(_) => (),
|
||||
}
|
||||
}
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
@@ -772,14 +793,69 @@ impl NodeNetwork {
|
||||
}
|
||||
}
|
||||
DocumentNodeImplementation::Unresolved(_) => (),
|
||||
DocumentNodeImplementation::Extract => {
|
||||
panic!("Extract nodes should have been removed before flattening");
|
||||
}
|
||||
DocumentNodeImplementation::Extract => (),
|
||||
}
|
||||
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
||||
self.nodes.insert(id, node);
|
||||
}
|
||||
|
||||
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
|
||||
let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {} does not exist", id))?.clone();
|
||||
if let DocumentNodeImplementation::Unresolved(ident) = &node.implementation {
|
||||
if ident.name == "graphene_core::ops::IdNode" {
|
||||
assert_eq!(node.inputs.len(), 1, "Id node has more than one input");
|
||||
if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] {
|
||||
let input_node_id = node_id;
|
||||
for output in self.nodes.values_mut() {
|
||||
for input in &mut output.inputs {
|
||||
if let NodeInput::Node {
|
||||
node_id: output_node_id,
|
||||
output_index: output_output_index,
|
||||
..
|
||||
} = input
|
||||
{
|
||||
if *output_node_id == id {
|
||||
*output_node_id = input_node_id;
|
||||
*output_output_index = output_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
for NodeOutput {
|
||||
ref mut node_id,
|
||||
ref mut node_output_index,
|
||||
} in self.outputs.iter_mut()
|
||||
{
|
||||
if *node_id == id {
|
||||
*node_id = input_node_id;
|
||||
*node_output_index = output_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.nodes.remove(&id);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_redundant_id_nodes(&mut self) {
|
||||
let id_nodes = self
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|(_, node)| {
|
||||
matches!(&node.implementation, DocumentNodeImplementation::Unresolved(ident) if ident == &NodeIdentifier::new("graphene_core::ops::IdNode"))
|
||||
&& node.inputs.len() == 1
|
||||
&& matches!(node.inputs[0], NodeInput::Node { .. })
|
||||
})
|
||||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>();
|
||||
for id in id_nodes {
|
||||
if let Err(e) = self.remove_id_node(id) {
|
||||
log::warn!("{}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_extract_nodes(&mut self) {
|
||||
let mut extraction_nodes = self
|
||||
.nodes
|
||||
@@ -792,14 +868,20 @@ impl NodeNetwork {
|
||||
for (_, node) in &mut extraction_nodes {
|
||||
if let DocumentNodeImplementation::Extract = node.implementation {
|
||||
assert_eq!(node.inputs.len(), 1);
|
||||
let NodeInput::Node { node_id, output_index, lambda } = node.inputs.pop().unwrap() else {
|
||||
let NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else {
|
||||
panic!("Extract node has no input");
|
||||
};
|
||||
assert_eq!(output_index, 0);
|
||||
assert!(lambda);
|
||||
let input_node = self.nodes.get_mut(&node_id).unwrap();
|
||||
// TODO: check if we can readd lambda checking
|
||||
let mut input_node = self.nodes.remove(&node_id).unwrap();
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into());
|
||||
node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node.clone()), false)];
|
||||
for input in input_node.inputs.iter_mut() {
|
||||
match input {
|
||||
NodeInput::Node { .. } | NodeInput::Value { .. } => *input = NodeInput::Network(generic!(T)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)];
|
||||
}
|
||||
}
|
||||
self.nodes.extend(extraction_nodes);
|
||||
@@ -926,6 +1008,7 @@ mod test {
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
..Default::default()
|
||||
};
|
||||
// TODO: Extend test cases to test nested network
|
||||
let mut extraction_network = NodeNetwork {
|
||||
inputs: vec![],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
@@ -945,7 +1028,7 @@ mod test {
|
||||
..Default::default()
|
||||
};
|
||||
extraction_network.resolve_extract_nodes();
|
||||
assert_eq!(extraction_network.nodes.len(), 2);
|
||||
assert_eq!(extraction_network.nodes.len(), 1);
|
||||
let inputs = extraction_network.nodes.get(&1).unwrap().inputs.clone();
|
||||
assert_eq!(inputs.len(), 1);
|
||||
assert!(matches!(&inputs[0], &NodeInput::Value{ tagged_value: TaggedValue::DocumentNode(ref network), ..} if network == &id_node));
|
||||
|
||||
@@ -189,7 +189,7 @@ impl<'a> TaggedValue {
|
||||
pub fn to_primitive_string(&self) -> String {
|
||||
match self {
|
||||
TaggedValue::None => "()".to_string(),
|
||||
TaggedValue::String(x) => x.clone(),
|
||||
TaggedValue::String(x) => format!("\"{}\"", x),
|
||||
TaggedValue::U32(x) => x.to_string(),
|
||||
TaggedValue::F32(x) => x.to_string(),
|
||||
TaggedValue::F64(x) => x.to_string(),
|
||||
|
||||
@@ -10,19 +10,23 @@ pub struct Compiler {}
|
||||
impl Compiler {
|
||||
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
network.resolve_extract_nodes();
|
||||
println!("flattening");
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
network.remove_redundant_id_nodes();
|
||||
network.resolve_extract_nodes();
|
||||
network.remove_dead_nodes();
|
||||
let proto_networks = network.into_proto_networks();
|
||||
proto_networks.map(move |mut proto_network| {
|
||||
if resolve_inputs {
|
||||
println!("resolving inputs");
|
||||
log::debug!("resolving inputs");
|
||||
proto_network.resolve_inputs();
|
||||
}
|
||||
proto_network.reorder_ids();
|
||||
proto_network.generate_stable_node_ids();
|
||||
log::debug!("proto network: {:?}", proto_network);
|
||||
proto_network
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
use xxhash_rust::xxh3::Xxh3;
|
||||
|
||||
use crate::document::value;
|
||||
use crate::document::NodeId;
|
||||
use crate::document::{value, InlineRust};
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::*;
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -66,6 +66,10 @@ impl core::fmt::Display for ProtoNetwork {
|
||||
write_node(f, network, id.0, indent + 1)?;
|
||||
}
|
||||
}
|
||||
ConstructionArgs::Inline(inline) => {
|
||||
f.write_str(&"\t".repeat(indent + 1))?;
|
||||
f.write_fmt(format_args!("Inline construction argument: {inline:?}"))?
|
||||
}
|
||||
}
|
||||
f.write_str(&"\t".repeat(indent))?;
|
||||
f.write_str("}\n")?;
|
||||
@@ -83,6 +87,7 @@ pub enum ConstructionArgs {
|
||||
Value(value::TaggedValue),
|
||||
// the bool indicates whether to treat the node as lambda node
|
||||
Nodes(Vec<(NodeId, bool)>),
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
impl PartialEq for ConstructionArgs {
|
||||
@@ -105,6 +110,7 @@ impl Hash for ConstructionArgs {
|
||||
}
|
||||
}
|
||||
Self::Value(value) => value.hash(state),
|
||||
Self::Inline(inline) => inline.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,6 +120,7 @@ impl ConstructionArgs {
|
||||
match self {
|
||||
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("&n{}", n.0)).collect(),
|
||||
ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
|
||||
ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,6 +460,7 @@ impl TypingContext {
|
||||
.map(|node| node.ty())
|
||||
})
|
||||
.collect::<Result<Vec<Type>, String>>()?,
|
||||
ConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()],
|
||||
};
|
||||
|
||||
// Get the node input type from the proto node declaration
|
||||
|
||||
Reference in New Issue
Block a user