///////////////////////////////////////////////////////////////////////////////
// Copyright (C) Microsoft Corporation, 2000.
//
// pshdrval.cpp
//
// Direct3D Reference Device - PixelShader validation
//
///////////////////////////////////////////////////////////////////////////////
#include "pch.cpp"
#pragma hdrstop

// Use these macros when looking at CPSInstruction derived members of the current instruction (CBaseInstruction)
#define _CURR_PS_INST   ((CPSInstruction*)m_pCurrInst)
#define _PREV_PS_INST   (m_pCurrInst?((CPSInstruction*)(m_pCurrInst->m_pPrevInst)):NULL)

//-----------------------------------------------------------------------------
// CPSInstruction::CalculateComponentReadMasks()
//
// Figure out which components of each source parameter is read by a pixelshader
// instruction.  For certain pixelshader instructions, the some components
// are also read from the dest parameter.
//
// Note: When this function is changed, the changes need to be ported to
// refrast's CalculateSourceReadMasks() function in rast\pshader.cpp
// (Though that function does not care about channels read from the dest parameter
//  like this one does).
//-----------------------------------------------------------------------------
void CPSInstruction::CalculateComponentReadMasks(DWORD dwVersion)
{
    UINT i, j;

    switch( m_Type ) // instructions that actually read from the *Destination* register...
    {
    case D3DSIO_TEXM3x2DEPTH:
    case D3DSIO_TEXDEPTH:
        m_DstParam.m_ComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1;
        break;
    case D3DSIO_TEXKILL:
        if( (D3DPS_VERSION(1,4) == dwVersion) && (D3DSPR_TEMP == m_DstParam.m_RegType) )
        {
            // for ps.1.4, texkill on an r# register only reads rgb
            m_DstParam.m_ComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2;
        }
        else
        {
            m_DstParam.m_ComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3;
        }
        break;
    }

    for( i = 0; i < m_SrcParamCount; i++ )
    {
        DWORD NeededComponents;
        DWORD ReadComponents = 0;

        switch( m_Type )
        {
        case D3DSIO_TEX:      // only in ps.1.4 does texld have source parameter
            if( D3DPS_VERSION(1,4) == dwVersion )
            {
                // for ps.1.4, texld has a source parameter
                NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2;
            }
            else // versions < ps.1.4 don't have a src param on tex, so we shouldn't get here.  But maybe in ps.2.0...
            {
                NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3;
            }
            break;
        case D3DSIO_TEXCOORD:
            if( D3DPS_VERSION(1,4) == dwVersion )
            {
                // for ps.1.4, texcrd has a source parameter
                NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2;
            }
            else // versions < ps.1.4 don't have a src param on texcoord, so we shouldn't get here.  But maybe in ps.2.0...
            {
                NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3;
            }
            break;
        case D3DSIO_TEXBEM:
        case D3DSIO_TEXBEML:
            NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1;
            break;
        case D3DSIO_DP3:
            NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2;
            break;
        case D3DSIO_DP4:
            NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3;
            break;
        case D3DSIO_BEM: // ps.1.4
            NeededComponents = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1;
            break;
        default: 
            // standard component-wise instruction, 
            // OR an op we know reads .rgba and we also know it will be validated to .rgba writemask
            NeededComponents = m_DstParam.m_WriteMask;
            break;
        }

        // Figure out which components of this source parameter are read (taking into account swizzle)
        for(j = 0; j < 4; j++)
        {
            if( NeededComponents & COMPONENT_MASKS[j] )
                ReadComponents |= COMPONENT_MASKS[(m_SrcParam[i].m_SwizzleShift >> (D3DVS_SWIZZLE_SHIFT + 2*j)) & 0x3];
        }
        m_SrcParam[i].m_ComponentReadMask = ReadComponents;
    }
}

//-----------------------------------------------------------------------------
// CBasePShaderValidator::CBasePShaderValidator
//-----------------------------------------------------------------------------
CBasePShaderValidator::CBasePShaderValidator(   const DWORD* pCode,
                                        const D3DCAPS8* pCaps,
                                        DWORD Flags )
                                        : CBaseShaderValidator( pCode, pCaps, Flags )
{
    // Note that the base constructor initialized m_ReturnCode to E_FAIL.
    // Only set m_ReturnCode to S_OK if validation has succeeded,
    // before exiting this constructor.

    m_CycleNum              = 0;
    m_TexOpCount            = 0;
    m_BlendOpCount          = 0;
    m_TotalOpCount          = 0;

    m_pTempRegFile          = NULL;
    m_pInputRegFile         = NULL;
    m_pConstRegFile         = NULL;
    m_pTextureRegFile       = NULL;

    if( !m_bBaseInitOk )
        return;
}

//-----------------------------------------------------------------------------
// CBasePShaderValidator::~CBasePShaderValidator
//-----------------------------------------------------------------------------
CBasePShaderValidator::~CBasePShaderValidator()
{
    delete m_pTempRegFile;
    delete m_pInputRegFile;
    delete m_pConstRegFile;
    delete m_pTextureRegFile;
}

//-----------------------------------------------------------------------------
// CBasePShaderValidator::AllocateNewInstruction
//-----------------------------------------------------------------------------
CBaseInstruction* CBasePShaderValidator::AllocateNewInstruction(CBaseInstruction*pPrevInst)
{
    return new CPSInstruction((CPSInstruction*)pPrevInst);
}

//-----------------------------------------------------------------------------
// CBasePShaderValidator::DecodeNextInstruction
//-----------------------------------------------------------------------------
BOOL CBasePShaderValidator::DecodeNextInstruction()
{
    m_pCurrInst->m_Type = (D3DSHADER_INSTRUCTION_OPCODE_TYPE)(*m_pCurrToken & D3DSI_OPCODE_MASK);

    if( D3DSIO_COMMENT == m_pCurrInst->m_Type )
    {
        ParseCommentForAssemblerMessages(m_pCurrToken); // does not advance m_pCurrToken

        // Skip comments
        DWORD NumDWORDs = ((*m_pCurrToken) & D3DSI_COMMENTSIZE_MASK) >> D3DSI_COMMENTSIZE_SHIFT;
        m_pCurrToken += (NumDWORDs+1);
        return TRUE;
    }

    // Find out if the instruction is a TexOp and/or TexMOp.  Needed by multiple validation rules,
    // as well as further below in DecodeNextInstruction.
    IsCurrInstTexOp();

    // If the assembler has sent us file and/or line number messages,
    // received by ParseCommentForAssemblerMesssages(),
    // then bind this information to the current instruction.
    // This info can be used in error spew to direct the shader developer
    // to exactly where a problem is located.
    m_pCurrInst->SetSpewFileNameAndLineNumber(m_pLatestSpewFileName,m_pLatestSpewLineNumber);

    if( *m_pCurrToken & D3DSI_COISSUE )
    {
        _CURR_PS_INST->m_bCoIssue = TRUE;
    }
    else if( D3DSIO_NOP != m_pCurrInst->m_Type )
    {
        m_CycleNum++; // First cycle is 1. (co-issued instructions will have same cycle number)
    }
    _CURR_PS_INST->m_CycleNum = m_CycleNum;

    m_SpewInstructionCount++; // only used for spew, not for any limits
    m_pCurrInst->m_SpewInstructionCount = m_SpewInstructionCount;

    DWORD dwReservedBits = PS_INST_TOKEN_RESERVED_MASK;

    if( (*m_pCurrToken) & dwReservedBits )
    {
        Spew(SPEW_INSTRUCTION_ERROR,m_pCurrInst,"Reserved bit(s) set in instruction parameter token!  Aborting validation.");
        return FALSE;
    }

    m_pCurrToken++;

    // Decode dst param
    if (*m_pCurrToken & (1L<<31))
    {
        (m_pCurrInst->m_DstParamCount)++;
        DecodeDstParam( &m_pCurrInst->m_DstParam, *m_pCurrToken );
        if( (*m_pCurrToken) & PS_DSTPARAM_TOKEN_RESERVED_MASK )
        {
            Spew(SPEW_INSTRUCTION_ERROR,m_pCurrInst,"Reserved bit(s) set in destination parameter token!  Aborting validation.");
            return FALSE;
        }
        m_pCurrToken++;
        if( D3DSIO_DEF == m_pCurrInst->m_Type )
        {
            // Skip source params (float vector) - nothing to check
            // This is the only instruction with 4 source params,
            // and further, this is the only instruction that has
            // raw numbers as parameters.  This justifies the
            // special case treatment here - we pretend
            // D3DSIO_DEF only has a dst param (which we will check).
            m_pCurrToken += 4;
            return TRUE;
        }
    }

    // Decode src param(s)
    while (*m_pCurrToken & (1L<<31))
    {
        (m_pCurrInst->m_SrcParamCount)++;
        if( (m_pCurrInst->m_SrcParamCount + m_pCurrInst->m_DstParamCount) > SHADER_INSTRUCTION_MAX_PARAMS )
        {
            m_pCurrInst->m_SrcParamCount--;
            m_pCurrToken++; // eat up extra parameters and skip to next
            continue;
        }

        // Below: index is [SrcParamCount - 1] because m_SrcParam array needs 0 based index.
        DecodeSrcParam( &(m_pCurrInst->m_SrcParam[m_pCurrInst->m_SrcParamCount - 1]),*m_pCurrToken );

        if( (*m_pCurrToken) & PS_SRCPARAM_TOKEN_RESERVED_MASK )
        {
            Spew(SPEW_INSTRUCTION_ERROR,m_pCurrInst,"Reserved bit(s) set in source %d parameter token!  Aborting validation.",
                            m_pCurrInst->m_SrcParamCount);
            return FALSE;
        }
        m_pCurrToken++;
    }

    // Figure out which components of each source operand actually need to be read,
    // taking into account destination write mask, the type of instruction, source swizzle, etc.
    // (must be after IsCurrInstTexOp() )
    m_pCurrInst->CalculateComponentReadMasks(m_Version);

    return TRUE;
}

//-----------------------------------------------------------------------------
//
// CBasePShaderValidator Wrapper Functions
//
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// GetNewPSValidator
//
// Called by ValidatePixelShaderInternal and ValidatePixelShader below.
//-----------------------------------------------------------------------------
CBasePShaderValidator* GetNewPSValidator( const DWORD* pCode,
                                              const D3DCAPS8* pCaps,
                                              const DWORD Flags )
{
    if( !pCode )
        return NULL;
    else if( D3DPS_VERSION(1,4) > *pCode )
        return new CPShaderValidator10(pCode,pCaps,Flags);
    else
        return new CPShaderValidator14(pCode,pCaps,Flags);
}

//-----------------------------------------------------------------------------
// ValidatePixelShaderInternal
//-----------------------------------------------------------------------------
BOOL ValidatePixelShaderInternal( const DWORD* pCode, const D3DCAPS8* pCaps )
{
    CBasePShaderValidator * pValidator = NULL;
    BOOL bSuccess = FALSE;

    pValidator = GetNewPSValidator( pCode, pCaps, 0 );
    if( NULL == pValidator )
    {
        OutputDebugString("Out of memory.\n");
        return bSuccess;
    }
    bSuccess = SUCCEEDED(pValidator->GetStatus()) ? TRUE : FALSE;
    delete pValidator;
    return bSuccess;
}

//-----------------------------------------------------------------------------
// ValidatePixelShader
//
// Don't forget to call "free" on the buffer returned in ppBuf.
//-----------------------------------------------------------------------------
HRESULT WINAPI ValidatePixelShader( const DWORD* pCode,
                                    const D3DCAPS8* pCaps,
                                    const DWORD Flags,
                                    char** const ppBuf )
{
    CBasePShaderValidator * pValidator = NULL;
    HRESULT hr;

    pValidator = GetNewPSValidator( pCode, pCaps, Flags );
    if( NULL == pValidator )
    {
        OutputDebugString("Out of memory.\n");
        return E_FAIL;
    }
    if( ppBuf )
    {
        *ppBuf = (char*)HeapAlloc(GetProcessHeap(), 0, pValidator->GetRequiredLogBufferSize());
        if( NULL == *ppBuf )
            OutputDebugString("Out of memory.\n");
        else
            pValidator->WriteLogToBuffer(*ppBuf);
    }
    hr = pValidator->GetStatus();
    delete pValidator;
    return hr;
}