Skip to content

Commit 6f97c13

Browse files
Buffer contiguous spans to batch row callbacks
1 parent 44fa989 commit 6f97c13

1 file changed

Lines changed: 71 additions & 19 deletions

File tree

src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.cs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ private readonly void EmitRowCoverage<TRowHandler>(
536536
int spanStart = 0;
537537
int spanEnd = 0;
538538
float spanCoverage = 0F;
539+
int runStart = -1;
540+
int runEnd = -1;
539541
int minWord = minTouchedColumn / WordBitCount;
540542
int maxWord = maxTouchedColumn / WordBitCount;
541543

@@ -565,7 +567,12 @@ private readonly void EmitRowCoverage<TRowHandler>(
565567
{
566568
if (coverage <= 0F)
567569
{
568-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
570+
// Zero coverage is a hard break. Everything buffered so far belongs
571+
// to the contiguous non-zero region immediately before x, and the
572+
// current pixel is outside that region. Flush now so a later non-zero
573+
// span cannot be merged across this hole into the same row callback.
574+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
575+
FlushBufferedRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
569576
spanStart = x + 1;
570577
spanEnd = spanStart;
571578
spanCoverage = 0F;
@@ -576,7 +583,7 @@ private readonly void EmitRowCoverage<TRowHandler>(
576583
}
577584
else
578585
{
579-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
586+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
580587
spanStart = x;
581588
spanEnd = x + 1;
582589
spanCoverage = coverage;
@@ -588,7 +595,11 @@ private readonly void EmitRowCoverage<TRowHandler>(
588595
// non-zero coverage and must be emitted as its own run.
589596
if (cover == 0)
590597
{
591-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
598+
// A zero-coverage gap is the same kind of hard break as a zero
599+
// coverage cell above: the buffered run must end before the gap so
600+
// the next visible span starts a new contiguous non-zero interval.
601+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
602+
FlushBufferedRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
592603
spanStart = x;
593604
spanEnd = x + 1;
594605
spanCoverage = coverage;
@@ -599,8 +610,11 @@ private readonly void EmitRowCoverage<TRowHandler>(
599610
if (gapCoverage <= 0F)
600611
{
601612
// Even-odd can map non-zero winding to zero coverage.
602-
// Treat this as a hard run break so we don't bridge holes.
603-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
613+
// Treat this as a hard run break so we don't bridge across a
614+
// zero-alpha hole and emit one callback for what is really two
615+
// separate visible regions.
616+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
617+
FlushBufferedRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
604618
spanStart = x;
605619
spanEnd = x + 1;
606620
spanCoverage = coverage;
@@ -613,16 +627,16 @@ private readonly void EmitRowCoverage<TRowHandler>(
613627
}
614628
else
615629
{
616-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, x, spanCoverage);
630+
BufferSpan(scanline, spanStart, x, spanCoverage, ref runStart, ref runEnd);
617631
spanStart = x;
618632
spanEnd = x + 1;
619633
spanCoverage = coverage;
620634
}
621635
}
622636
else
623637
{
624-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
625-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanEnd, x, gapCoverage);
638+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
639+
BufferSpan(scanline, spanEnd, x, gapCoverage, ref runStart, ref runEnd);
626640
spanStart = x;
627641
spanEnd = x + 1;
628642
spanCoverage = coverage;
@@ -634,12 +648,18 @@ private readonly void EmitRowCoverage<TRowHandler>(
634648
}
635649
}
636650

637-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
651+
BufferSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
638652

639653
if (cover != 0 && spanEnd < this.width)
640654
{
641-
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift));
655+
BufferSpan(scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift), ref runStart, ref runEnd);
642656
}
657+
658+
// At this point the buffered run, if any, represents one contiguous destination-space
659+
// interval whose pixels all have non-zero coverage. Emitting that interval in one
660+
// callback preserves the exact per-pixel coverage values already written into the
661+
// scratch scanline while avoiding a stream of tiny span callbacks.
662+
FlushBufferedRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
643663
}
644664

645665
/// <summary>
@@ -687,27 +707,59 @@ private readonly float AreaToCoverage(int area)
687707
}
688708

689709
/// <summary>
690-
/// Emits one constant-coverage span directly to the row handler.
710+
/// Buffers one non-zero span into the current contiguous row run.
691711
/// </summary>
692712
[MethodImpl(MethodImplOptions.AggressiveInlining)]
693-
private static void EmitSpan<TRowHandler>(
713+
private static void BufferSpan(
714+
Span<float> scanline,
715+
int start,
716+
int end,
717+
float coverage,
718+
ref int runStart,
719+
ref int runEnd)
720+
{
721+
if (coverage <= 0F || end <= start)
722+
{
723+
return;
724+
}
725+
726+
if (runStart < 0)
727+
{
728+
runStart = start;
729+
runEnd = end;
730+
}
731+
else if (end > runEnd)
732+
{
733+
runEnd = end;
734+
}
735+
736+
// All spans in one buffered run are contiguous in destination space. That lets us
737+
// pack them into one scratch slice, keep their exact per-pixel coverage values, and
738+
// later hand the whole visible interval to the renderer in a single callback.
739+
scanline[(start - runStart)..(end - runStart)].Fill(coverage);
740+
}
741+
742+
/// <summary>
743+
/// Emits the currently buffered contiguous run, if any.
744+
/// </summary>
745+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
746+
private static void FlushBufferedRun<TRowHandler>(
694747
ref TRowHandler rowHandler,
695748
int destinationY,
696749
int destinationLeft,
697750
Span<float> scanline,
698-
int start,
699-
int end,
700-
float coverage)
751+
ref int runStart,
752+
ref int runEnd)
701753
where TRowHandler : struct, IRasterizerCoverageRowHandler
702754
{
703-
if (coverage <= 0F || end <= start)
755+
if (runStart < 0)
704756
{
705757
return;
706758
}
707759

708-
int length = end - start;
709-
scanline[..length].Fill(coverage);
710-
rowHandler.Handle(destinationY, destinationLeft + start, scanline[..length]);
760+
rowHandler.Handle(destinationY, destinationLeft + runStart, scanline[..(runEnd - runStart)]);
761+
runStart = -1;
762+
runEnd = -1;
711763
}
712764

713765
/// <summary>

0 commit comments

Comments
 (0)