partially rendering

This commit is contained in:
Jaklyy 2023-12-23 21:26:49 -05:00
parent 78da2846e6
commit bf26b6817d
3 changed files with 151 additions and 249 deletions

View File

@ -336,7 +336,7 @@ public:
// GPU 2D Read Timings: For Emulating Buffer Read/Write Race Conditions
static constexpr int DelayBetweenReads = 809 * TimingFrac;
static constexpr int ScanlineReadSpeed = 256 * TimingFrac;
static constexpr int ScanlineReadInc = DelayBetweenReads + ReadScanline;
static constexpr int ScanlineReadInc = DelayBetweenReads + ScanlineReadSpeed;
static constexpr int GPU2DSpeedFirstInPair = 810 * TimingFrac; // 810 | the delay between finishing reading a pair and beginning reading a new pair.
@ -344,8 +344,9 @@ public:
// and beginning reading the second scanline of a scanline pair.
static constexpr int GPU2DReadScanline = 256 * TimingFrac; // 256 | the time it takes to read a scanline.
static constexpr int GPU2DReadSLPair = 1618 * TimingFrac; // 1618 | notably the same as the scanline increment.
static constexpr int InitGPU2DTimeout = 51874 * TimingFrac; // 51618? | when it starts reading the first scanline.
static constexpr int InitGPU2DTimeout = 51874 * TimingFrac; // 51618? | when it finishes reading the first scanline.
static constexpr int GPU2D48Scanlines = GPU2DReadSLPair * 24; // time to read 48 scanlines.
static constexpr int FrameLength = ScanlineReadInc * 263; // how long the entire frame is. TODO: Verify if we actually need this?
// GPU 3D Rasterization Timings: For Emulating Scanline Timeout

View File

@ -114,33 +114,22 @@ void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept
}
}
bool SoftRenderer::DoTimings(s32 cycles, bool odd)
bool SoftRenderer::DoTimings(s32 cycles, s32* timingcounter)
{
// add timings to a counter and check if underflowed.
s32* counter;
if (odd) counter = &RasterTimingOdd;
else counter = &RasterTimingEven;
*counter += cycles;
if (RasterTiming + *counter > ScanlineTimeout) return false;
return true;
*timingcounter += cycles;
if (RasterTiming + *timingcounter <= ScanlineTimeout) return false;
else return true;
}
bool SoftRenderer::CheckTimings(s32 cycles, bool odd)
bool SoftRenderer::CheckTimings(s32 cycles, s32* timingcounter)
{
// check if there are 'cycles' amount of cycles remaining.
s32* counter;
if (odd) counter = &RasterTimingOdd;
else counter = &RasterTimingEven;
if (ScanlineTimeout - (RasterTiming + *counter) >= cycles) return true;
if (RasterTiming + *timingcounter <= ScanlineTimeout - cycles) return true;
else return false;
}
u32 SoftRenderer::DoTimingsPixels(s32 pixels, bool odd)
u32 SoftRenderer::DoTimingsPixels(s32 pixels, s32* timingcounter)
{
// calculate and return the difference between the old span and the new span, while adding timings to the timings counter
@ -149,22 +138,18 @@ u32 SoftRenderer::DoTimingsPixels(s32 pixels, bool odd)
pixels -= NumFreePixels;
s32* counter;
if (odd) counter = &RasterTimingOdd;
else counter = &RasterTimingEven;
*counter += pixels;
pixels = -(ScanlineTimeout - (RasterTiming + *counter));
*timingcounter += pixels;
pixels = -(ScanlineTimeout - (RasterTiming + *timingcounter));
if (pixels > 0)
{
*counter -= pixels;
*timingcounter -= pixels;
return pixels;
}
else return 0;
}
bool SoftRenderer::DoTimingsSlopes(RendererPolygon* rp, s32 y, bool odd)
bool SoftRenderer::DoTimingsSlopes(RendererPolygon* rp, s32 y, s32* timingcounter)
{
// determine the timing impact of the first polygon's slopes.
@ -173,15 +158,11 @@ bool SoftRenderer::DoTimingsSlopes(RendererPolygon* rp, s32 y, bool odd)
if (polygon->YTop == polygon->YBottom) return false; // 0 px tall line polygons do not have slopes, and thus no timing penalty
if (y == polygon->YTop) return false;
s32* counter;
if (odd) counter = &RasterTimingOdd;
else counter = &RasterTimingEven;
if (y >= polygon->Vertices[rp->NextVL]->FinalPosition[1] && rp->CurVL != polygon->VBottom) *timingcounter += FirstPerSlope;
if (y >= polygon->Vertices[rp->NextVL]->FinalPosition[1] && rp->CurVL != polygon->VBottom) *counter += FirstPerSlope;
if (y >= polygon->Vertices[rp->NextVR]->FinalPosition[1] && rp->CurVR != polygon->VBottom) *timingcounter += FirstPerSlope;
if (y >= polygon->Vertices[rp->NextVR]->FinalPosition[1] && rp->CurVR != polygon->VBottom) *counter += FirstPerSlope;
return DoTimings(FirstPerSlope*2, odd); // CHECKME: does this need to be done every time its incremented here? does this even need to be done *at all?*
return DoTimings(FirstPerSlope*2, timingcounter); // CHECKME: does this need to be done every time its incremented here? does this even need to be done *at all?*
}
void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const
@ -768,7 +749,7 @@ void SoftRenderer::CheckSlope(RendererPolygon* rp, s32 y)
}
}
bool SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y, bool odd)
bool SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y, s32* timingcounter)
{
Polygon* polygon = rp->PolyData;
@ -903,7 +884,7 @@ bool SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon*
// determine if the span can be rendered within the time allotted to the scanline
// TODO: verify the timing characteristics of shadow masks are the same as regular polygons.
s32 diff = DoTimingsPixels(xend-x, odd);
s32 diff = DoTimingsPixels(xend-x, timingcounter);
if (diff != 0)
{
xend -= diff;
@ -996,7 +977,7 @@ bool SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon*
return abortscanline;
}
bool SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y, bool odd)
bool SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y, s32* timingcounter)
{
Polygon* polygon = rp->PolyData;
u32 polyattr = (polygon->Attr & 0x3F008000);
@ -1153,7 +1134,7 @@ bool SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s3
if (xend > 256) xend = 256;
// determine if the span can be rendered within the time allotted to the scanline
s32 diff = DoTimingsPixels(xend-x, odd);
s32 diff = DoTimingsPixels(xend-x, timingcounter);
if (diff != 0)
{
xend -= diff;
@ -1450,7 +1431,7 @@ bool SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s3
return abortscanline;
}
bool SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys, bool odd)
void SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys, s32* timingcounter)
{
bool abort = false;
bool first = true;
@ -1461,17 +1442,17 @@ bool SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys, bool odd)
if (y == polygon->YBottom && y != polygon->YTop)
{
if (!abort) abort = (first && DoTimings(FirstNull, odd)) || DoTimings(EmptyPolyScanline, odd);
if (!abort) abort = (first && DoTimings(FirstNull, timingcounter)) || DoTimings(EmptyPolyScanline, timingcounter);
first = false;
}
else if (y >= polygon->YTop && (y < polygon->YBottom || (y == polygon->YTop && polygon->YBottom == polygon->YTop)))
{
//if (y == polygon->YTop) if(DoTimings(FirstPolyScanline, odd)) abort = true;
//if (y == polygon->YTop) if(DoTimings(FirstPolyScanline, timingcounter)) abort = true;
if (!abort) abort = (first && DoTimingsSlopes(rp, y, odd)) // incorrect. needs research; behavior is strange...
|| DoTimings(PerPolyScanline, odd)
|| (!CheckTimings(MinToStartPoly, odd));
if (!abort) abort = (first && DoTimingsSlopes(rp, y, timingcounter)) // incorrect. needs research; behavior is strange...
|| DoTimings(PerPolyScanline, timingcounter)
|| (!CheckTimings(MinToStartPoly, timingcounter));
if (abort)
{
@ -1479,15 +1460,15 @@ bool SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys, bool odd)
Step(rp);
}
else if (polygon->IsShadowMask)
abort = RenderShadowMaskScanline(gpu.GPU3D, rp, y, odd);
abort = RenderShadowMaskScanline(gpu.GPU3D, rp, y, timingcounter);
else
abort = RenderPolygonScanline(gpu, rp, y, odd);
abort = RenderPolygonScanline(gpu, rp, y, timingcounter);
first = false;
}
}
return abort;
return;
}
u32 SoftRenderer::CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const
@ -1766,8 +1747,9 @@ u16 SoftRenderer::BeginPushScanline(s32 y, s32 pixelstodraw)
{
// push the finished scanline to the appropriate frame buffers.
// if a scanline is late enough to intersect with the 2d engine read time it will be partially drawn
/*
u16 start;
if (pixelstodraw > 256)
if (pixelstodraw >= 256)
{
start = 0;
pixelstodraw = 256;
@ -1786,21 +1768,26 @@ u16 SoftRenderer::BeginPushScanline(s32 y, s32 pixelstodraw)
pixelstodraw += jitter;
start -= jitter;
}
bufferpos = y % 48;
u8 bufferpos = y % 48;
memcpy(&RDBuffer[bufferpos*ScanlineWidth+start], &ColorBuffer[y*ScanlineWidth+start], 4 * pixelstodraw);
return start;
*/
u8 bufferpos = y % 48;
memcpy(&RDBuffer[bufferpos*ScanlineWidth], &ColorBuffer[y*ScanlineWidth], 4*ScanlineWidth);
return 0;
}
void SoftRenderer::ReadScanline(s32 y)
{
u8 bufferpos = y % 48;
memcpy(&FinalBuffer[y*ScanlineWidth], &RDBuffer[bufferpos*ScanlineWidth], 4 * ScanlineWidth);
}
void SoftRenderer::FinishPushScanline(s32 y s32 pixelsremain)
void SoftRenderer::FinishPushScanline(s32 y, s32 pixelsremain)
{
if (pixelsremain = 0) return;
bufferpos = y % 48;
u8 bufferpos = y % 48;
memcpy(&RDBuffer[bufferpos*ScanlineWidth], &ColorBuffer[y*ScanlineWidth], 4 * pixelsremain);
}
@ -1821,27 +1808,27 @@ void SoftRenderer::RenderPolygons(GPU& gpu, Polygon** polygons, int npolys)
s32 yold;
rasterevents[RenderStart] = 0;
rasterevents[RenderFinal] = INT_MAX/2;
rasterevents[RenderFinalP2] = INT_MAX;
rasterevents[ScanlineWrite] = INT_MAX;
rasterevents[PushScanline] = INT_MAX/2;
rasterevents[PushScanlineP2] = INT_MAX/2;
rasterevents[ScanlineRead] = InitGPU2DTimeout;
ScanlineTimeout = INT_MAX;
ScanlineTimeout = INT_MAX/2;
RasterTiming = 0;
RasterTimingEven = 0;
RasterTimingOdd = 0;
u8 scanlinesread = 0
u8 scanlinesrendered;
s32 rastertimingeven = 0;
s32 rastertimingodd = 0;
u8 scanlinesread = 0;
u8 scanlinespushed = 0;
s8 scanlinesrendered = 0;
s8 scanlineswaiting = 0;
u8 nextevent;
u16 leftoversa;
u16 leftoversb;
bool finalunsched = true;
bool doa, dob, fina;
u16 leftoversa, leftoversb;
while (scanlinesread < 192)
while ((scanlinesread < 192 || scanlinespushed < 192) && (RasterTiming < FrameLength))
{
nextevent = 0;
for (int i = 1; i < RasterEvents_MAX - finalunsched; i++)
for (s32 i = 1; i < RasterEvents_MAX; i++)
{
if (rasterevents[nextevent] > rasterevents[i])
if (rasterevents[i] < rasterevents[nextevent])
nextevent = i;
}
@ -1849,68 +1836,107 @@ void SoftRenderer::RenderPolygons(GPU& gpu, Polygon** polygons, int npolys)
{
case RenderStart:
bool abort = RenderScanline(gpu, y, j, true);
abort |= RenderScanline(gpu, y+1, j, false);
RasterTiming = rasterevents[RenderStart];
{
s32 rastertimingeven = 0;
s32 rastertimingodd = 0;
RenderScanline(gpu, y, j, &rastertimingeven);
RenderScanline(gpu, y+1, j, &rastertimingodd);
s32 timespent = std::max(rastertimingeven, rastertimingodd);
timespent = std::max(RasterTimingEven, RasterTimingOdd);
RasterTiming += timespent;
if ((RasterTiming + timespent) < (rasterevents[RenderFinal]+FinalPassLen))
RasterTiming += FinalPassLen;
else
RasterTiming += timespent;
s32 timeoutdist = ScanlineTimeout - RasterTiming;
RasterTiming += std::clamp(timeoutdist, 0, 12);
}
rasterevents[RenderFinal] = RasterTiming;
rasterevents[RenderScanline] = RasterTiming+RastDelay;
finalunsched = false;
if (y < 190) rasterevents[RenderStart] = RasterTiming+RastDelay;
else rasterevents[RenderStart] = INT_MAX/2;
break;
case RenderFinal:
if (y > 2)
rasterevents[PushScanline] = rasterevents[RenderFinal] + RastDelay;
if (y >= 2)
{
ScanlineFinalPass(gpu.GPU3D, y-1);
leftoversa = BeginPushScanline(y-1, (rasterevents[ScanlineRead] - ScanlineReadSpeed) - (rasterevents[RenderFinal] + FinalPassLen));
if (leftoversa != 0)
{
rasterevents[RenderFinalP2] = rasterevents[ScanlineRead] - ScanlineReadSpeed;
yold = y;
}
else
{
scanlineswaiting++;
scanlinesrendered++;
}
}
if (y < 192)
{
ScanlineFinalPass(gpu.GPU3D, y);
leftoversb = BeginPushScanline(y, (rasterevents[ScanlineRead] + DelayBetweenReads) - (rasterevents[RenderFinal] + FinalPassLen));
if (leftoversb != 0)
{
rasterevents[RenderFinalP2] = rasterevents[ScanlineRead] + DelaybetweenReads;
yold = y;
}
else
{
scanlineswaiting++;
scanlinesrendered++;
}
finalunsched = true;
scanlinesrendered++;
doa = true;
}
else
{
rasterevents[RenderFinal] += FinalPassLen;
doa = false;
}
if (y < 192)
{
ScanlineFinalPass(gpu.GPU3D, y);
scanlinesrendered++;
rasterevents[RenderFinal] = INT_MAX/2;
}
else
rasterevents[RenderFinal] += FinalPassLen;
y += 2;
break;
case PushScanline:
if (scanlineswaiting >= 48)
{
//reschedule events if buffer is full
rasterevents[PushScanline] = rasterevents[ScanlineRead];
rasterevents[RenderStart] = ((y >= 190) ? INT_MAX/2 : rasterevents[ScanlineRead] + RastDelay);
rasterevents[RenderFinal] = ((y >= 192) ? INT_MAX/2 : rasterevents[ScanlineRead]);
break;
}
if (doa)
{
leftoversa = BeginPushScanline(scanlinespushed, 256);//(rasterevents[ScanlineRead] - ScanlineReadSpeed) - (rasterevents[PushScanline] + FinalPassLen));
scanlinesrendered--;
if (leftoversa != 0)
{
rasterevents[PushScanlineP2] = rasterevents[ScanlineRead] - ScanlineReadSpeed; // todo: fix this
fina = true;
}
else
{
scanlineswaiting++;
scanlinespushed++;
}
}
else
{
leftoversb = BeginPushScanline(scanlinespushed, 256);//(rasterevents[ScanlineRead] + DelayBetweenReads) - (rasterevents[PushScanline] + FinalPassLen));
scanlinesrendered--;
if (leftoversb != 0)
{
rasterevents[PushScanlineP2] = rasterevents[ScanlineRead] + DelayBetweenReads; // todo: fix this
fina = false;
}
else
{
scanlineswaiting++;
scanlinespushed++;
}
}
if (scanlinesrendered <= 0)
rasterevents[PushScanline] = INT_MAX/2;
else
doa = !doa;
break;
case ScanlineRead:
ReadScanline(scanlinesread);
@ -1923,152 +1949,26 @@ void SoftRenderer::RenderPolygons(GPU& gpu, Polygon** polygons, int npolys)
scanlineswaiting--;
break;
case RenderFinalP2:
case PushScanlineP2:
if (y > 2)
if (fina)
{
FinishPushScanline(yold-1, leftoversa);
scanlineswaiting++;
scanlinesrendered++;
scanlinespushed++;
}
if (y < 192)
else
{
FinishPushScanline(yold, leftoversb);
scanlineswaiting++;
scanlinesrendered++;
scanlinespushed++;
}
rasterevents[RenderFinalP2] = INT_MAX;
rasterevents[PushScanlineP2] = INT_MAX/2;
break;
}
}
}
/*ScanlineRead = InitGPU2DTimeout;
ScanlineTimeout = INT_MAX;
RasterTiming = 0;
s32 prevscanlineread;
s32 prevrastertiming;
for (y = 0; y < 192; y += 2)
{
RasterTimingEven = 0;
RasterTimingOdd = 0;
// scanlines are rendered in pairs simultaneously
bool abort = RenderScanline(gpu, y, j, true);
abort |= RenderScanline(gpu, y+1, j, false);
timespent = std::max(RasterTimingEven, RasterTimingOdd);
if (timespend > FreeTiming)
RasterTiming += timespent;
// the next loop begins
if (y!=0)
{
// finish second scanline from 2 pairs back
ScanlineFinalPass(gpu.GPU3D, y-1);
PushScanline(y-1, (ScanlineRead+GPU2DReadScanline)-(RasterTiming+FinishScanline));
ScanlineRead += ScanlineReadInc;
if constexpr (threaded) Platform::Semaphore_Post(Sema_ScanlineCount);
}
// finish previous first scanline
ScanlineFinalPass(gpu.GPU3D, y);
PushScanline(y, (ScanlineRead+GPU2DReadScanline)-(RasterTiming+FinishScanline));
ScanlineRead += ScanlineReadInc;
if constexpr (threaded) Platform::Semaphore_Post(Sema_ScanlineCount);
y += 2;
}
RasterTiming +=
// one more loop just to finish off the final scanline
ScanlineFinalPass(gpu.GPU3D, 191);
PushScanline(191, (ScanlineRead+GPU2DReadScanline)-(RasterTiming+FinishScanline));
if constexpr (threaded) Platform::Semaphore_Post(Sema_ScanlineCount);
*/
/*s32 y = 0;
s8 prevbufferline = -2;
s8 buffersize = 0;
RasterTiming = INT_MAX/2;
bool abort = false;
ClearBuffers(gpu);
s32 gpu2dtracking = InitGPU2DTimeout;
s32 prev2dtime;
bool readodd = true;
for (u8 quarter = 0; quarter < 4; quarter++)
for (u8 bufferline = 0; bufferline < 48; bufferline += 2)
{
RasterTimingOdd = 0;
RasterTimingEven = 0;
if (y == 2) RasterTiming = InitialTiming;
RasterTiming += ScanlineIncrement;
gpu2dtracking += GPU2DReadSLPair;
if (abort) RasterTiming += AbortIncrement; // if previous scanline was aborted, allow an extra 12 pixels worth of timing
if (y >= 50)
{
if (RasterTiming > Post50Max)
{
s32 temp = RasterTiming - Post50Max;
RasterTiming = Post50Max;
gpu2dtracking -= temp;
}
if (buffersize > 48) buffersize = 48;
}
abort = RenderScanline(gpu, y, j, true);
abort |= RenderScanline(gpu, y+1, j, false);
buffersize += 2;
//RasterTiming += ScanlineBreak;
s32 timespent = std::max(RasterTimingOdd, RasterTimingEven);
if (RasterTiming - timespent <= UnderflowFlag) gpu.GPU3D.DispCnt |= (1<<12); // checkme: should this flag set itself every frame a "underflowed" frame is rendered, even if said frame is duplicated?
timespent -= FreeTiming;
if (timespent > 0)
{
RasterTiming -= timespent;
gpu2dtracking -= timespent;
}
//if (RasterTiming < 0) RasterTiming = 0;
buffersize = 0;
for (int i = gpu2dtracking; i > 0; i -= GPU2DReadSLPair/2) buffersize++;
if (buffersize < gpu.GPU3D.RDLines) gpu.GPU3D.RDLines = buffersize;
if (prevbufferline >= 0)
{
ScanlineFinalPass(gpu.GPU3D, y-2, prevbufferline, true, prev2dtime);
ScanlineFinalPass(gpu.GPU3D, y-1, prevbufferline+1, false, prev2dtime);
if (threaded)
{
Platform::Semaphore_Post(Sema_ScanlineCount);
Platform::Semaphore_Post(Sema_ScanlineCount);
}
}
y += 2;
prevbufferline = bufferline;
prev2dtime = gpu2dtracking;
}
ScanlineFinalPass(gpu.GPU3D, 190, prevbufferline, true, prev2dtime);
ScanlineFinalPass(gpu.GPU3D, 191, prevbufferline+1, false, prev2dtime);
if (threaded)
{
Platform::Semaphore_Post(Sema_ScanlineCount);
Platform::Semaphore_Post(Sema_ScanlineCount);
}
}*/
void SoftRenderer::VCount144(GPU& gpu)
{

View File

@ -453,10 +453,10 @@ private:
};
RendererPolygon PolygonList[2048];
bool DoTimings(s32 cycles, bool odd);
bool CheckTimings(s32 cycles, bool odd);
u32 DoTimingsPixels(s32 pixels, bool odd);
bool DoTimingsSlopes(RendererPolygon* rp, s32 y, bool odd);
bool DoTimings(s32 cycles, s32* timingcounter);
bool CheckTimings(s32 cycles, s32* timingcounter);
u32 DoTimingsPixels(s32 pixels, s32* timingcounter);
bool DoTimingsSlopes(RendererPolygon* rp, s32 y, s32* timingcounter);
void TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const;
u32 RenderPixel(const GPU& gpu, const Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) const;
void PlotTranslucentPixel(const GPU3D& gpu3d, u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow);
@ -465,28 +465,29 @@ private:
void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const;
void Step(RendererPolygon* rp);
void CheckSlope(RendererPolygon* rp, s32 y);
bool RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y, bool odd);
bool RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y, bool odd);
bool RenderScanline(const GPU& gpu, s32 y, int npolys, bool odd);
bool RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y, s32* timingcounter);
bool RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y, s32* timingcounter);
void RenderScanline(const GPU& gpu, s32 y, int npolys, s32* timingcounter);
u32 CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const;
void ScanlineFinalPass(const GPU3D& gpu3d, s32 y, u8 rdbufferoffset, bool odd, s32 uhohzone);
void ScanlineFinalPass(const GPU3D& gpu3d, s32 y);
void ClearBuffers(const GPU& gpu);
u16 BeginPushScanline(s32 y, s32 pixelstodraw);
void ReadScanline(s32 y);
void FinishPushScanline(s32 y, s32 pixelsremain);
template <bool threaded> void RenderPolygons(GPU& gpu, Polygon** polygons, int npolys);
voi PushScanline(s32 y, s32 pixelstodraw);
void RenderThreadFunc(GPU& gpu);
// counters for scanline rasterization timings
s32 ScanlineTimeout;
s32 RasterTiming;
s32 RasterTimingOdd;
s32 RasterTimingEven;
enum
{
RenderStart = 0,
ScanlineRead,
RenderFinalP2,
PushScanline,
PushScanlineP2,
RenderFinal,
RasterEvents_MAX,
};