@@ -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