@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
2929/// <item>
3030/// <description>
3131/// <see cref="FlushPreparedBatch{TPixel}(Configuration, ICanvasFrame{TPixel}, CompositionBatch)"/>
32- /// rasterizes one shared coverage map per batch and applies brushes in original command order.
32+ /// rasterizes shared coverage scanlines per batch and applies brushes in original command order.
3333/// </description>
3434/// </item>
3535/// </list>
@@ -177,15 +177,12 @@ internal void FlushPreparedBatch<TPixel>(
177177 }
178178
179179 CompositionCoverageDefinition definition = compositionBatch . Definition ;
180- using Buffer2D < float > coverageMap = this . CreateCoverageMap ( definition , configuration . MemoryAllocator ) ;
181-
182180 Rectangle destinationBounds = destinationFrame . Rectangle ;
183181 IReadOnlyList < PreparedCompositionCommand > commands = compositionBatch . Commands ;
184182 int commandCount = commands . Count ;
185183 BrushApplicator < TPixel > [ ] applicators = new BrushApplicator < TPixel > [ commandCount ] ;
186184 try
187185 {
188- int maxHeight = 0 ;
189186 for ( int i = 0 ; i < commandCount ; i ++ )
190187 {
191188 PreparedCompositionCommand command = commands [ i ] ;
@@ -195,17 +192,22 @@ internal void FlushPreparedBatch<TPixel>(
195192 command . GraphicsOptions ,
196193 commandRegion ,
197194 command . BrushBounds ) ;
198-
199- if ( command . DestinationRegion . Height > maxHeight )
200- {
201- maxHeight = command . DestinationRegion . Height ;
202- }
203195 }
204196
205- // Iterate by row so we slice the already-rasterized coverage map once per command row.
206- // We can do this in parallel since the applicators are thread-safe and each row is independent.
207- RowOperation < TPixel > operation = new ( coverageMap , commands , applicators , destinationBounds , maxHeight ) ;
208- ParallelRowIterator . IterateRows ( configuration , destinationBounds , in operation ) ;
197+ // Stream composition directly from rasterizer scanlines so we do not allocate
198+ // and then re-read an intermediate coverage map.
199+ RowOperation < TPixel > operation = new (
200+ commands ,
201+ applicators ,
202+ destinationBounds ,
203+ definition . RasterizerOptions . Interest . Top ) ;
204+ this . PrimaryRasterizer . Rasterize (
205+ definition . Path ,
206+ definition . RasterizerOptions ,
207+ configuration . MemoryAllocator ,
208+ ref operation ,
209+ static ( int y , Span < float > scanline , ref RowOperation < TPixel > callbackState ) =>
210+ callbackState . InvokeScanline ( y , scanline ) ) ;
209211 }
210212 finally
211213 {
@@ -216,80 +218,43 @@ internal void FlushPreparedBatch<TPixel>(
216218 }
217219 }
218220
219- /// <summary>
220- /// Rasterizes one batch coverage map into a dense floating-point buffer.
221- /// </summary>
222- /// <param name="definition">The path and rasterizer options shared by every command in the batch.</param>
223- /// <param name="allocator">The allocator used for temporary coverage storage.</param>
224- /// <returns>The populated coverage map for the batch interest region.</returns>
225- private Buffer2D < float > CreateCoverageMap (
226- in CompositionCoverageDefinition definition ,
227- MemoryAllocator allocator )
228- {
229- Size size = definition . RasterizerOptions . Interest . Size ;
230- Buffer2D < float > coverage = allocator . Allocate2D < float > ( size , AllocationOptions . Clean ) ;
231-
232- ( Buffer2D < float > Buffer , int DestinationTop ) state = ( coverage , definition . RasterizerOptions . Interest . Top ) ;
233- this . PrimaryRasterizer . Rasterize (
234- definition . Path ,
235- definition . RasterizerOptions ,
236- allocator ,
237- ref state ,
238- static ( int y , Span < float > scanline , ref ( Buffer2D < float > Buffer , int DestinationTop ) callbackState ) =>
239- {
240- int row = y - callbackState . DestinationTop ;
241- scanline . CopyTo ( callbackState . Buffer . DangerousGetRowSpan ( row ) ) ;
242- } ) ;
243-
244- return coverage ;
245- }
246-
247- private readonly struct RowOperation < TPixel > : IRowOperation
221+ private readonly struct RowOperation < TPixel >
248222 where TPixel : unmanaged, IPixel < TPixel >
249223 {
250- private readonly Buffer2D < float > coverageMap ;
251224 private readonly IReadOnlyList < PreparedCompositionCommand > commands ;
252225 private readonly BrushApplicator < TPixel > [ ] applicators ;
253226 private readonly Rectangle destinationBounds ;
254- private readonly int maxHeight ;
227+ private readonly int coverageTop ;
255228
256229 public RowOperation (
257- Buffer2D < float > coverageMap ,
258230 IReadOnlyList < PreparedCompositionCommand > commands ,
259231 BrushApplicator < TPixel > [ ] applicators ,
260232 Rectangle destinationBounds ,
261- int maxHeight )
233+ int coverageTop )
262234 {
263- this . coverageMap = coverageMap ;
264235 this . commands = commands ;
265236 this . applicators = applicators ;
266237 this . destinationBounds = destinationBounds ;
267- this . maxHeight = maxHeight ;
238+ this . coverageTop = coverageTop ;
268239 }
269240
270- public void Invoke ( int y )
241+ public void InvokeScanline ( int y , Span < float > scanline )
271242 {
272- if ( y >= this . maxHeight )
273- {
274- return ;
275- }
276-
243+ int sourceY = y - this . coverageTop ;
277244 for ( int i = 0 ; i < this . commands . Count ; i ++ )
278245 {
279246 PreparedCompositionCommand command = this . commands [ i ] ;
280- if ( y >= command . DestinationRegion . Height )
247+ int commandY = sourceY - command . SourceOffset . Y ;
248+ if ( ( uint ) commandY >= ( uint ) command . DestinationRegion . Height )
281249 {
282250 continue ;
283251 }
284252
285253 int destinationX = this . destinationBounds . X + command . DestinationRegion . X ;
286- int destinationY = this . destinationBounds . Y + command . DestinationRegion . Y ;
254+ int destinationY = this . destinationBounds . Y + command . DestinationRegion . Y + commandY ;
287255 int sourceStartX = command . SourceOffset . X ;
288- int sourceStartY = command . SourceOffset . Y ;
289-
290- Span < float > rowCoverage = this . coverageMap . DangerousGetRowSpan ( sourceStartY + y ) ;
291- Span < float > rowSlice = rowCoverage . Slice ( sourceStartX , command . DestinationRegion . Width ) ;
292- ApplyCoverageSpans ( this . applicators [ i ] , rowSlice , destinationX , destinationY + y ) ;
256+ Span < float > rowSlice = scanline . Slice ( sourceStartX , command . DestinationRegion . Width ) ;
257+ ApplyCoverageSpans ( this . applicators [ i ] , rowSlice , destinationX , destinationY ) ;
293258 }
294259 }
295260
0 commit comments