@@ -257,6 +257,10 @@ public static FlushScene Create(
257257 int singleBandItemCount = 0 ;
258258 int smallEdgeItemCount = 0 ;
259259
260+ // Segment start offsets into the band assignment cache — one entry per visible item.
261+ // Used in Phases 4 and 5 to slice each item's portion of the cache.
262+ int [ ] itemSegmentStarts = new int [ visibleItemCount ] ;
263+
260264 // Phase 3: derive scene-wide maxima once so every worker can allocate one reusable scratch set.
261265 {
262266 Span < RasterizerOptions > roSpan = rasterizerOptionsBuffer . AsSpan ( ) ;
@@ -281,6 +285,7 @@ public static FlushScene Create(
281285 maxWidth = Math . Max ( maxWidth , width ) ;
282286 maxWordsPerRow = Math . Max ( maxWordsPerRow , BitVectorsForMaxBitCount ( width ) ) ;
283287 maxCoverStride = Math . Max ( maxCoverStride , ( int ) coverStride ) ;
288+ itemSegmentStarts [ i ] = ( int ) totalEdgeCount ;
284289 totalEdgeCount += mpSpan [ i ] . TotalSegmentCount ;
285290
286291 if ( bcSpan [ i ] == 1 )
@@ -295,6 +300,12 @@ public static FlushScene Create(
295300 }
296301 }
297302
303+ // Band assignment cache: stores the packed (firstBand | lastBand << 16) for each segment,
304+ // or -1 if the segment falls outside the item's interest rectangle.
305+ // Built during Phase 4 so Phase 5 can scatter from integers instead of re-enumerating path geometry.
306+ IMemoryOwner < int > bandAssignmentCacheOwner = allocator . Allocate < int > ( Math . Max ( ( int ) totalEdgeCount , 1 ) ) ;
307+ Memory < int > bandAssignmentCache = bandAssignmentCacheOwner . Memory ;
308+
298309 IMemoryOwner < int > itemBandOffsetStartsOwner = allocator . Allocate < int > ( visibleItemCount + 1 ) ;
299310 int totalBandOffsetCount = 0 ;
300311 {
@@ -312,7 +323,9 @@ public static FlushScene Create(
312323 IMemoryOwner < int > bandSegmentOffsetsOwner = allocator . Allocate < int > ( totalBandOffsetCount ) ;
313324 long totalBandSegmentRefs = 0 ;
314325
315- // Phase 4: count how many prepared segments each item contributes to each row band.
326+ // Phase 4: count how many prepared segments each item contributes to each row band,
327+ // and record the packed band assignment for each segment in the cache so Phase 5
328+ // can scatter from integers rather than re-enumerating path geometry.
316329 _ = Parallel . ForEach (
317330 Partitioner . Create ( 0 , visibleItemCount ) ,
318331 ( ) => 0L ,
@@ -323,15 +336,17 @@ public static FlushScene Create(
323336 Span < int > bcSpan = itemBandCounts . Span ;
324337 Span < int > offsetSpan = itemBandOffsetStartsOwner . Memory . Span ;
325338 Span < int > bsoSpan = bandSegmentOffsetsOwner . Memory . Span ;
339+ Span < int > bacSpan = bandAssignmentCache . Span ;
326340
327341 for ( int i = range . Item1 ; i < range . Item2 ; i ++ )
328342 {
329- localTotal += CountBandSegmentRefs (
343+ localTotal += CountAndStoreBandSegmentRefs (
330344 mpSpan [ i ] ,
331345 compactedCommands [ i ] . DestinationOffset . Y ,
332346 in roSpan [ i ] ,
333347 bcSpan [ i ] ,
334- bsoSpan . Slice ( offsetSpan [ i ] , bcSpan [ i ] ) ) ;
348+ bsoSpan . Slice ( offsetSpan [ i ] , bcSpan [ i ] ) ,
349+ bacSpan . Slice ( itemSegmentStarts [ i ] , mpSpan [ i ] . TotalSegmentCount ) ) ;
335350 }
336351
337352 return localTotal ;
@@ -365,18 +380,19 @@ public static FlushScene Create(
365380
366381 IMemoryOwner < int > bandSegmentIndicesOwner = allocator . Allocate < int > ( Math . Max ( runningSegmentOffset , 1 ) ) ;
367382
368- // Phase 5: materialize the dense per-band segment index lists using the offsets computed above.
383+ // Phase 5: scatter segment indices into the dense per-band lists using offsets from the prefix sum.
384+ // Reads the compact band assignment cache built in Phase 4 — no path geometry re-enumeration.
369385 _ = Parallel . ForEach (
370386 Partitioner . Create ( 0 , visibleItemCount ) ,
371387 ( ) => Array . Empty < int > ( ) ,
372388 ( range , _ , bandCursorBuffer ) =>
373389 {
374390 Span < MaterializedPath > mpSpan = pathBuffer . AsSpan ( ) ;
375- Span < RasterizerOptions > roSpan = rasterizerOptionsBuffer . AsSpan ( ) ;
376391 Span < int > bcSpan = itemBandCounts . Span ;
377392 Span < int > offsetSpan = itemBandOffsetStartsOwner . Memory . Span ;
378393 Span < int > bsoSpan = bandSegmentOffsetsOwner . Memory . Span ;
379394 Span < int > bsiSpan = bandSegmentIndicesOwner . Memory . Span ;
395+ Span < int > bacSpan = bandAssignmentCache . Span ;
380396
381397 for ( int i = range . Item1 ; i < range . Item2 ; i ++ )
382398 {
@@ -395,11 +411,8 @@ public static FlushScene Create(
395411 Span < int > bandCursors = bandCursorBuffer . AsSpan ( 0 , bandCount ) ;
396412 bandOffsets [ ..bandCount ] . CopyTo ( bandCursors ) ;
397413
398- FillBandSegmentRefs (
399- mpSpan [ i ] ,
400- compactedCommands [ i ] . DestinationOffset . Y ,
401- in roSpan [ i ] ,
402- bandCount ,
414+ FillBandSegmentRefsFromCache (
415+ bacSpan . Slice ( itemSegmentStarts [ i ] , mpSpan [ i ] . TotalSegmentCount ) ,
403416 bandCursors ,
404417 bsiSpan ) ;
405418 }
@@ -408,6 +421,8 @@ public static FlushScene Create(
408421 } ,
409422 static _ => { } ) ;
410423
424+ bandAssignmentCacheOwner . Dispose ( ) ;
425+
411426 int rowCount = ( maxBandIndex - minBandIndex ) + 1 ;
412427 int [ ] rowCounts = new int [ rowCount ] ;
413428 int totalRefs = 0 ;
@@ -719,16 +734,22 @@ private static FlushScene Empty()
719734 0 ) ;
720735
721736 /// <summary>
722- /// Counts how many references each band needs for one materialized path.
737+ /// Counts how many references each band needs for one materialized path and records the packed
738+ /// band assignment <c>(firstBand | lastBand << 16)</c> for each segment in <paramref name="bandAssignments"/>,
739+ /// or <c>-1</c> for segments that fall outside the item's interest rectangle.
740+ /// The cache is consumed by <see cref="FillBandSegmentRefsFromCache"/> in Phase 5, eliminating a
741+ /// second enumeration of path geometry.
723742 /// </summary>
724- private static int CountBandSegmentRefs (
743+ private static int CountAndStoreBandSegmentRefs (
725744 MaterializedPath path ,
726745 int translateY ,
727746 in RasterizerOptions options ,
728747 int bandCount ,
729- Span < int > bandCounts )
748+ Span < int > bandCounts ,
749+ Span < int > bandAssignments )
730750 {
731751 bandCounts . Clear ( ) ;
752+ bandAssignments . Fill ( - 1 ) ;
732753
733754 if ( bandCount <= 0 || path . TotalSegmentCount == 0 )
734755 {
@@ -742,10 +763,11 @@ private static int CountBandSegmentRefs(
742763 int bandTopStart = ( firstSceneBandIndex * rowHeight ) - interest . Top ;
743764 int totalCount = 0 ;
744765
766+ int segmentIndex = 0 ;
745767 MaterializedPath . SegmentEnumerator enumerator = path . GetSegmentEnumerator ( ) ;
746768 while ( enumerator . MoveNext ( ) )
747769 {
748- if ( ! TryGetLocalBandSpan (
770+ if ( TryGetLocalBandSpan (
749771 enumerator . CurrentMinY ,
750772 enumerator . CurrentMaxY ,
751773 translateY ,
@@ -758,68 +780,43 @@ private static int CountBandSegmentRefs(
758780 out int firstBandIndex ,
759781 out int lastBandIndex ) )
760782 {
761- continue ;
783+ bandAssignments [ segmentIndex ] = firstBandIndex | ( lastBandIndex << 16 ) ;
784+ for ( int bandIndex = firstBandIndex ; bandIndex <= lastBandIndex ; bandIndex ++ )
785+ {
786+ bandCounts [ bandIndex ] ++ ;
787+ totalCount ++ ;
788+ }
762789 }
763790
764- for ( int bandIndex = firstBandIndex ; bandIndex <= lastBandIndex ; bandIndex ++ )
765- {
766- bandCounts [ bandIndex ] ++ ;
767- totalCount ++ ;
768- }
791+ segmentIndex ++ ;
769792 }
770793
771794 return totalCount ;
772795 }
773796
774797 /// <summary>
775- /// Fills the dense per-band segment reference table for one materialized path.
798+ /// Scatters segment indices into the dense per-band reference table by reading the compact
799+ /// band assignment cache built during Phase 4. No path geometry is accessed.
776800 /// </summary>
777- private static void FillBandSegmentRefs (
778- MaterializedPath path ,
779- int translateY ,
780- in RasterizerOptions options ,
781- int bandCount ,
801+ private static void FillBandSegmentRefsFromCache (
802+ ReadOnlySpan < int > bandAssignments ,
782803 Span < int > bandCursors ,
783804 Span < int > bandSegmentIndices )
784805 {
785- if ( bandCount <= 0 || path . TotalSegmentCount == 0 )
786- {
787- return ;
788- }
789-
790- Rectangle interest = options . Interest ;
791- float samplingOffsetY = options . SamplingOrigin == RasterizerSamplingOrigin . PixelCenter ? 0.5F : 0F ;
792- int rowHeight = DefaultRasterizer . PreferredRowHeight ;
793- int firstSceneBandIndex = FloorDiv ( interest . Top , rowHeight ) ;
794- int bandTopStart = ( firstSceneBandIndex * rowHeight ) - interest . Top ;
795-
796- int segmentIndex = 0 ;
797- MaterializedPath . SegmentEnumerator enumerator = path . GetSegmentEnumerator ( ) ;
798- while ( enumerator . MoveNext ( ) )
806+ for ( int segmentIndex = 0 ; segmentIndex < bandAssignments . Length ; segmentIndex ++ )
799807 {
800- if ( ! TryGetLocalBandSpan (
801- enumerator . CurrentMinY ,
802- enumerator . CurrentMaxY ,
803- translateY ,
804- interest . Top ,
805- interest . Height ,
806- samplingOffsetY ,
807- bandTopStart ,
808- rowHeight ,
809- bandCount ,
810- out int firstBandIndex ,
811- out int lastBandIndex ) )
808+ int packed = bandAssignments [ segmentIndex ] ;
809+ if ( packed < 0 )
812810 {
813- segmentIndex ++ ;
814811 continue ;
815812 }
816813
814+ int firstBandIndex = packed & 0xFFFF ;
815+ int lastBandIndex = ( packed >> 16 ) & 0xFFFF ;
817816 for ( int bandIndex = firstBandIndex ; bandIndex <= lastBandIndex ; bandIndex ++ )
818817 {
819818 bandSegmentIndices [ bandCursors [ bandIndex ] ++ ] = segmentIndex ;
820819 }
821-
822- segmentIndex ++ ;
823820 }
824821 }
825822
0 commit comments