///////////////////////////////////////////////////////////////////////////////
// Copyright (C) Microsoft Corporation, 2000.
//
// pixproc.cpp
//
// Direct3D Reference Device - Pixel Processor
//
///////////////////////////////////////////////////////////////////////////////
#include "pch.cpp"
#pragma hdrstop

//-----------------------------------------------------------------------------
//
// WritePixel - Writes pixel and (maybe) depth to current render target.
//
//-----------------------------------------------------------------------------
void
RefRast::WritePixel(
    INT32 iX, INT32 iY, UINT Sample,
    const RDColor& Color, const RDDepth& Depth)
{
    m_pRD->m_pRenderTarget->WritePixelColor( iX, iY, Sample, Color,
        m_pRD->GetRS()[D3DRS_DITHERENABLE]);

    // don't write if Z buffering disabled or Z write disabled
    if ( !( m_pRD->GetRS()[D3DRS_ZENABLE     ] ) ||
         !( m_pRD->GetRS()[D3DRS_ZWRITEENABLE] ) ) { return; }

    m_pRD->m_pRenderTarget->WritePixelDepth( iX, iY, Sample, Depth );
}

//-----------------------------------------------------------------------------
//
// DoPixels - Invoked for each set of 2x2 pixels by the scan converter, applies
// texture, specular, fog, alpha blend, and writes result to surface.  Also
// implements depth, alpha, and stencil tests.
//
//-----------------------------------------------------------------------------
void
RefRast::DoPixels( void )
{
#if DBG
    for ( m_iPix = 0; m_iPix < 4; m_iPix++ )
    {
        if ( !m_bPixelIn[m_iPix] ) continue;
        if (m_pRD->m_pDbgMon) m_pRD->m_pDbgMon->NextEvent( D3DDM_EVENT_PIXEL );
    }
#endif

    // pixel shader executed for all 4 pixels of 2x2 grid at one time
    if (m_pCurrentPixelShader)
        ExecShader();

    for ( m_iPix = 0; m_iPix < 4; m_iPix++ )
    {
        if ( !m_bPixelIn[m_iPix] ) continue;
        if ( m_bPixelDiscard[m_iPix] ) continue;

        RDColor PixelColor;
        if ( !m_bLegacyPixelShade )
        {
            // pixel shader final color always left in temp register 0
            PixelColor = m_TempReg[0][m_iPix];
            // saturate before blend and FB access
            PixelColor.Clamp();
        }
        else
        {
            // apply legacy pixel shading (texture lookups already done by ExecShader)
            PixelColor = m_InputReg[0][m_iPix];
            RDColor PixelSpecular( m_InputReg[1][m_iPix] );
            RDColor LastStageColor( PixelColor );
            RDColor ResultColor( PixelColor );
            RDColor TempColor( (DWORD)0x0 );
            for ( int iStage=0; iStage<m_pRD->m_cActiveTextureStages; iStage++ )
            {

                if ( m_pRD->GetTSS(iStage)[D3DTSS_COLOROP] == D3DTOP_DISABLE )
                {
                    ResultColor = LastStageColor; // pass result of previous stage
                    break;
                }

                // no blend if texture bound to stage is bumpmap
                if ( ( m_pRD->GetTSS(iStage)[D3DTSS_COLOROP] == D3DTOP_BUMPENVMAP ) ||
                     ( m_pRD->GetTSS(iStage)[D3DTSS_COLOROP] == D3DTOP_BUMPENVMAPLUMINANCE ) )
                {
                    continue;
                }

                RDColor TextureColor( m_TextReg[iStage][m_iPix] );
                DoTextureBlendStage( iStage, PixelColor, PixelSpecular,
                    LastStageColor, TextureColor, TempColor, ResultColor );

                // set color for next stage
                LastStageColor = ResultColor;
            }
            PixelColor = ResultColor;

            // add specular and saturate
            if ( m_pRD->GetRS()[D3DRS_SPECULARENABLE] )
            {
                PixelColor.R += PixelSpecular.R;
                PixelColor.G += PixelSpecular.G;
                PixelColor.B += PixelSpecular.B;
                PixelColor.Saturate();
            }
        }

        // do alpha test - bail out if failed
        if ( m_pRD->GetRS()[D3DRS_ALPHATESTENABLE] &&
             !AlphaTest( PixelColor.A ) )
        {
            continue;
        }

        // apply fog
        if ( m_pRD->GetRS()[D3DRS_FOGENABLE] )
        {
            RDColor FogColor = m_pRD->GetRS()[D3DRS_FOGCOLOR];
            // (TODO: account for pre-multiplied alpha here??)
            FLOAT ObjColorFrac = m_FogIntensity[m_iPix];
            FLOAT FogColorFrac = 1.f - m_FogIntensity[m_iPix];
            PixelColor.R = (ObjColorFrac * PixelColor.R) + (FogColorFrac * FogColor.R);
            PixelColor.G = (ObjColorFrac * PixelColor.G) + (FogColorFrac * FogColor.G);
            PixelColor.B = (ObjColorFrac * PixelColor.B) + (FogColorFrac * FogColor.B);
        }

        //
        // remainder is done per-sample for multisample buffers
        //
        INT32 iX = m_iX[m_iPix];
        INT32 iY = m_iY[m_iPix];
        do
        {
            RDColor FinalPixelColor = PixelColor;
            UINT iSample = GetCurrentSample();

            if ( !m_bIsLine &&
                   ( !GetCurrentSampleMask() ||
                     !m_bSampleCovered[iSample][m_iPix] ) )
            {
                // iSample not covered by this geometry
                continue;
            }
            //
            // read current depth for this pixel and do depth test - cannot
            // bail out if failed because stencil may need to be updated
            //
            BOOL bDepthTestPassed = TRUE;
            if ( m_pRD->GetRS()[D3DRS_ZENABLE] )
            {
                m_Depth[m_iPix] = m_SampleDepth[iSample][m_iPix];

                RDDepth BufferDepth( m_Depth[m_iPix].GetSType() );
                m_pRD->m_pRenderTarget->ReadPixelDepth( iX, iY, iSample, BufferDepth );
                bDepthTestPassed = DepthCloser( m_Depth[m_iPix], BufferDepth );
            }

            //
            // do stencil operation
            //
            BOOL bStencilTestPassed = TRUE;
            if ( m_pRD->GetRS()[D3DRS_STENCILENABLE] )
            {
                // read stencil buffer and do stencil operation
                UINT8 uStncBuf = 0x0;
                m_pRD->m_pRenderTarget->ReadPixelStencil( iX, iY, iSample, uStncBuf );
                UINT8 uStncNew;
                bStencilTestPassed =
                    DoStencil( uStncBuf, bDepthTestPassed, m_pRD->m_pRenderTarget->m_pDepth->GetSurfaceFormat(), uStncNew );

                // update stencil only if changed
                if ( uStncNew != uStncBuf )
                {
                    // compute new buffer value based on write mask
                    UINT8 uStncWMask = m_pRD->GetRS()[D3DRS_STENCILWRITEMASK];
                    UINT8 uStncBufNew = (uStncBuf & ~uStncWMask) | (uStncNew & uStncWMask);
                    m_pRD->m_pRenderTarget->WritePixelStencil( iX, iY, iSample, uStncBufNew );
                }
            }

            if ( !(bDepthTestPassed && bStencilTestPassed) )
            {
                continue;
            }

            //
            // do alpha blend and write mask
            //
            if ( ( ( m_pRD->GetRS()[D3DRS_COLORWRITEENABLE] & 0xF) != 0xF ) ||
                 ( m_pRD->GetRS()[D3DRS_ALPHABLENDENABLE] ) )
            {
                RDColor BufferColor;
                m_pRD->m_pRenderTarget->ReadPixelColor( iX, iY, iSample, BufferColor );

                if ( m_pRD->GetRS()[D3DRS_ALPHABLENDENABLE] )
                {
                    DoAlphaBlend( FinalPixelColor, BufferColor, FinalPixelColor );
                }

                if ( !(m_pRD->GetRS()[D3DRS_COLORWRITEENABLE] & D3DCOLORWRITEENABLE_RED) )
                    FinalPixelColor.R = BufferColor.R;
                if ( !(m_pRD->GetRS()[D3DRS_COLORWRITEENABLE] & D3DCOLORWRITEENABLE_GREEN) )
                    FinalPixelColor.G = BufferColor.G;
                if ( !(m_pRD->GetRS()[D3DRS_COLORWRITEENABLE] & D3DCOLORWRITEENABLE_BLUE) )
                    FinalPixelColor.B = BufferColor.B;
                if ( !(m_pRD->GetRS()[D3DRS_COLORWRITEENABLE] & D3DCOLORWRITEENABLE_ALPHA) )
                    FinalPixelColor.A = BufferColor.A;
            }

#if 0
{
    extern float g_GammaTable[];
    FinalPixelColor.R = g_GammaTable[ (UINT8)(255.f*FinalPixelColor.R) ];
    FinalPixelColor.G = g_GammaTable[ (UINT8)(255.f*FinalPixelColor.G) ];
    FinalPixelColor.B = g_GammaTable[ (UINT8)(255.f*FinalPixelColor.B) ];
}
#endif
            //
            // update color and depth buffers
            //
            WritePixel( iX, iY, iSample, FinalPixelColor, m_Depth[m_iPix] );

        } while (NextSample());
    }
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Pixel Processing Utility Functions                                        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
//
// Depth compare method used for Z buffering and fragment processing.
//
// Returns TRUE if DepthVal is closer than DepthBuf.  DepthA is the generated
// value and DepthB
//
//-----------------------------------------------------------------------------
BOOL
RefRast::DepthCloser(
    const RDDepth& DepthVal,
    const RDDepth& DepthBuf )
{
    if ( !m_pRD->GetRS()[D3DRS_ZENABLE] ) { return TRUE; }


    switch ( m_pRD->GetRS()[D3DRS_ZFUNC] )
    {
    case D3DCMP_NEVER:        return FALSE;
    case D3DCMP_LESS:         return ( DOUBLE(DepthVal) <  DOUBLE(DepthBuf) );
    case D3DCMP_EQUAL:        return ( DOUBLE(DepthVal) == DOUBLE(DepthBuf) );
    case D3DCMP_LESSEQUAL:    return ( DOUBLE(DepthVal) <= DOUBLE(DepthBuf) );
    case D3DCMP_GREATER:      return ( DOUBLE(DepthVal) >  DOUBLE(DepthBuf) );
    case D3DCMP_NOTEQUAL:     return ( DOUBLE(DepthVal) != DOUBLE(DepthBuf) );
    case D3DCMP_GREATEREQUAL: return ( DOUBLE(DepthVal) >= DOUBLE(DepthBuf) );
    case D3DCMP_ALWAYS:       return TRUE;
    }

    return TRUE;
}

//-----------------------------------------------------------------------------
//
// Alpha test method for pixel processing.
//
// Returns TRUE if alpha test passes.
//
//-----------------------------------------------------------------------------
BOOL
RefRast::AlphaTest( FLOAT fAlpha )
{
    // grab 8 bit unsigned alpha value
    UINT8 uAlpha = (UINT8)(255.f*fAlpha);

    // form 8 bit alpha reference value
    UINT8 uAlphaRef8 = m_pRD->GetRS()[D3DRS_ALPHAREF];

    // do alpha test and either return directly or pass through
    switch ( m_pRD->GetRS()[D3DRS_ALPHAFUNC] )
    {
    case D3DCMP_NEVER:        return FALSE;
    case D3DCMP_LESS:         return (uAlpha <  uAlphaRef8);
    case D3DCMP_EQUAL:        return (uAlpha == uAlphaRef8);
    case D3DCMP_LESSEQUAL:    return (uAlpha <= uAlphaRef8);
    case D3DCMP_GREATER:      return (uAlpha >  uAlphaRef8);
    case D3DCMP_NOTEQUAL:     return (uAlpha != uAlphaRef8);
    case D3DCMP_GREATEREQUAL: return (uAlpha >= uAlphaRef8);
    case D3DCMP_ALWAYS:       return TRUE;
    }

    return TRUE;
}

//-----------------------------------------------------------------------------
//
// DoStencil - Performs stencil test.  Returns TRUE if stencil test passed.
// Also computes stencil result value (to be written back to stencil planes
// if test passes, subject to stencil write mask).
//
//-----------------------------------------------------------------------------
BOOL
RefRast::DoStencil(
    UINT8 uStncBuf,     // in: stencil buffer value
    BOOL bDepthTest,    // in: boolean result of depth test
    RDSurfaceFormat DepthSType,   // in: surface type of Z buffer
    UINT8& uStncRet)    // out: stencil value result
{
    // support 8 bit stencil only, so do everything as UINT8's

    // max value for masking and saturation ops
    UINT8 uStncMax;
    switch(DepthSType)
    {
    case RD_SF_Z24S8:
    case RD_SF_S8Z24: uStncMax = 0xff; break;
    case RD_SF_Z15S1:
    case RD_SF_S1Z15: uStncMax = 0x1;  break;
    case RD_SF_Z24X4S4:
    case RD_SF_X4S4Z24: uStncMax = 0xf;  break;
    default:          uStncMax = 0;    break;  // don't let stencil become non 0
    }

    // get reference from renderstate
    UINT8 uStncRef = (UINT8)(m_pRD->GetRS()[D3DRS_STENCILREF]);
    // mask to use only bits possibly present in stencil buffer
    uStncRef &= uStncMax;

    // form masked values for test
    UINT8 uStncMask = (UINT8)(m_pRD->GetRS()[D3DRS_STENCILMASK]);
    UINT8 uStncBufM = uStncBuf & uStncMask;
    UINT8 uStncRefM = uStncRef & uStncMask;

    // do stencil compare function
    BOOL bStncTest = FALSE;
    switch ( m_pRD->GetRS()[D3DRS_STENCILFUNC] )
    {
    case D3DCMP_NEVER:        bStncTest = FALSE; break;
    case D3DCMP_LESS:         bStncTest = (uStncRefM <  uStncBufM); break;
    case D3DCMP_EQUAL:        bStncTest = (uStncRefM == uStncBufM); break;
    case D3DCMP_LESSEQUAL:    bStncTest = (uStncRefM <= uStncBufM); break;
    case D3DCMP_GREATER:      bStncTest = (uStncRefM >  uStncBufM); break;
    case D3DCMP_NOTEQUAL:     bStncTest = (uStncRefM != uStncBufM); break;
    case D3DCMP_GREATEREQUAL: bStncTest = (uStncRefM >= uStncBufM); break;
    case D3DCMP_ALWAYS:       bStncTest = TRUE; break;
    }

    // determine which stencil operation to perform
    DWORD dwStencilOp;
    if ( !bStncTest )
    {
        // stencil test failed - depth test does not matter
        dwStencilOp = m_pRD->GetRS()[D3DRS_STENCILFAIL];
    }
    else
    {
        // stencil test passed - select based on depth pass/fail
        dwStencilOp = ( !bDepthTest )
            ? ( m_pRD->GetRS()[D3DRS_STENCILZFAIL] )
            : ( m_pRD->GetRS()[D3DRS_STENCILPASS] );
    }

    uStncRet = 0x0;
    switch ( dwStencilOp )
    {
    case D3DSTENCILOP_KEEP:    uStncRet = uStncBuf; break;
    case D3DSTENCILOP_ZERO:    uStncRet = 0x00; break;
    case D3DSTENCILOP_REPLACE: uStncRet = uStncRef; break;
    case D3DSTENCILOP_INCRSAT:
        uStncRet = (uStncBuf==uStncMax)?(uStncMax):(uStncBuf+1); break;
    case D3DSTENCILOP_DECRSAT:
        uStncRet = (uStncBuf==0x00)?(0x00):(uStncBuf-1); break;
    case D3DSTENCILOP_INVERT:  uStncRet = ~uStncBuf; break;
    case D3DSTENCILOP_INCR:    uStncRet = uStncBuf+1; break;
    case D3DSTENCILOP_DECR:    uStncRet = uStncBuf-1; break;
    }

    return bStncTest;
}

//-----------------------------------------------------------------------------
//
// DoAlphaBlend - Performs color blending of source and destination colors
// producing a result color.
//
//-----------------------------------------------------------------------------
void
RefRast::DoAlphaBlend(
    const RDColor& SrcColor,    // in: source pixel color
    const RDColor& DstColor,    // in: destination (buffer) color
    RDColor& ResColor)          // out: result (blended) color
{
    RDColor SrcColorFactor;
    RDColor DstColorFactor;
    BOOL bDestBlendOverride = FALSE;

    // no SRC/DST blend (or clamp) required for MIN or MAX BLENDOP
    switch ( m_pRD->GetRS()[D3DRS_BLENDOP] )
    {
    case D3DBLENDOP_MIN:
        ResColor.R = MIN(SrcColor.R,DstColor.R);
        ResColor.G = MIN(SrcColor.G,DstColor.G);
        ResColor.B = MIN(SrcColor.B,DstColor.B);
        ResColor.A = MIN(SrcColor.A,DstColor.A);
        return;
    case D3DBLENDOP_MAX:
        ResColor.R = MAX(SrcColor.R,DstColor.R);
        ResColor.G = MAX(SrcColor.G,DstColor.G);
        ResColor.B = MAX(SrcColor.B,DstColor.B);
        ResColor.A = MAX(SrcColor.A,DstColor.A);
        return;
    }

    // compute source blend factors
    switch ( m_pRD->GetRS()[D3DRS_SRCBLEND] )
    {

    default:
    case D3DBLEND_ZERO:
        SrcColorFactor.SetAllChannels( 0.F );
        break;

    case D3DBLEND_ONE:
        SrcColorFactor.SetAllChannels( 1.F );
        break;

    case D3DBLEND_SRCCOLOR:
        SrcColorFactor.R = SrcColor.R;
        SrcColorFactor.G = SrcColor.G;
        SrcColorFactor.B = SrcColor.B;
        SrcColorFactor.A = SrcColor.A;
        break;

    case D3DBLEND_INVSRCCOLOR:
        SrcColorFactor.R = ( 1.f - SrcColor.R );
        SrcColorFactor.G = ( 1.f - SrcColor.G );
        SrcColorFactor.B = ( 1.f - SrcColor.B );
        SrcColorFactor.A = ( 1.f - SrcColor.A );
        break;

    case D3DBLEND_SRCALPHA:
        SrcColorFactor.SetAllChannels( SrcColor.A );
        break;

    case D3DBLEND_INVSRCALPHA:
        SrcColorFactor.SetAllChannels( 1.f - SrcColor.A );
        break;

    case D3DBLEND_DESTALPHA:
        SrcColorFactor.SetAllChannels( DstColor.A );
        break;

    case D3DBLEND_INVDESTALPHA:
        SrcColorFactor.SetAllChannels( 1.f - DstColor.A );
        break;

    case D3DBLEND_DESTCOLOR:
        SrcColorFactor.R = DstColor.R;
        SrcColorFactor.G = DstColor.G;
        SrcColorFactor.B = DstColor.B;
        SrcColorFactor.A = DstColor.A;
        break;

    case D3DBLEND_INVDESTCOLOR:
        SrcColorFactor.R = ( 1.f - DstColor.R );
        SrcColorFactor.G = ( 1.f - DstColor.G );
        SrcColorFactor.B = ( 1.f - DstColor.B );
        SrcColorFactor.A = ( 1.f - DstColor.A );
        break;

    case D3DBLEND_SRCALPHASAT:
        {
            FLOAT F = MIN( SrcColor.A, 1.f - DstColor.A );
            SrcColorFactor.R = F;
            SrcColorFactor.G = F;
            SrcColorFactor.B = F;
        }
        SrcColorFactor.A = 1.F;
        break;

    // these are for SRCBLEND only and override DESTBLEND
    case D3DBLEND_BOTHSRCALPHA:
        bDestBlendOverride = TRUE;
        SrcColorFactor.SetAllChannels( SrcColor.A );
        DstColorFactor.SetAllChannels( 1.f - SrcColor.A );
        break;

    case D3DBLEND_BOTHINVSRCALPHA:
        bDestBlendOverride = TRUE;
        SrcColorFactor.SetAllChannels( 1.f - SrcColor.A );
        DstColorFactor.SetAllChannels( SrcColor.A );
        break;
    }

    // compute destination blend factors
    if ( !bDestBlendOverride )
    {
        switch ( m_pRD->GetRS()[D3DRS_DESTBLEND] )
        {

        default:
        case D3DBLEND_ZERO:
            DstColorFactor.SetAllChannels( 0.F );
            break;

        case D3DBLEND_ONE:
            DstColorFactor.SetAllChannels( 1.F );
            break;

        case D3DBLEND_SRCCOLOR:
            DstColorFactor.R = SrcColor.R;
            DstColorFactor.G = SrcColor.G;
            DstColorFactor.B = SrcColor.B;
            DstColorFactor.A = SrcColor.A;
            break;

        case D3DBLEND_INVSRCCOLOR:
            DstColorFactor.R = ( 1.f - SrcColor.R );
            DstColorFactor.G = ( 1.f - SrcColor.G );
            DstColorFactor.B = ( 1.f - SrcColor.B );
            DstColorFactor.A = ( 1.f - SrcColor.A );
            break;

        case D3DBLEND_SRCALPHA:
            DstColorFactor.SetAllChannels( SrcColor.A );
            break;

        case D3DBLEND_INVSRCALPHA:
            DstColorFactor.SetAllChannels( 1.f - SrcColor.A );
            break;

        case D3DBLEND_DESTALPHA:
            DstColorFactor.SetAllChannels( DstColor.A );
            break;

        case D3DBLEND_INVDESTALPHA:
            DstColorFactor.SetAllChannels( 1.f - DstColor.A );
            break;

        case D3DBLEND_DESTCOLOR:
            DstColorFactor.R = DstColor.R;
            DstColorFactor.G = DstColor.G;
            DstColorFactor.B = DstColor.B;
            DstColorFactor.A = DstColor.A;
            break;

        case D3DBLEND_INVDESTCOLOR:
            DstColorFactor.R = ( 1.f - DstColor.R );
            DstColorFactor.G = ( 1.f - DstColor.G );
            DstColorFactor.B = ( 1.f - DstColor.B );
            DstColorFactor.A = ( 1.f - DstColor.A );
            break;

        case D3DBLEND_SRCALPHASAT:
            {
                FLOAT F = MIN( SrcColor.A, 1.f - DstColor.A );
                DstColorFactor.R = F;
                DstColorFactor.G = F;
                DstColorFactor.B = F;
            }
            DstColorFactor.A = 1.F;
            break;
        }
    }

    // apply blend factors to update pixel color (MIN and MAX handled above)
    RDColor SclSrc, SclDst;
    SclSrc.R = SrcColorFactor.R * SrcColor.R;
    SclSrc.G = SrcColorFactor.G * SrcColor.G;
    SclSrc.B = SrcColorFactor.B * SrcColor.B;
    SclSrc.A = SrcColorFactor.A * SrcColor.A;
    SclDst.R = DstColorFactor.R * DstColor.R;
    SclDst.G = DstColorFactor.G * DstColor.G;
    SclDst.B = DstColorFactor.B * DstColor.B;
    SclDst.A = DstColorFactor.A * DstColor.A;
    switch ( m_pRD->GetRS()[D3DRS_BLENDOP] )
    {
    default:
    case D3DBLENDOP_ADD:
        ResColor.R = SclSrc.R + SclDst.R;
        ResColor.G = SclSrc.G + SclDst.G;
        ResColor.B = SclSrc.B + SclDst.B;
        ResColor.A = SclSrc.A + SclDst.A;
        break;
    case D3DBLENDOP_SUBTRACT:
        ResColor.R = SclSrc.R - SclDst.R;
        ResColor.G = SclSrc.G - SclDst.G;
        ResColor.B = SclSrc.B - SclDst.B;
        ResColor.A = SclSrc.A - SclDst.A;
        break;
    case D3DBLENDOP_REVSUBTRACT:
        ResColor.R = SclDst.R - SclSrc.R;
        ResColor.G = SclDst.G - SclSrc.G;
        ResColor.B = SclDst.B - SclSrc.B;
        ResColor.A = SclDst.A - SclSrc.A;
        break;
    }

    // clamp result
    ResColor.Clamp();
}

///////////////////////////////////////////////////////////////////////////////
// end