From 68a2fb2be35f1995e78c29a0f5bef3780943c845 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 27 Aug 2026 14:44:19 +0000 Subject: [PATCH] Render root levels natively and remove the unconstructed stack form --- editor/src/node_graph_executor/runtime.rs | 4 +- node-graph/libraries/core-types/src/record.rs | 60 +----- .../libraries/graphic-types/src/boundary.rs | 6 +- .../libraries/graphic-types/src/graphic.rs | 172 ++++++------------ .../libraries/rendering/src/renderer.rs | 148 ++++++++++----- node-graph/nodes/graphic/src/artboard.rs | 2 +- node-graph/nodes/graphic/src/graphic.rs | 4 +- node-graph/nodes/graphic/src/record.rs | 48 ++--- node-graph/nodes/gstd/src/render_node.rs | 8 +- 9 files changed, 194 insertions(+), 258 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 7d7eebfa3f..a919531aca 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -495,7 +495,7 @@ impl NodeRuntime { let thumbnail_renders = &mut self.thumbnail_renders; let vector_modify = &mut self.vector_modify; let result = self.executor.introspect_with(monitor_node_path, |layout, batch, _arena| { - use graphene_std::core_types::record::{Group, GroupContent, GroupItem, RunView}; + use graphene_std::core_types::record::{Group, GroupItem, RunView}; let type_id = layout.element.type_id; // Graphic run: thumbnail (text-aware bounds, since the `BoundingBox` trait can't lay out `Graphic::Text` content) if type_id == std::any::TypeId::of::() { @@ -505,7 +505,7 @@ impl NodeRuntime { let bounds = graphene_std::renderer::graphic_list_bounding_box(&RunView::::new(&item)?, DAffine2::IDENTITY); let group = Graphic::Group(Group { row: None, - content: GroupContent::Run(item), + content: item, }); Self::render_thumbnail(thumbnail_renders, parent_network_node_id, &group, bounds, responses) } diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index e6336ac22d..2d1f89ca7c 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -2015,48 +2015,36 @@ impl crate::bounds::BoundingBox for Run } } -/// The records a group stores: a single homogeneous run, or a list of -/// segments. -#[derive(Clone, Debug)] -pub enum GroupContent { - Run(GroupItem), - Stack(Vec), -} - -/// Records nested inside one element. The `row` holds the group's own -/// attribute record. A group that sits on a lane leaves it `None`, because -/// that lane's record carries the attributes. +/// Records nested inside one element: a single homogeneous run. The `row` +/// holds the group's own attribute record. A group that sits on a lane leaves +/// it `None`, because that lane's record carries the attributes. The typed +/// segment stack returns when merge constructs segments. #[derive(Clone, Debug)] pub struct Group { pub row: Option, - pub content: GroupContent, + pub content: GroupItem, } impl Group { /// The group deep-copied out of its evaluation, every run in owned form. pub fn copy_out(&self) -> Group { - let content = match &self.content { - GroupContent::Run(item) => GroupContent::Run(item.copy_out()), - GroupContent::Stack(children) => GroupContent::Stack(children.iter().map(Group::copy_out).collect()), - }; Group { row: self.row.as_ref().map(GroupItem::copy_out), - content, + content: self.content.copy_out(), } } /// Re-parks an owned group's runs into `arena`; `None` reports arena /// exhaustion. pub fn replay(&self, arena: &crate::arena::Arena) -> Option { - let content = match &self.content { - GroupContent::Run(item) => GroupContent::Run(item.replay(arena)?), - GroupContent::Stack(children) => GroupContent::Stack(children.iter().map(|child| child.replay(arena)).collect::>()?), - }; let row = match &self.row { Some(row) => Some(row.replay(arena)?), None => None, }; - Some(Group { row, content }) + Some(Group { + row, + content: self.content.replay(arena)?, + }) } } @@ -2126,34 +2114,6 @@ impl graphene_hash::CacheHash for GroupItem { } } -impl PartialEq for GroupContent { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (GroupContent::Run(a), GroupContent::Run(b)) => a == b, - (GroupContent::Stack(a), GroupContent::Stack(b)) => a == b, - _ => false, - } - } -} - -impl graphene_hash::CacheHash for GroupContent { - fn cache_hash(&self, state: &mut H) { - match self { - GroupContent::Run(item) => { - state.write_u8(0); - item.cache_hash(state); - } - GroupContent::Stack(children) => { - state.write_u8(1); - state.write_usize(children.len()); - for child in children { - child.cache_hash(state); - } - } - } - } -} - impl PartialEq for Group { fn eq(&self, other: &Self) -> bool { self.row == other.row && self.content == other.content diff --git a/node-graph/libraries/graphic-types/src/boundary.rs b/node-graph/libraries/graphic-types/src/boundary.rs index dc9ef5ae01..5cf2e15ef2 100644 --- a/node-graph/libraries/graphic-types/src/boundary.rs +++ b/node-graph/libraries/graphic-types/src/boundary.rs @@ -10,7 +10,7 @@ use core_types::arena::Arena; use core_types::context::InjectIndex; use core_types::gpoll::{Finality, GraphError}; use core_types::node::Node; -use core_types::record::{Group, GroupContent, GroupItem, LevelStatus, RecordValue, materialize_level}; +use core_types::record::{Group, GroupItem, LevelStatus, RecordValue, materialize_level}; use core_types::uuid::NodeId; use glam::{DAffine2, DVec2}; use vector_types::GradientStops; @@ -36,7 +36,7 @@ where LevelGroup::Group( Group { row: None, - content: GroupContent::Run(item), + content: item, }, finality, ) @@ -82,7 +82,7 @@ pub fn batch_to_legacy(layout: &core_types::record::Layout, batch: core_types::n if item.typed_lanes::().is_some() { let group = Group { row: None, - content: GroupContent::Run(item), + content: item, }; return Some(Box::new(group_to_legacy_list(&group))); } diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 4bee35aeda..dd3eed0d05 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -671,55 +671,33 @@ impl RunAttrs { } } -pub fn group_row_transform(group: &core_types::record::Group) -> DAffine2 { - match &group.row { - Some(row) if !row.is_empty() => RunAttrs::read_or(row, RunAttrs::of(row).transform, 0, DAffine2::IDENTITY), - _ => DAffine2::IDENTITY, - } -} - pub fn group_is_empty(group: &core_types::record::Group) -> bool { - match &group.content { - core_types::record::GroupContent::Run(item) => item.is_empty(), - core_types::record::GroupContent::Stack(children) => children.iter().all(group_is_empty), - } + group.content.is_empty() } fn group_all_clipped(group: &core_types::record::Group) -> bool { - match &group.content { - core_types::record::GroupContent::Run(item) => { - let attrs = RunAttrs::of(item); - (0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.clipping_mask, lane, false)) - } - core_types::record::GroupContent::Stack(children) => children.iter().all(group_all_clipped), - } + let item = &group.content; + let attrs = RunAttrs::of(item); + (0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.clipping_mask, lane, false)) } fn group_is_opaque(group: &core_types::record::Group) -> bool { - match &group.content { - core_types::record::GroupContent::Run(item) => { - let attrs = RunAttrs::of(item); - let lanes = item.typed_lanes::(); - !item.is_empty() - && (0..item.len()).all(|lane| { - RunAttrs::read_or(item, attrs.opacity, lane, 1.) >= 1. - && RunAttrs::read_or(item, attrs.opacity_fill, lane, 1.) >= 1. - && lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_opaque()) - }) - } - core_types::record::GroupContent::Stack(children) => !children.is_empty() && children.iter().all(group_is_opaque), - } + let item = &group.content; + let attrs = RunAttrs::of(item); + let lanes = item.typed_lanes::(); + !item.is_empty() + && (0..item.len()).all(|lane| { + RunAttrs::read_or(item, attrs.opacity, lane, 1.) >= 1. + && RunAttrs::read_or(item, attrs.opacity_fill, lane, 1.) >= 1. + && lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_opaque()) + }) } fn group_is_fully_transparent(group: &core_types::record::Group) -> bool { - match &group.content { - core_types::record::GroupContent::Run(item) => { - let attrs = RunAttrs::of(item); - let lanes = item.typed_lanes::(); - (0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.opacity, lane, 1.) <= 0. || lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_fully_transparent())) - } - core_types::record::GroupContent::Stack(children) => children.iter().all(group_is_fully_transparent), - } + let item = &group.content; + let attrs = RunAttrs::of(item); + let lanes = item.typed_lanes::(); + (0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.opacity, lane, 1.) <= 0. || lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_fully_transparent())) } fn group_bounding_box(group: &core_types::record::Group, transform: DAffine2, include_stroke: bool, thumbnail: bool) -> RenderBoundingBox { @@ -772,24 +750,7 @@ fn group_bounding_box(group: &core_types::record::Group, transform: DAffine2, in .or_else(|| typed_run::(item, transform, include_stroke, thumbnail)) .unwrap_or(RenderBoundingBox::Infinite) } - match &group.content { - core_types::record::GroupContent::Run(item) => run_bounding_box(item, transform, include_stroke, thumbnail), - core_types::record::GroupContent::Stack(children) => { - let mut combined = None; - let mut any_infinite = false; - for child in children { - let bounds = group_bounding_box(child, transform * group_row_transform(child), include_stroke, thumbnail); - if let Some(short_circuit) = combine(&mut combined, &mut any_infinite, bounds, thumbnail) { - return short_circuit; - } - } - match (combined, any_infinite) { - (Some(bounds), _) => RenderBoundingBox::Rectangle(bounds), - (None, true) => RenderBoundingBox::Infinite, - (None, false) => RenderBoundingBox::None, - } - } - } + run_bounding_box(&group.content, transform, include_stroke, thumbnail) } /// One typed run as an owned list, elements cloned and every attribute copied @@ -998,8 +959,8 @@ const _: () = { pub fn direct_vector_len(graphic: &Graphic) -> usize { match graphic { Graphic::Vector(list) => list.len(), - Graphic::Group(group) => match (&group.row, &group.content) { - (None, core_types::record::GroupContent::Run(item)) => item.typed_lanes::().map_or(0, |lanes| lanes.len()), + Graphic::Group(group) => match &group.row { + None => group.content.typed_lanes::().map_or(0, |lanes| lanes.len()), _ => 0, }, _ => 0, @@ -1025,9 +986,8 @@ pub fn map_groups_to_legacy(graphic: &Graphic) -> Graphic { /// run keeps the run's typed variant, matching the `Into` the /// pre-flip wrap applied; everything else becomes the legacy group list. pub fn group_to_legacy_graphic(group: &core_types::record::Group) -> Graphic { - if group.row.is_none() - && let core_types::record::GroupContent::Run(item) = &group.content - { + if group.row.is_none() { + let item = &group.content; let typed = None .or_else(|| run_to_legacy_list::(item).map(Graphic::Vector)) .or_else(|| run_to_legacy_list::>(item).map(Graphic::RasterCPU)) @@ -1043,49 +1003,26 @@ pub fn group_to_legacy_graphic(group: &core_types::record::Group) -> Graphic { } /// The group as a legacy `List`: a `Graphic` run becomes the items, -/// another typed run becomes one item holding its typed list, and stack -/// segments become one item each with the segment's row attributes. +/// another typed run becomes one item holding its typed list. pub fn group_to_legacy_list(group: &core_types::record::Group) -> List { - match &group.content { - core_types::record::GroupContent::Run(item) => { - if let Some(mut list) = run_to_legacy_list::(item) { - for element in list.iter_element_values_mut() { - *element = map_groups_to_legacy(element); - } - push_lane_paint_into_interiors(&mut list); - return list; - } - let element = None - .or_else(|| run_to_legacy_list::(item).map(Graphic::Vector)) - .or_else(|| run_to_legacy_list::>(item).map(Graphic::RasterCPU)) - .or_else(|| run_to_legacy_list::>(item).map(Graphic::RasterGPU)) - .or_else(|| run_to_legacy_list::(item).map(Graphic::Color)) - .or_else(|| run_to_legacy_list::(item).map(Graphic::Gradient)) - .or_else(|| run_to_legacy_list::(item).map(Graphic::Text)); - match element { - Some(element) => List::new_from_element(element), - None => List::new(), - } - } - core_types::record::GroupContent::Stack(children) => { - let mut list = List::new(); - for child in children { - list.push(Item::new_from_element(group_to_legacy_graphic(child))); - let index = list.len() - 1; - if let Some(row) = &child.row { - if !row.is_empty() { - for field in &row.layout().fields { - // SAFETY: the offset comes from the row's own layout. - let value = unsafe { (field.read_erased)(row.lanes().get(0).rec().ptr().add(field.offset)) }; - list.set_attribute_value_dyn(field.name, index, AttributeValueDyn(value)); - } - } - } - } - map_paint_attrs_to_legacy(&mut list); - push_lane_paint_into_interiors(&mut list); - list + let item = &group.content; + if let Some(mut list) = run_to_legacy_list::(item) { + for element in list.iter_element_values_mut() { + *element = map_groups_to_legacy(element); } + push_lane_paint_into_interiors(&mut list); + return list; + } + let element = None + .or_else(|| run_to_legacy_list::(item).map(Graphic::Vector)) + .or_else(|| run_to_legacy_list::>(item).map(Graphic::RasterCPU)) + .or_else(|| run_to_legacy_list::>(item).map(Graphic::RasterGPU)) + .or_else(|| run_to_legacy_list::(item).map(Graphic::Color)) + .or_else(|| run_to_legacy_list::(item).map(Graphic::Gradient)) + .or_else(|| run_to_legacy_list::(item).map(Graphic::Text)); + match element { + Some(element) => List::new_from_element(element), + None => List::new(), } } @@ -1094,18 +1031,15 @@ fn group_render_complexity(group: &core_types::record::Group) -> usize { let lanes = item.typed_lanes::()?; Some((0..lanes.len()).map(|lane| lanes.element_ref(lane).render_complexity()).sum()) } - match &group.content { - core_types::record::GroupContent::Run(item) => None - .or_else(|| typed_run::(item)) - .or_else(|| typed_run::(item)) - .or_else(|| typed_run::>(item)) - .or_else(|| typed_run::>(item)) - .or_else(|| typed_run::(item)) - .or_else(|| typed_run::(item)) - .or_else(|| typed_run::(item)) - .unwrap_or(item.len()), - core_types::record::GroupContent::Stack(children) => children.iter().map(group_render_complexity).sum(), - } + let item = &group.content; + None.or_else(|| typed_run::(item)) + .or_else(|| typed_run::(item)) + .or_else(|| typed_run::>(item)) + .or_else(|| typed_run::>(item)) + .or_else(|| typed_run::(item)) + .or_else(|| typed_run::(item)) + .or_else(|| typed_run::(item)) + .unwrap_or(item.len()) } impl BoundingBox for Graphic { @@ -1333,7 +1267,7 @@ mod run_tests { let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; let group = core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(item), + content: item, }; let expected = group_to_legacy_list(&group); (map_groups_to_owned(&Graphic::Group(group)), expected) @@ -1358,7 +1292,7 @@ mod run_tests { let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) }; let nested = Graphic::Group(core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(inner_item), + content: inner_item, }); let outer_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); @@ -1371,7 +1305,7 @@ mod run_tests { let outer_item = unsafe { GroupItem::from_resident(RecordBatch::new(outer_bytes.as_ptr(), 1, &outer_layout)) }; let group = core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(outer_item), + content: outer_item, }; let expected = group_to_legacy_list(&group); (map_groups_to_owned(&Graphic::Group(group)), expected) @@ -1393,7 +1327,7 @@ mod run_tests { let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, inner_layout)) }; List::new_from_element(Graphic::Group(core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(inner_item), + content: inner_item, })) } diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index df4c97be79..4d72160650 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -12,7 +12,7 @@ use core_types::color::Color; use core_types::color::SRGBA8; use core_types::lane::LaneSource; use core_types::list::{Item, List}; -use core_types::record::{Group, GroupContent, RunView}; +use core_types::record::{Group, RunView}; use core_types::math::quad::Quad; use core_types::render_complexity::RenderComplexity; use core_types::transform::Footprint; @@ -22,7 +22,7 @@ use dyn_any::DynAny; use glam::{DAffine2, DMat2, DVec2}; use graphene_hash::CacheHashWrapper; use graphene_resource::Resource; -use graphic_types::graphic::{LanePaint, PaintColumns, PaintOverlay, group_to_legacy_list, has_paint, is_paint_present, paint_graphics, set_paint_attribute, vector_can_reduce_to_clip_path}; +use graphic_types::graphic::{LanePaint, PaintColumns, PaintOverlay, has_paint, is_paint_present, paint_graphics, set_paint_attribute, vector_can_reduce_to_clip_path}; use graphic_types::markers::{EditorMergedLayers, Fill, Stroke}; use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture}; use graphic_types::vector_types::gradient::{GradientStops, GradientType}; @@ -679,13 +679,10 @@ fn render_element_vello<'a>(element: &'a Graphic, reach: PaintReach<'a>, scene: fn element_can_reduce_to_clip_path<'a>(element: &'a Graphic, reach: PaintReach<'a>) -> bool { match element { Graphic::Vector(inner) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(inner, reach.paint)), - Graphic::Group(group) => match &group.content { - GroupContent::Run(item) => match RunView::::new(item) { - Some(run) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&run, reach.paint)), - Some(run) => vector_can_reduce_to_clip_path(&run), - None => false, - }, - GroupContent::Stack(_) => false, + Graphic::Group(group) => match RunView::::new(&group.content) { + Some(run) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&run, reach.paint)), + Some(run) => vector_can_reduce_to_clip_path(&run), + None => false, }, _ => element.can_reduce_to_clip_path(), } @@ -777,7 +774,7 @@ fn collect_group_row_metadata(group: &Group, metadata: &mut RenderMetadata, elem RunView::::new(item).map(|run| run.attr::(0)) } - let GroupContent::Run(item) = &group.content else { return }; + let item = &group.content; if group.row.is_some() || item.is_empty() || item.typed_lanes::().is_some() { return; } @@ -822,13 +819,10 @@ fn add_element_upstream_outline_targets<'a>(element: &'a Graphic, reach: PaintRe } } -/// The native group render: a run dispatches on its element type into the -/// generic bodies. `Stack` has consumers but no constructor yet, so it and -/// unknown element types keep the legacy conversion. +/// The native group render: the run dispatches on its element type into the +/// generic bodies; an unknown element type renders as nothing. fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) { - let GroupContent::Run(item) = &group.content else { - return group_to_legacy_list(group).render_svg(render, render_params); - }; + let item = &group.content; if let Some(run) = RunView::::new(item) { render_graphic_svg_with(&run, reach.into_group_graphics(), render, render_params) } else if let Some(run) = RunView::::new(item) { @@ -845,15 +839,11 @@ fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut Sv render_gradient_svg(&run, render, render_params) } else if let Some(run) = RunView::::new(item) { render_text_svg(&run, render, render_params) - } else { - group_to_legacy_list(group).render_svg(render, render_params) } } fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { - let GroupContent::Run(item) = &group.content else { - return group_to_legacy_list(group).render_to_vello(scene, transform, context, render_params); - }; + let item = &group.content; if let Some(run) = RunView::::new(item) { render_graphic_vello_with(&run, reach.into_group_graphics(), scene, transform, context, render_params) } else if let Some(run) = RunView::::new(item) { @@ -871,8 +861,6 @@ fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut S render_gradient_vello(&run, scene, transform, render_params) } else if let Some(run) = RunView::::new(item) { render_text_vello(&run, scene, transform, render_params) - } else { - group_to_legacy_list(group).render_to_vello(scene, transform, context, render_params) } } @@ -880,9 +868,7 @@ fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut S /// typed variant the conversion produced, so a caller's element id passes /// through to the typed body unchanged. fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { - let GroupContent::Run(item) = &group.content else { - return group_to_legacy_list(group).collect_metadata(metadata, footprint, element_id); - }; + let item = &group.content; if let Some(run) = RunView::::new(item) { collect_graphic_metadata_with(&run, reach.into_group_graphics(), metadata, footprint, element_id) } else if let Some(run) = RunView::::new(item) { @@ -897,15 +883,11 @@ fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata: } else if item.typed_lanes::().is_some() || item.typed_lanes::().is_some() { } else if let Some(run) = RunView::::new(item) { collect_text_metadata(&run, metadata, footprint, element_id) - } else { - group_to_legacy_list(group).collect_metadata(metadata, footprint, element_id) } } fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>, click_targets: &mut Vec) { - let GroupContent::Run(item) = &group.content else { - return group_to_legacy_list(group).add_upstream_click_targets(click_targets); - }; + let item = &group.content; if let Some(run) = RunView::::new(item) { add_graphic_upstream_click_targets_with(&run, reach.into_group_graphics(), click_targets) } else if let Some(run) = RunView::::new(item) { @@ -917,16 +899,11 @@ fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>, add_raster_upstream_click_targets(click_targets) } else if let Some(run) = RunView::::new(item) { add_text_upstream_click_targets(&run, click_targets) - } else if item.typed_lanes::().is_some() || item.typed_lanes::().is_some() { - } else { - group_to_legacy_list(group).add_upstream_click_targets(click_targets) } } fn add_group_upstream_outline_targets<'a>(group: &'a Group, reach: PaintReach<'a>, outlines: &mut Vec) { - let GroupContent::Run(item) = &group.content else { - return group_to_legacy_list(group).add_upstream_outline_targets(outlines); - }; + let item = &group.content; if let Some(run) = RunView::::new(item) { add_graphic_upstream_outline_targets_with(&run, reach.into_group_graphics(), outlines) } else if let Some(run) = RunView::::new(item) { @@ -938,9 +915,6 @@ fn add_group_upstream_outline_targets<'a>(group: &'a Group, reach: PaintReach<'a add_raster_upstream_click_targets(outlines) } else if let Some(run) = RunView::::new(item) { add_text_upstream_click_targets(&run, outlines) - } else if item.typed_lanes::().is_some() || item.typed_lanes::().is_some() { - } else { - group_to_legacy_list(group).add_upstream_outline_targets(outlines) } } @@ -2966,6 +2940,92 @@ impl Render for List { } } +impl Render for RunView<'_, Graphic> { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { + render_graphic_svg(self, render, render_params) + } + + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { + render_graphic_vello(self, scene, transform, context, render_params) + } + + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + collect_graphic_metadata(self, metadata, footprint, element_id) + } + + fn add_upstream_click_targets(&self, click_targets: &mut Vec) { + add_graphic_upstream_click_targets(self, click_targets) + } + + fn add_upstream_outline_targets(&self, outlines: &mut Vec) { + add_graphic_upstream_outline_targets(self, outlines) + } + + fn contains_artboard(&self) -> bool { + graphic_contains_artboard(self) + } +} + +impl Render for RunView<'_, Vector> { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { + render_vector_svg(self, render, render_params) + } + + fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { + render_vector_vello(self, scene, parent_transform, context, render_params) + } + + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option) { + collect_vector_metadata(self, metadata, footprint, caller_element_id) + } + + fn add_upstream_click_targets(&self, click_targets: &mut Vec) { + add_vector_upstream_click_targets(self, click_targets) + } + + fn add_upstream_outline_targets(&self, outlines: &mut Vec) { + add_vector_upstream_outline_targets(self, outlines) + } +} + +impl Render for RunView<'_, Raster> { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { + render_raster_cpu_svg(self, render, render_params) + } + + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) { + render_raster_cpu_vello(self, scene, transform, render_params) + } + + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + collect_raster_metadata(self, metadata, footprint, element_id) + } + + fn add_upstream_click_targets(&self, click_targets: &mut Vec) { + add_raster_upstream_click_targets(click_targets) + } +} + +impl Render for RunView<'_, Color> { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { + render_color_svg(self, render, render_params) + } + + fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { + render_color_vello(self, scene, render_params) + } +} + +impl Render for RunView<'_, GradientStops> { + fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { + render_gradient_svg(self, render, render_params) + } + + fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { + render_gradient_vello(self, scene, parent_transform, render_params) + } +} + impl Render for RunView<'_, Artboard> { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { render_artboard_svg(self, render, render_params) @@ -3109,7 +3169,7 @@ mod group_walk_tests { let bytes = unsafe { write_lanes::(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; // SAFETY: `bytes` holds two lanes of `layout` at its stride. let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; - let group = Group { row: None, content: GroupContent::Run(item) }; + let group = Group { row: None, content: item }; let params = RenderParams::default(); let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, ¶ms)); @@ -3128,7 +3188,7 @@ mod group_walk_tests { let bytes = unsafe { write_lanes::(&layout, &[&inner], &[Some(&paint)]) }; // SAFETY: `bytes` holds one lane of `layout` at its stride. let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; - let group = Group { row: None, content: GroupContent::Run(item) }; + let group = Group { row: None, content: item }; let params = RenderParams::default(); let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, ¶ms)); @@ -3147,7 +3207,7 @@ mod group_walk_tests { let bytes = unsafe { write_lanes::(&layout, &[&vectors[0]], &[Some(&paint)]) }; // SAFETY: `bytes` holds one lane of `layout` at its stride. let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; - let group = Group { row: None, content: GroupContent::Run(item) }; + let group = Group { row: None, content: item }; let footprint = Footprint::default(); let caller = NodeId(9); @@ -3172,7 +3232,7 @@ mod group_walk_tests { let bytes = unsafe { write_lanes::(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; // SAFETY: `bytes` holds two lanes of `layout` at its stride. let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; - let group = Group { row: None, content: GroupContent::Run(item) }; + let group = Group { row: None, content: item }; let mut native = Vec::new(); Graphic::Group(group.clone()).add_upstream_click_targets(&mut native); diff --git a/node-graph/nodes/graphic/src/artboard.rs b/node-graph/nodes/graphic/src/artboard.rs index 5c7c8ad90b..0d3c83fce4 100644 --- a/node-graph/nodes/graphic/src/artboard.rs +++ b/node-graph/nodes/graphic/src/artboard.rs @@ -39,7 +39,7 @@ pub fn create_artboard( let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; let content = core_types::list::List::new_from_element(Graphic::Group(core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(item), + content: item, })); // Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 7ecc0c8b3a..3cc2f99998 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -462,7 +462,7 @@ pub fn wrap_graphic( let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; Ok(Graphic::Group(core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(item), + content: item, })) } @@ -532,7 +532,7 @@ pub fn to_graphic_typed bool { - match &group.content { - core_types::record::GroupContent::Run(item) => item.typed_lanes::().is_some(), - core_types::record::GroupContent::Stack(children) => children.iter().all(group_expands), - } + group.content.typed_lanes::().is_some() } pub(crate) fn group_leaf_count(group: &core_types::record::Group, fully_flatten: bool, depth: usize) -> usize { - match &group.content { - core_types::record::GroupContent::Run(item) => { - let lanes = item.typed_lanes::().expect("guarded by group_expands"); - (0..lanes.len()).map(|lane| leaf_count(lanes.element_ref(lane), fully_flatten, depth + 1)).sum() - } - core_types::record::GroupContent::Stack(children) => children.iter().map(|child| group_leaf_count(child, fully_flatten, depth)).sum(), - } + let lanes = group.content.typed_lanes::().expect("guarded by group_expands"); + (0..lanes.len()).map(|lane| leaf_count(lanes.element_ref(lane), fully_flatten, depth + 1)).sum() } pub(crate) fn group_locate(group: &core_types::record::Group, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic, DAffine2)> { - match &group.content { - core_types::record::GroupContent::Run(item) => { - let lanes = item.typed_lanes::().expect("guarded by group_expands"); - let offset = item.layout().offset_of(ATTR_TRANSFORM, 0); - (0..lanes.len()).find_map(|lane| { - // SAFETY: the offset comes from the item's own layout. - let lane_transform = offset.map(|offset| unsafe { item.lanes().get(lane).rec().read::(offset) }).unwrap_or(DAffine2::IDENTITY); - locate(lanes.element_ref(lane), transform * lane_transform, fully_flatten, depth + 1, remaining) - }) - } - core_types::record::GroupContent::Stack(children) => children - .iter() - .find_map(|child| group_locate(child, transform * graphic_types::graphic::group_row_transform(child), fully_flatten, depth, remaining)), - } + let item = &group.content; + let lanes = item.typed_lanes::().expect("guarded by group_expands"); + let offset = item.layout().offset_of(ATTR_TRANSFORM, 0); + (0..lanes.len()).find_map(|lane| { + // SAFETY: the offset comes from the item's own layout. + let lane_transform = offset.map(|offset| unsafe { item.lanes().get(lane).rec().read::(offset) }).unwrap_or(DAffine2::IDENTITY); + locate(lanes.element_ref(lane), transform * lane_transform, fully_flatten, depth + 1, remaining) + }) } /// Leaf rows a graphic expands to: its children's counts when the walk @@ -120,7 +106,7 @@ fn wrap(_: impl Ctx, content: IList) -> Result, Interrup let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; Ok(Graphic::Group(core_types::record::Group { row: None, - content: core_types::record::GroupContent::Run(item), + content: item, })) } @@ -728,9 +714,7 @@ mod tests { panic!("expected a group element"); }; assert!(group.row.is_none()); - let record::GroupContent::Run(item) = &group.content else { - panic!("expected a single run"); - }; + let item = &group.content; assert_eq!(item.len(), 2); let lanes = item.typed_lanes::().expect("the run holds the adopted graphic lanes"); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap(); @@ -770,9 +754,7 @@ mod tests { let Graphic::Group(group) = (unsafe { record::borrow_element::(record::Rec::new(slot.as_ptr())) }) else { panic!("the replay restores the group element"); }; - let record::GroupContent::Run(item) = &group.content else { - panic!("expected a single run"); - }; + let item = &group.content; assert_eq!(item.len(), 2); let lanes = item.typed_lanes::().expect("the run holds the adopted graphic lanes"); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap(); diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 6e917da751..32e451aeaa 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -76,25 +76,25 @@ fn render_intermediate( } /// The leveled form of `render_intermediate`: the wire's records materialize -/// into a run and render through the legacy conversion. +/// into a run, which renders directly. #[node_macro::node(category(""))] fn render_intermediate_leveled( ctx: impl Ctx + ExtractVarArgs + ExtractIndex + InjectIndex + Copy, #[implementations(Artboard, Graphic, Vector, Raster, Color, GradientStops, String)] data: IList, ) -> Result where - List: Render, + for<'a> core_types::record::RunView<'a, T>: Render, { // SAFETY: a materialized input's frames are arena-resident. let item = unsafe { core_types::record::GroupItem::from_resident(data.batch()) }; - let data = graphic_types::graphic::run_to_render_list::(&item).expect("the run holds the row's element type"); + let run = core_types::record::RunView::::new(&item).expect("the run holds the row's element type"); let render_params = ctx .vararg(0) .expect("Did not find var args") .downcast_ref::() .expect("Downcasting render params yielded invalid type"); - Ok(intermediate_of(&data, render_params)) + Ok(intermediate_of(&run, render_params)) } #[node_macro::node(category(""))]