Render root levels natively and remove the unconstructed stack form

This commit is contained in:
Dennis Kobert
2026-08-27 14:44:19 +00:00
parent 50911a6a25
commit 68a2fb2be3
9 changed files with 194 additions and 258 deletions

View File

@@ -495,7 +495,7 @@ impl NodeRuntime {
let thumbnail_renders = &mut self.thumbnail_renders; let thumbnail_renders = &mut self.thumbnail_renders;
let vector_modify = &mut self.vector_modify; let vector_modify = &mut self.vector_modify;
let result = self.executor.introspect_with(monitor_node_path, |layout, batch, _arena| { 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; let type_id = layout.element.type_id;
// Graphic run: thumbnail (text-aware bounds, since the `BoundingBox` trait can't lay out `Graphic::Text` content) // Graphic run: thumbnail (text-aware bounds, since the `BoundingBox` trait can't lay out `Graphic::Text` content)
if type_id == std::any::TypeId::of::<Graphic>() { if type_id == std::any::TypeId::of::<Graphic>() {
@@ -505,7 +505,7 @@ impl NodeRuntime {
let bounds = graphene_std::renderer::graphic_list_bounding_box(&RunView::<Graphic>::new(&item)?, DAffine2::IDENTITY); let bounds = graphene_std::renderer::graphic_list_bounding_box(&RunView::<Graphic>::new(&item)?, DAffine2::IDENTITY);
let group = Graphic::Group(Group { let group = Graphic::Group(Group {
row: None, row: None,
content: GroupContent::Run(item), content: item,
}); });
Self::render_thumbnail(thumbnail_renders, parent_network_node_id, &group, bounds, responses) Self::render_thumbnail(thumbnail_renders, parent_network_node_id, &group, bounds, responses)
} }

View File

@@ -2015,48 +2015,36 @@ impl<T: crate::bounds::BoundingBox + 'static> crate::bounds::BoundingBox for Run
} }
} }
/// The records a group stores: a single homogeneous run, or a list of /// Records nested inside one element: a single homogeneous run. The `row`
/// segments. /// holds the group's own attribute record. A group that sits on a lane leaves
#[derive(Clone, Debug)] /// it `None`, because that lane's record carries the attributes. The typed
pub enum GroupContent { /// segment stack returns when merge constructs segments.
Run(GroupItem),
Stack(Vec<Group>),
}
/// 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.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Group { pub struct Group {
pub row: Option<GroupItem>, pub row: Option<GroupItem>,
pub content: GroupContent, pub content: GroupItem,
} }
impl Group { impl Group {
/// The group deep-copied out of its evaluation, every run in owned form. /// The group deep-copied out of its evaluation, every run in owned form.
pub fn copy_out(&self) -> Group { 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 { Group {
row: self.row.as_ref().map(GroupItem::copy_out), 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 /// Re-parks an owned group's runs into `arena`; `None` reports arena
/// exhaustion. /// exhaustion.
pub fn replay(&self, arena: &crate::arena::Arena) -> Option<Group> { pub fn replay(&self, arena: &crate::arena::Arena) -> Option<Group> {
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::<Option<_>>()?),
};
let row = match &self.row { let row = match &self.row {
Some(row) => Some(row.replay(arena)?), Some(row) => Some(row.replay(arena)?),
None => None, 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<H: core::hash::Hasher>(&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 { impl PartialEq for Group {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.row == other.row && self.content == other.content self.row == other.row && self.content == other.content

View File

@@ -10,7 +10,7 @@ use core_types::arena::Arena;
use core_types::context::InjectIndex; use core_types::context::InjectIndex;
use core_types::gpoll::{Finality, GraphError}; use core_types::gpoll::{Finality, GraphError};
use core_types::node::Node; 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 core_types::uuid::NodeId;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use vector_types::GradientStops; use vector_types::GradientStops;
@@ -36,7 +36,7 @@ where
LevelGroup::Group( LevelGroup::Group(
Group { Group {
row: None, row: None,
content: GroupContent::Run(item), content: item,
}, },
finality, finality,
) )
@@ -82,7 +82,7 @@ pub fn batch_to_legacy(layout: &core_types::record::Layout, batch: core_types::n
if item.typed_lanes::<Graphic>().is_some() { if item.typed_lanes::<Graphic>().is_some() {
let group = Group { let group = Group {
row: None, row: None,
content: GroupContent::Run(item), content: item,
}; };
return Some(Box::new(group_to_legacy_list(&group))); return Some(Box::new(group_to_legacy_list(&group)));
} }

View File

@@ -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 { pub fn group_is_empty(group: &core_types::record::Group) -> bool {
match &group.content { group.content.is_empty()
core_types::record::GroupContent::Run(item) => item.is_empty(),
core_types::record::GroupContent::Stack(children) => children.iter().all(group_is_empty),
}
} }
fn group_all_clipped(group: &core_types::record::Group) -> bool { fn group_all_clipped(group: &core_types::record::Group) -> bool {
match &group.content { let item = &group.content;
core_types::record::GroupContent::Run(item) => { let attrs = RunAttrs::of(item);
let attrs = RunAttrs::of(item); (0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.clipping_mask, lane, false))
(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),
}
} }
fn group_is_opaque(group: &core_types::record::Group) -> bool { fn group_is_opaque(group: &core_types::record::Group) -> bool {
match &group.content { let item = &group.content;
core_types::record::GroupContent::Run(item) => { let attrs = RunAttrs::of(item);
let attrs = RunAttrs::of(item); let lanes = item.typed_lanes::<Graphic>();
let lanes = item.typed_lanes::<Graphic>(); !item.is_empty()
!item.is_empty() && (0..item.len()).all(|lane| {
&& (0..item.len()).all(|lane| { RunAttrs::read_or(item, attrs.opacity, lane, 1.) >= 1.
RunAttrs::read_or(item, attrs.opacity, lane, 1.) >= 1. && RunAttrs::read_or(item, attrs.opacity_fill, 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())
&& 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),
}
} }
fn group_is_fully_transparent(group: &core_types::record::Group) -> bool { fn group_is_fully_transparent(group: &core_types::record::Group) -> bool {
match &group.content { let item = &group.content;
core_types::record::GroupContent::Run(item) => { let attrs = RunAttrs::of(item);
let attrs = RunAttrs::of(item); let lanes = item.typed_lanes::<Graphic>();
let lanes = item.typed_lanes::<Graphic>(); (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()))
(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),
}
} }
fn group_bounding_box(group: &core_types::record::Group, transform: DAffine2, include_stroke: bool, thumbnail: bool) -> RenderBoundingBox { 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::<String>(item, transform, include_stroke, thumbnail)) .or_else(|| typed_run::<String>(item, transform, include_stroke, thumbnail))
.unwrap_or(RenderBoundingBox::Infinite) .unwrap_or(RenderBoundingBox::Infinite)
} }
match &group.content { run_bounding_box(&group.content, transform, include_stroke, thumbnail)
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,
}
}
}
} }
/// One typed run as an owned list, elements cloned and every attribute copied /// 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 { pub fn direct_vector_len(graphic: &Graphic) -> usize {
match graphic { match graphic {
Graphic::Vector(list) => list.len(), Graphic::Vector(list) => list.len(),
Graphic::Group(group) => match (&group.row, &group.content) { Graphic::Group(group) => match &group.row {
(None, core_types::record::GroupContent::Run(item)) => item.typed_lanes::<Vector>().map_or(0, |lanes| lanes.len()), None => group.content.typed_lanes::<Vector>().map_or(0, |lanes| lanes.len()),
_ => 0, _ => 0,
}, },
_ => 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<Graphic>` the /// run keeps the run's typed variant, matching the `Into<Graphic>` the
/// pre-flip wrap applied; everything else becomes the legacy group list. /// pre-flip wrap applied; everything else becomes the legacy group list.
pub fn group_to_legacy_graphic(group: &core_types::record::Group) -> Graphic { pub fn group_to_legacy_graphic(group: &core_types::record::Group) -> Graphic {
if group.row.is_none() if group.row.is_none() {
&& let core_types::record::GroupContent::Run(item) = &group.content let item = &group.content;
{
let typed = None let typed = None
.or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector)) .or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector))
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(Graphic::RasterCPU)) .or_else(|| run_to_legacy_list::<Raster<CPU>>(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<Graphic>`: a `Graphic` run becomes the items, /// The group as a legacy `List<Graphic>`: a `Graphic` run becomes the items,
/// another typed run becomes one item holding its typed list, and stack /// another typed run becomes one item holding its typed list.
/// segments become one item each with the segment's row attributes.
pub fn group_to_legacy_list(group: &core_types::record::Group) -> List<Graphic> { pub fn group_to_legacy_list(group: &core_types::record::Group) -> List<Graphic> {
match &group.content { let item = &group.content;
core_types::record::GroupContent::Run(item) => { if let Some(mut list) = run_to_legacy_list::<Graphic>(item) {
if let Some(mut list) = run_to_legacy_list::<Graphic>(item) { for element in list.iter_element_values_mut() {
for element in list.iter_element_values_mut() { *element = map_groups_to_legacy(element);
*element = map_groups_to_legacy(element);
}
push_lane_paint_into_interiors(&mut list);
return list;
}
let element = None
.or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector))
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(Graphic::RasterCPU))
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(Graphic::RasterGPU))
.or_else(|| run_to_legacy_list::<Color>(item).map(Graphic::Color))
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(Graphic::Gradient))
.or_else(|| run_to_legacy_list::<String>(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
} }
push_lane_paint_into_interiors(&mut list);
return list;
}
let element = None
.or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector))
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(Graphic::RasterCPU))
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(Graphic::RasterGPU))
.or_else(|| run_to_legacy_list::<Color>(item).map(Graphic::Color))
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(Graphic::Gradient))
.or_else(|| run_to_legacy_list::<String>(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::<T>()?; let lanes = item.typed_lanes::<T>()?;
Some((0..lanes.len()).map(|lane| lanes.element_ref(lane).render_complexity()).sum()) Some((0..lanes.len()).map(|lane| lanes.element_ref(lane).render_complexity()).sum())
} }
match &group.content { let item = &group.content;
core_types::record::GroupContent::Run(item) => None None.or_else(|| typed_run::<Graphic>(item))
.or_else(|| typed_run::<Graphic>(item)) .or_else(|| typed_run::<Vector>(item))
.or_else(|| typed_run::<Vector>(item)) .or_else(|| typed_run::<Raster<CPU>>(item))
.or_else(|| typed_run::<Raster<CPU>>(item)) .or_else(|| typed_run::<Raster<GPU>>(item))
.or_else(|| typed_run::<Raster<GPU>>(item)) .or_else(|| typed_run::<Color>(item))
.or_else(|| typed_run::<Color>(item)) .or_else(|| typed_run::<GradientStops>(item))
.or_else(|| typed_run::<GradientStops>(item)) .or_else(|| typed_run::<String>(item))
.or_else(|| typed_run::<String>(item)) .unwrap_or(item.len())
.unwrap_or(item.len()),
core_types::record::GroupContent::Stack(children) => children.iter().map(group_render_complexity).sum(),
}
} }
impl BoundingBox for Graphic { 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 item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) };
let group = core_types::record::Group { let group = core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(item), content: item,
}; };
let expected = group_to_legacy_list(&group); let expected = group_to_legacy_list(&group);
(map_groups_to_owned(&Graphic::Group(group)), expected) (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 inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
let nested = Graphic::Group(core_types::record::Group { let nested = Graphic::Group(core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(inner_item), content: inner_item,
}); });
let outer_layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[]); let outer_layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[]);
@@ -1371,7 +1305,7 @@ mod run_tests {
let outer_item = unsafe { GroupItem::from_resident(RecordBatch::new(outer_bytes.as_ptr(), 1, &outer_layout)) }; let outer_item = unsafe { GroupItem::from_resident(RecordBatch::new(outer_bytes.as_ptr(), 1, &outer_layout)) };
let group = core_types::record::Group { let group = core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(outer_item), content: outer_item,
}; };
let expected = group_to_legacy_list(&group); let expected = group_to_legacy_list(&group);
(map_groups_to_owned(&Graphic::Group(group)), expected) (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)) }; 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 { List::new_from_element(Graphic::Group(core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(inner_item), content: inner_item,
})) }))
} }

View File

@@ -12,7 +12,7 @@ use core_types::color::Color;
use core_types::color::SRGBA8; use core_types::color::SRGBA8;
use core_types::lane::LaneSource; use core_types::lane::LaneSource;
use core_types::list::{Item, List}; 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::math::quad::Quad;
use core_types::render_complexity::RenderComplexity; use core_types::render_complexity::RenderComplexity;
use core_types::transform::Footprint; use core_types::transform::Footprint;
@@ -22,7 +22,7 @@ use dyn_any::DynAny;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
use graphene_hash::CacheHashWrapper; use graphene_hash::CacheHashWrapper;
use graphene_resource::Resource; 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::markers::{EditorMergedLayers, Fill, Stroke};
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture}; use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
use graphic_types::vector_types::gradient::{GradientStops, GradientType}; 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 { fn element_can_reduce_to_clip_path<'a>(element: &'a Graphic, reach: PaintReach<'a>) -> bool {
match element { match element {
Graphic::Vector(inner) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(inner, reach.paint)), Graphic::Vector(inner) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(inner, reach.paint)),
Graphic::Group(group) => match &group.content { Graphic::Group(group) => match RunView::<Vector>::new(&group.content) {
GroupContent::Run(item) => match RunView::<Vector>::new(item) { Some(run) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&run, reach.paint)),
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),
Some(run) => vector_can_reduce_to_clip_path(&run), None => false,
None => false,
},
GroupContent::Stack(_) => false,
}, },
_ => element.can_reduce_to_clip_path(), _ => element.can_reduce_to_clip_path(),
} }
@@ -777,7 +774,7 @@ fn collect_group_row_metadata(group: &Group, metadata: &mut RenderMetadata, elem
RunView::<T>::new(item).map(|run| run.attr::<Transform>(0)) RunView::<T>::new(item).map(|run| run.attr::<Transform>(0))
} }
let GroupContent::Run(item) = &group.content else { return }; let item = &group.content;
if group.row.is_some() || item.is_empty() || item.typed_lanes::<Graphic>().is_some() { if group.row.is_some() || item.is_empty() || item.typed_lanes::<Graphic>().is_some() {
return; 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 /// The native group render: the run dispatches on its element type into the
/// generic bodies. `Stack` has consumers but no constructor yet, so it and /// generic bodies; an unknown element type renders as nothing.
/// unknown element types keep the legacy conversion.
fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) { fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) {
let GroupContent::Run(item) = &group.content else { let item = &group.content;
return group_to_legacy_list(group).render_svg(render, render_params);
};
if let Some(run) = RunView::<Graphic>::new(item) { if let Some(run) = RunView::<Graphic>::new(item) {
render_graphic_svg_with(&run, reach.into_group_graphics(), render, render_params) render_graphic_svg_with(&run, reach.into_group_graphics(), render, render_params)
} else if let Some(run) = RunView::<Vector>::new(item) { } else if let Some(run) = RunView::<Vector>::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) render_gradient_svg(&run, render, render_params)
} else if let Some(run) = RunView::<String>::new(item) { } else if let Some(run) = RunView::<String>::new(item) {
render_text_svg(&run, render, render_params) 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) { 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 { let item = &group.content;
return group_to_legacy_list(group).render_to_vello(scene, transform, context, render_params);
};
if let Some(run) = RunView::<Graphic>::new(item) { if let Some(run) = RunView::<Graphic>::new(item) {
render_graphic_vello_with(&run, reach.into_group_graphics(), scene, transform, context, render_params) render_graphic_vello_with(&run, reach.into_group_graphics(), scene, transform, context, render_params)
} else if let Some(run) = RunView::<Vector>::new(item) { } else if let Some(run) = RunView::<Vector>::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) render_gradient_vello(&run, scene, transform, render_params)
} else if let Some(run) = RunView::<String>::new(item) { } else if let Some(run) = RunView::<String>::new(item) {
render_text_vello(&run, scene, transform, render_params) 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 /// typed variant the conversion produced, so a caller's element id passes
/// through to the typed body unchanged. /// 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<NodeId>) { fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let GroupContent::Run(item) = &group.content else { let item = &group.content;
return group_to_legacy_list(group).collect_metadata(metadata, footprint, element_id);
};
if let Some(run) = RunView::<Graphic>::new(item) { if let Some(run) = RunView::<Graphic>::new(item) {
collect_graphic_metadata_with(&run, reach.into_group_graphics(), metadata, footprint, element_id) collect_graphic_metadata_with(&run, reach.into_group_graphics(), metadata, footprint, element_id)
} else if let Some(run) = RunView::<Vector>::new(item) { } else if let Some(run) = RunView::<Vector>::new(item) {
@@ -897,15 +883,11 @@ fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata:
} else if item.typed_lanes::<Color>().is_some() || item.typed_lanes::<GradientStops>().is_some() { } else if item.typed_lanes::<Color>().is_some() || item.typed_lanes::<GradientStops>().is_some() {
} else if let Some(run) = RunView::<String>::new(item) { } else if let Some(run) = RunView::<String>::new(item) {
collect_text_metadata(&run, metadata, footprint, element_id) 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<ClickTarget>) { fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>, click_targets: &mut Vec<ClickTarget>) {
let GroupContent::Run(item) = &group.content else { let item = &group.content;
return group_to_legacy_list(group).add_upstream_click_targets(click_targets);
};
if let Some(run) = RunView::<Graphic>::new(item) { if let Some(run) = RunView::<Graphic>::new(item) {
add_graphic_upstream_click_targets_with(&run, reach.into_group_graphics(), click_targets) add_graphic_upstream_click_targets_with(&run, reach.into_group_graphics(), click_targets)
} else if let Some(run) = RunView::<Vector>::new(item) { } else if let Some(run) = RunView::<Vector>::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) add_raster_upstream_click_targets(click_targets)
} else if let Some(run) = RunView::<String>::new(item) { } else if let Some(run) = RunView::<String>::new(item) {
add_text_upstream_click_targets(&run, click_targets) add_text_upstream_click_targets(&run, click_targets)
} else if item.typed_lanes::<Color>().is_some() || item.typed_lanes::<GradientStops>().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<ClickTarget>) { fn add_group_upstream_outline_targets<'a>(group: &'a Group, reach: PaintReach<'a>, outlines: &mut Vec<ClickTarget>) {
let GroupContent::Run(item) = &group.content else { let item = &group.content;
return group_to_legacy_list(group).add_upstream_outline_targets(outlines);
};
if let Some(run) = RunView::<Graphic>::new(item) { if let Some(run) = RunView::<Graphic>::new(item) {
add_graphic_upstream_outline_targets_with(&run, reach.into_group_graphics(), outlines) add_graphic_upstream_outline_targets_with(&run, reach.into_group_graphics(), outlines)
} else if let Some(run) = RunView::<Vector>::new(item) { } else if let Some(run) = RunView::<Vector>::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) add_raster_upstream_click_targets(outlines)
} else if let Some(run) = RunView::<String>::new(item) { } else if let Some(run) = RunView::<String>::new(item) {
add_text_upstream_click_targets(&run, outlines) add_text_upstream_click_targets(&run, outlines)
} else if item.typed_lanes::<Color>().is_some() || item.typed_lanes::<GradientStops>().is_some() {
} else {
group_to_legacy_list(group).add_upstream_outline_targets(outlines)
} }
} }
@@ -2966,6 +2940,92 @@ impl Render for List<String> {
} }
} }
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<NodeId>) {
collect_graphic_metadata(self, metadata, footprint, element_id)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
add_graphic_upstream_click_targets(self, click_targets)
}
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
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<NodeId>) {
collect_vector_metadata(self, metadata, footprint, caller_element_id)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
add_vector_upstream_click_targets(self, click_targets)
}
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
add_vector_upstream_outline_targets(self, outlines)
}
}
impl Render for RunView<'_, Raster<CPU>> {
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<NodeId>) {
collect_raster_metadata(self, metadata, footprint, element_id)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
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> { impl Render for RunView<'_, Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
render_artboard_svg(self, render, render_params) render_artboard_svg(self, render, render_params)
@@ -3109,7 +3169,7 @@ mod group_walk_tests {
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) };
// SAFETY: `bytes` holds two lanes of `layout` at its stride. // SAFETY: `bytes` holds two lanes of `layout` at its stride.
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; 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 params = RenderParams::default();
let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, &params)); let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, &params));
@@ -3128,7 +3188,7 @@ mod group_walk_tests {
let bytes = unsafe { write_lanes::<Graphic>(&layout, &[&inner], &[Some(&paint)]) }; let bytes = unsafe { write_lanes::<Graphic>(&layout, &[&inner], &[Some(&paint)]) };
// SAFETY: `bytes` holds one lane of `layout` at its stride. // SAFETY: `bytes` holds one lane of `layout` at its stride.
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; 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 params = RenderParams::default();
let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, &params)); let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, &params));
@@ -3147,7 +3207,7 @@ mod group_walk_tests {
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0]], &[Some(&paint)]) }; let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0]], &[Some(&paint)]) };
// SAFETY: `bytes` holds one lane of `layout` at its stride. // SAFETY: `bytes` holds one lane of `layout` at its stride.
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; 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 footprint = Footprint::default();
let caller = NodeId(9); let caller = NodeId(9);
@@ -3172,7 +3232,7 @@ mod group_walk_tests {
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) };
// SAFETY: `bytes` holds two lanes of `layout` at its stride. // SAFETY: `bytes` holds two lanes of `layout` at its stride.
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; 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(); let mut native = Vec::new();
Graphic::Group(group.clone()).add_upstream_click_targets(&mut native); Graphic::Group(group.clone()).add_upstream_click_targets(&mut native);

View File

@@ -39,7 +39,7 @@ pub fn create_artboard(
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; 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 { let content = core_types::list::List::new_from_element(Graphic::Group(core_types::record::Group {
row: None, 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 // Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input

View File

@@ -462,7 +462,7 @@ pub fn wrap_graphic<T: Clone + Send + Sync + core_types::CacheHash + 'static>(
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
Ok(Graphic::Group(core_types::record::Group { Ok(Graphic::Group(core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(item), content: item,
})) }))
} }
@@ -532,7 +532,7 @@ pub fn to_graphic_typed<T: Clone + Send + Sync + core_types::CacheHash + 'static
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
Ok(Graphic::Group(core_types::record::Group { Ok(Graphic::Group(core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(item), content: item,
})) }))
} }

View File

@@ -13,40 +13,26 @@ use graphic_types::graphic::Graphic;
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
use vector_types::{GradientStop, GradientStops}; use vector_types::{GradientStop, GradientStops};
/// Whether the walk can descend into a group: every run holds `Graphic` /// Whether the walk can descend into a group: the run holds `Graphic`
/// elements. /// elements.
pub(crate) fn group_expands(group: &core_types::record::Group) -> bool { pub(crate) fn group_expands(group: &core_types::record::Group) -> bool {
match &group.content { group.content.typed_lanes::<Graphic>().is_some()
core_types::record::GroupContent::Run(item) => item.typed_lanes::<Graphic>().is_some(),
core_types::record::GroupContent::Stack(children) => children.iter().all(group_expands),
}
} }
pub(crate) fn group_leaf_count(group: &core_types::record::Group, fully_flatten: bool, depth: usize) -> usize { pub(crate) fn group_leaf_count(group: &core_types::record::Group, fully_flatten: bool, depth: usize) -> usize {
match &group.content { let lanes = group.content.typed_lanes::<Graphic>().expect("guarded by group_expands");
core_types::record::GroupContent::Run(item) => { (0..lanes.len()).map(|lane| leaf_count(lanes.element_ref(lane), fully_flatten, depth + 1)).sum()
let lanes = item.typed_lanes::<Graphic>().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(),
}
} }
pub(crate) fn group_locate(group: &core_types::record::Group, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic, DAffine2)> { 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 { let item = &group.content;
core_types::record::GroupContent::Run(item) => { let lanes = item.typed_lanes::<Graphic>().expect("guarded by group_expands");
let lanes = item.typed_lanes::<Graphic>().expect("guarded by group_expands"); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0);
let offset = item.layout().offset_of(ATTR_TRANSFORM, 0); (0..lanes.len()).find_map(|lane| {
(0..lanes.len()).find_map(|lane| { // SAFETY: the offset comes from the item's own layout.
// SAFETY: the offset comes from the item's own layout. let lane_transform = offset.map(|offset| unsafe { item.lanes().get(lane).rec().read::<DAffine2>(offset) }).unwrap_or(DAffine2::IDENTITY);
let lane_transform = offset.map(|offset| unsafe { item.lanes().get(lane).rec().read::<DAffine2>(offset) }).unwrap_or(DAffine2::IDENTITY); locate(lanes.element_ref(lane), transform * lane_transform, fully_flatten, depth + 1, remaining)
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)),
}
} }
/// Leaf rows a graphic expands to: its children's counts when the walk /// Leaf rows a graphic expands to: its children's counts when the walk
@@ -120,7 +106,7 @@ fn wrap(_: impl Ctx, content: IList<Graphic>) -> Result<IList<Graphic>, Interrup
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
Ok(Graphic::Group(core_types::record::Group { Ok(Graphic::Group(core_types::record::Group {
row: None, row: None,
content: core_types::record::GroupContent::Run(item), content: item,
})) }))
} }
@@ -728,9 +714,7 @@ mod tests {
panic!("expected a group element"); panic!("expected a group element");
}; };
assert!(group.row.is_none()); assert!(group.row.is_none());
let record::GroupContent::Run(item) = &group.content else { let item = &group.content;
panic!("expected a single run");
};
assert_eq!(item.len(), 2); assert_eq!(item.len(), 2);
let lanes = item.typed_lanes::<Graphic>().expect("the run holds the adopted graphic lanes"); let lanes = item.typed_lanes::<Graphic>().expect("the run holds the adopted graphic lanes");
let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap(); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap();
@@ -770,9 +754,7 @@ mod tests {
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(record::Rec::new(slot.as_ptr())) }) else { let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(record::Rec::new(slot.as_ptr())) }) else {
panic!("the replay restores the group element"); panic!("the replay restores the group element");
}; };
let record::GroupContent::Run(item) = &group.content else { let item = &group.content;
panic!("expected a single run");
};
assert_eq!(item.len(), 2); assert_eq!(item.len(), 2);
let lanes = item.typed_lanes::<Graphic>().expect("the run holds the adopted graphic lanes"); let lanes = item.typed_lanes::<Graphic>().expect("the run holds the adopted graphic lanes");
let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap(); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap();

View File

@@ -76,25 +76,25 @@ fn render_intermediate<T: 'static + Render + WasmNotSend + Send + Sync>(
} }
/// The leveled form of `render_intermediate`: the wire's records materialize /// 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(""))] #[node_macro::node(category(""))]
fn render_intermediate_leveled<T: Clone + Send + Sync + core_types::CacheHash + 'static>( fn render_intermediate_leveled<T: Clone + Send + Sync + core_types::CacheHash + 'static>(
ctx: impl Ctx + ExtractVarArgs + ExtractIndex + InjectIndex + Copy, ctx: impl Ctx + ExtractVarArgs + ExtractIndex + InjectIndex + Copy,
#[implementations(Artboard, Graphic, Vector, Raster<CPU>, Color, GradientStops, String)] data: IList<T>, #[implementations(Artboard, Graphic, Vector, Raster<CPU>, Color, GradientStops, String)] data: IList<T>,
) -> Result<RenderIntermediate, Interrupt> ) -> Result<RenderIntermediate, Interrupt>
where where
List<T>: Render, for<'a> core_types::record::RunView<'a, T>: Render,
{ {
// SAFETY: a materialized input's frames are arena-resident. // SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(data.batch()) }; let item = unsafe { core_types::record::GroupItem::from_resident(data.batch()) };
let data = graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type"); let run = core_types::record::RunView::<T>::new(&item).expect("the run holds the row's element type");
let render_params = ctx let render_params = ctx
.vararg(0) .vararg(0)
.expect("Did not find var args") .expect("Did not find var args")
.downcast_ref::<RenderParams>() .downcast_ref::<RenderParams>()
.expect("Downcasting render params yielded invalid type"); .expect("Downcasting render params yielded invalid type");
Ok(intermediate_of(&data, render_params)) Ok(intermediate_of(&run, render_params))
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]