From 084f14e973cab002901e3957a1aa83bf57dcafeb Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 26 Aug 2026 23:03:17 +0000 Subject: [PATCH] Resolve paint and transform columns once per lane loop --- node-graph/libraries/core-types/src/bounds.rs | 8 +++-- .../libraries/graphic-types/src/graphic.rs | 28 +++++++++++++---- .../libraries/rendering/src/renderer.rs | 30 +++++++++---------- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/node-graph/libraries/core-types/src/bounds.rs b/node-graph/libraries/core-types/src/bounds.rs index e0efbec699..517347c1e9 100644 --- a/node-graph/libraries/core-types/src/bounds.rs +++ b/node-graph/libraries/core-types/src/bounds.rs @@ -1,5 +1,5 @@ use crate::Color; -use crate::lane::LaneSource; +use crate::lane::{LaneColumn, LaneSource}; use glam::{DAffine2, DVec2}; #[derive(Clone, Copy, Default, Debug, PartialEq)] @@ -61,9 +61,10 @@ where { let mut combined_bounds = None; + let transforms = source.column::(); for lane in 0..source.lane_count() { let Some(element) = source.element(lane) else { continue }; - let lane_transform: DAffine2 = source.attr::(lane); + let lane_transform: DAffine2 = transforms.get(lane); match element.bounding_box(transform * lane_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, @@ -90,9 +91,10 @@ where let mut combined_bounds = None; let mut any_infinite = false; + let transforms = source.column::(); for lane in 0..source.lane_count() { let Some(element) = source.element(lane) else { continue }; - let lane_transform: DAffine2 = source.attr::(lane); + let lane_transform: DAffine2 = transforms.get(lane); match element.thumbnail_bounding_box(transform * lane_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => any_infinite = true, diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index f9bcf4e998..5c1864b39b 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -248,16 +248,32 @@ pub struct LanePaint<'a> { impl<'a> LanePaint<'a> { pub const NONE: Self = Self { fill: None, stroke: None }; - /// The lane's present, non-blank paint. - pub fn read(source: &'a S, index: usize) -> Self { + pub fn is_present(&self) -> bool { + self.fill.is_some() || self.stroke.is_some() + } +} + +/// A source's fill and stroke columns, resolved once for per-lane reads. +pub struct PaintColumns<'a, S: LaneSource + 'a> { + fill: S::Column<'a, Fill>, + stroke: S::Column<'a, Stroke>, +} + +impl<'a, S: LaneSource> PaintColumns<'a, S> { + pub fn new(source: &'a S) -> Self { Self { - fill: paint_graphics::(source, index), - stroke: paint_graphics::(source, index), + fill: source.column::(), + stroke: source.column::(), } } - pub fn is_present(&self) -> bool { - self.fill.is_some() || self.stroke.is_some() + /// The lane's present, non-blank paint. + pub fn read(&self, lane: usize) -> LanePaint<'a> { + let present = |value: Option>>| value.flatten().filter(|list| is_paint_present(list)); + LanePaint { + fill: present(self.fill.try_get(lane)), + stroke: present(self.stroke.try_get(lane)), + } } } diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index a6d5fffd31..ed658d0d81 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -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, 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, group_to_legacy_list, 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}; @@ -626,20 +626,13 @@ struct PaintReach<'a> { impl<'a> PaintReach<'a> { const NONE: Self = Self { paint: LanePaint::NONE, hops: 0 }; - fn read(source: &'a S, index: usize) -> Self { - Self { - paint: LanePaint::read(source, index), - hops: 2, - } - } - /// The lane's effective reach: an inherited paint stays authoritative /// (lane paint below a push's origin is inert in the legacy model), an /// absent one reads the lane's own paint. - fn for_lane(self, source: &'a S, index: usize) -> Self { + fn for_lane(self, columns: &PaintColumns<'a, S>, index: usize) -> Self { match self.paint.is_present() { true => self, - false => Self::read(source, index), + false => Self { paint: columns.read(index), hops: 2 }, } } @@ -1109,6 +1102,7 @@ fn render_graphic_svg>(source: &S, render: &mut } fn render_graphic_svg_with<'a, S: LaneSource>(source: &'a S, inherited: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) { + let paint_columns = PaintColumns::new(source); let mut mask_state = None; for index in 0..source.lane_count() { @@ -1117,7 +1111,7 @@ fn render_graphic_svg_with<'a, S: LaneSource>(source: &'a S, let opacity_attr: f64 = source.attr::(index); let opacity_fill_attr: f64 = source.attr::(index); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); render.parent_tag( "g", @@ -1170,6 +1164,7 @@ fn render_graphic_vello>(source: &S, scene: &mu } fn render_graphic_vello_with<'a, S: LaneSource>(source: &'a S, inherited: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { + let paint_columns = PaintColumns::new(source); let mut mask_element_and_transform = None; for index in 0..source.lane_count() { @@ -1179,7 +1174,7 @@ fn render_graphic_vello_with<'a, S: LaneSource>(source: &'a S let opacity_attr: f64 = source.attr::(index); let opacity_fill_attr: f64 = source.attr::(index); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); let mut layer = false; @@ -1253,12 +1248,13 @@ fn collect_graphic_metadata>(source: &S, metada } fn collect_graphic_metadata_with<'a, S: LaneSource>(source: &'a S, inherited: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + let paint_columns = PaintColumns::new(source); for index in 0..source.lane_count() { let item_transform: DAffine2 = source.attr::(index); let layer_path: &[NodeId] = source.attr::(index); let layer = layer_path.last().copied(); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); let mut footprint = footprint; footprint.transform *= item_transform; @@ -1278,7 +1274,7 @@ fn collect_graphic_metadata_with<'a, S: LaneSource>(source: & for index in 0..source.lane_count() { let item_transform: DAffine2 = source.attr::(index); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); let mut new_click_targets = Vec::new(); add_element_upstream_click_targets(element, reach, &mut new_click_targets); @@ -1307,10 +1303,11 @@ fn add_graphic_upstream_click_targets>(source: } fn add_graphic_upstream_click_targets_with<'a, S: LaneSource>(source: &'a S, inherited: PaintReach<'a>, click_targets: &mut Vec) { + let paint_columns = PaintColumns::new(source); for index in 0..source.lane_count() { let item_transform: DAffine2 = source.attr::(index); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); let mut new_click_targets = Vec::new(); add_element_upstream_click_targets(element, reach, &mut new_click_targets); @@ -1328,10 +1325,11 @@ fn add_graphic_upstream_outline_targets>(source } fn add_graphic_upstream_outline_targets_with<'a, S: LaneSource>(source: &'a S, inherited: PaintReach<'a>, outlines: &mut Vec) { + let paint_columns = PaintColumns::new(source); for index in 0..source.lane_count() { let item_transform: DAffine2 = source.attr::(index); let element = source.element(index).unwrap(); - let reach = inherited.for_lane(source, index); + let reach = inherited.for_lane(&paint_columns, index); let mut new_outlines = Vec::new(); add_element_upstream_outline_targets(element, reach, &mut new_outlines);