/////////////////////////////////////////////////////////////////////////////// // Copyright (C) Microsoft Corporation, 2000. // // valbase.cpp // // Direct3D Reference Device - PixelShader validation common infrastructure // /////////////////////////////////////////////////////////////////////////////// #include "pch.cpp" #pragma hdrstop //----------------------------------------------------------------------------- // DSTPARAM::DSTPARAM //----------------------------------------------------------------------------- DSTPARAM::DSTPARAM() { m_bParamUsed = FALSE; m_RegNum = (UINT)-1; m_WriteMask = 0; m_DstMod = D3DSPDM_NONE; m_DstShift = (DSTSHIFT)-1; m_RegType = (D3DSHADER_PARAM_REGISTER_TYPE)-1; m_ComponentReadMask = 0; } //----------------------------------------------------------------------------- // SRCPARAM::SRCPARAM //----------------------------------------------------------------------------- SRCPARAM::SRCPARAM() { m_bParamUsed = FALSE; m_RegNum = (UINT)-1; m_SwizzleShift = D3DSP_NOSWIZZLE; m_AddressMode = D3DVS_ADDRMODE_ABSOLUTE; m_RelativeAddrComponent = 0; m_SrcMod = D3DSPSM_NONE; m_RegType = (D3DSHADER_PARAM_REGISTER_TYPE)-1; m_ComponentReadMask = D3DSP_WRITEMASK_ALL; } //----------------------------------------------------------------------------- // CBaseInstruction::CBaseInstruction //----------------------------------------------------------------------------- CBaseInstruction::CBaseInstruction(CBaseInstruction* pPrevInst) { m_Type = D3DSIO_NOP; m_SrcParamCount = 0; m_DstParamCount = 0; m_pPrevInst = pPrevInst; m_pNextInst = NULL; m_pSpewLineNumber = NULL; m_pSpewFileName = NULL; m_SpewInstructionCount = 0; if( pPrevInst ) { pPrevInst->m_pNextInst = this; } } //----------------------------------------------------------------------------- // CBaseInstruction::SetSpewFileNameAndLineNumber //----------------------------------------------------------------------------- void CBaseInstruction::SetSpewFileNameAndLineNumber(const char* pFileName, const DWORD* pLineNumber) { m_pSpewFileName = pFileName; m_pSpewLineNumber = pLineNumber; } //----------------------------------------------------------------------------- // CBaseInstruction::MakeInstructionLocatorString // // Don't forget to 'delete' the string returned. //----------------------------------------------------------------------------- char* CBaseInstruction::MakeInstructionLocatorString() { for(UINT Length = 128; Length < 65536; Length *= 2) { int BytesStored; char *pBuffer = new char[Length]; if( !pBuffer ) { OutputDebugString("Out of memory.\n"); return NULL; } if( m_pSpewFileName ) { BytesStored = _snprintf( pBuffer, Length, "%s(%d) : ", m_pSpewFileName, m_pSpewLineNumber ? *m_pSpewLineNumber : 1); } else { BytesStored = _snprintf( pBuffer, Length, "(Statement %d) ", m_SpewInstructionCount ); } if( BytesStored >= 0 ) return pBuffer; delete [] pBuffer; } return NULL; } //----------------------------------------------------------------------------- // CAccessHistoryNode::CAccessHistoryNode //----------------------------------------------------------------------------- CAccessHistoryNode::CAccessHistoryNode( CAccessHistoryNode* pPreviousAccess, CAccessHistoryNode* pPreviousWriter, CAccessHistoryNode* pPreviousReader, CBaseInstruction* pInst, BOOL bWrite ) { DXGASSERT(pInst); m_pNextAccess = NULL; m_pPreviousAccess = pPreviousAccess; if( m_pPreviousAccess ) m_pPreviousAccess->m_pNextAccess = this; m_pPreviousWriter = pPreviousWriter; m_pPreviousReader = pPreviousReader; m_pInst = pInst; m_bWrite = bWrite; m_bRead = !bWrite; } //----------------------------------------------------------------------------- // CAccessHistory::CAccessHistory //----------------------------------------------------------------------------- CAccessHistory::CAccessHistory() { m_pFirstAccess = NULL; m_pMostRecentAccess = NULL; m_pMostRecentWriter = NULL; m_pMostRecentReader = NULL; m_bPreShaderInitialized = FALSE; } //----------------------------------------------------------------------------- // CAccessHistory::~CAccessHistory //----------------------------------------------------------------------------- CAccessHistory::~CAccessHistory() { CAccessHistoryNode* pCurrNode = m_pFirstAccess; CAccessHistoryNode* pDeleteMe; while( pCurrNode ) { pDeleteMe = pCurrNode; pCurrNode = pCurrNode->m_pNextAccess; delete pDeleteMe; } } //----------------------------------------------------------------------------- // CAccessHistory::NewAccess //----------------------------------------------------------------------------- BOOL CAccessHistory::NewAccess(CBaseInstruction* pInst, BOOL bWrite ) { m_pMostRecentAccess = new CAccessHistoryNode( m_pMostRecentAccess, m_pMostRecentWriter, m_pMostRecentReader, pInst, bWrite ); if( NULL == m_pMostRecentAccess ) { return FALSE; // out of memory } if( m_pFirstAccess == NULL ) { m_pFirstAccess = m_pMostRecentAccess; } if( bWrite ) { m_pMostRecentWriter = m_pMostRecentAccess; } else // it is a read. { m_pMostRecentReader = m_pMostRecentAccess; } return TRUE; } //----------------------------------------------------------------------------- // CAccessHistory::InsertReadBeforeWrite //----------------------------------------------------------------------------- BOOL CAccessHistory::InsertReadBeforeWrite(CAccessHistoryNode* pWriteNode, CBaseInstruction* pInst) { DXGASSERT(pWriteNode && pWriteNode->m_bWrite && pInst ); // append new node after node before pWriteNode CAccessHistoryNode* pReadBeforeWrite = new CAccessHistoryNode( pWriteNode->m_pPreviousAccess, pWriteNode->m_pPreviousWriter, pWriteNode->m_pPreviousReader, pInst, FALSE); if( NULL == pReadBeforeWrite ) { return FALSE; // out of memory } // Patch up all the dangling pointers // Pointer to first access may change if( m_pFirstAccess == pWriteNode ) { m_pFirstAccess = pReadBeforeWrite; } // Pointer to most recent reader may change if( m_pMostRecentReader == pWriteNode->m_pPreviousReader ) { m_pMostRecentReader = pReadBeforeWrite; } // Update all m_pPreviousRead pointers that need to be updated to point to the newly // inserted read. CAccessHistoryNode* pCurrAccess = pWriteNode; while(pCurrAccess && !(pCurrAccess->m_bRead && pCurrAccess->m_pPreviousAccess && pCurrAccess->m_pPreviousAccess->m_bRead) ) { pCurrAccess->m_pPreviousReader = pReadBeforeWrite; pCurrAccess = pCurrAccess->m_pPreviousAccess; } // re-attach pWriteNode and the accesses linked after it back to the original list pWriteNode->m_pPreviousAccess = pReadBeforeWrite; pReadBeforeWrite->m_pNextAccess = pWriteNode; return TRUE; } //----------------------------------------------------------------------------- // CRegisterFile::CRegisterFile //----------------------------------------------------------------------------- CRegisterFile::CRegisterFile(UINT NumRegisters, BOOL bWritable, UINT NumReadPorts, BOOL bPreShaderInitialized) { m_bInitOk = FALSE; m_NumRegisters = NumRegisters; m_bWritable = bWritable; m_NumReadPorts = NumReadPorts; for( UINT i = 0; i < NUM_COMPONENTS_IN_REGISTER; i++ ) { if( m_NumRegisters ) { m_pAccessHistory[i] = new CAccessHistory[m_NumRegisters]; if( NULL == m_pAccessHistory[i] ) { OutputDebugString( "Direct3D Shader Validator: Out of memory.\n" ); m_NumRegisters = 0; return; } } for( UINT j = 0; j < m_NumRegisters; j++ ) { m_pAccessHistory[i][j].m_bPreShaderInitialized = bPreShaderInitialized; } // To get the access history for a component of a register, use: // m_pAccessHistory[component][register number] } } //----------------------------------------------------------------------------- // CRegisterFile::~CRegisterFile //----------------------------------------------------------------------------- CRegisterFile::~CRegisterFile() { for( UINT i = 0; i < NUM_COMPONENTS_IN_REGISTER; i++ ) { delete [] m_pAccessHistory[i]; } } //----------------------------------------------------------------------------- // CBaseShaderValidator::CBaseShaderValidator //----------------------------------------------------------------------------- CBaseShaderValidator::CBaseShaderValidator( const DWORD* pCode, const D3DCAPS8* pCaps, DWORD Flags ) { m_ReturnCode = E_FAIL; // do this first. m_bBaseInitOk = FALSE; m_pLog = new CErrorLog(Flags & SHADER_VALIDATOR_LOG_ERRORS); if( NULL == m_pLog ) { OutputDebugString("D3D PixelShader Validator: Out of memory.\n"); return; } // ---------------------------------------------------- // Member variable initialization // m_pCaps = pCaps; m_ErrorCount = 0; m_bSeenAllInstructions = FALSE; m_SpewInstructionCount = 0; m_pInstructionList = NULL; m_pCurrInst = NULL; m_pCurrToken = pCode; // can be null - vertex shader fixed function if( m_pCurrToken ) m_Version = *(m_pCurrToken++); else m_Version = 0; m_pLatestSpewLineNumber = NULL; m_pLatestSpewFileName = NULL; for( UINT i = 0; i < SHADER_INSTRUCTION_MAX_SRCPARAMS; i++ ) { m_bSrcParamError[i] = FALSE; } m_bBaseInitOk = TRUE; return; } //----------------------------------------------------------------------------- // CBaseShaderValidator::~CBaseShaderValidator //----------------------------------------------------------------------------- CBaseShaderValidator::~CBaseShaderValidator() { while( m_pCurrInst ) // Delete the linked list of instructions { CBaseInstruction* pDeleteMe = m_pCurrInst; m_pCurrInst = m_pCurrInst->m_pPrevInst; delete pDeleteMe; } delete m_pLog; } //----------------------------------------------------------------------------- // CBaseShaderValidator::DecodeDstParam //----------------------------------------------------------------------------- void CBaseShaderValidator::DecodeDstParam( DSTPARAM* pDstParam, DWORD Token ) { DXGASSERT(pDstParam); pDstParam->m_bParamUsed = TRUE; pDstParam->m_RegNum = Token & D3DSP_REGNUM_MASK; pDstParam->m_WriteMask = Token & D3DSP_WRITEMASK_ALL; pDstParam->m_DstMod = (D3DSHADER_PARAM_DSTMOD_TYPE)(Token & D3DSP_DSTMOD_MASK); pDstParam->m_DstShift = (DSTSHIFT)((Token & D3DSP_DSTSHIFT_MASK) >> D3DSP_DSTSHIFT_SHIFT ); pDstParam->m_RegType = (D3DSHADER_PARAM_REGISTER_TYPE)(Token & D3DSP_REGTYPE_MASK); } //----------------------------------------------------------------------------- // CBaseShaderValidator::DecodeSrcParam //----------------------------------------------------------------------------- void CBaseShaderValidator::DecodeSrcParam( SRCPARAM* pSrcParam, DWORD Token ) { DXGASSERT(pSrcParam); pSrcParam->m_bParamUsed = TRUE; pSrcParam->m_RegNum = Token & D3DSP_REGNUM_MASK; pSrcParam->m_SwizzleShift = Token & D3DSP_SWIZZLE_MASK; pSrcParam->m_AddressMode = (D3DVS_ADDRESSMODE_TYPE)(Token & D3DVS_ADDRESSMODE_MASK); pSrcParam->m_RelativeAddrComponent = COMPONENT_MASKS[(Token >> 14) & 0x3]; pSrcParam->m_SrcMod = (D3DSHADER_PARAM_SRCMOD_TYPE)(Token & D3DSP_SRCMOD_MASK); pSrcParam->m_RegType = (D3DSHADER_PARAM_REGISTER_TYPE)(Token & D3DSP_REGTYPE_MASK); } //----------------------------------------------------------------------------- // CBaseShaderValidator::ValidateShader //----------------------------------------------------------------------------- void CBaseShaderValidator::ValidateShader() { m_SpewInstructionCount++; // Consider the version token as the first // statement (1) for spew counting. if( !InitValidation() ) // i.e. Set up max register counts { // Returns false on: // 1) Unrecognized version token, // 2) Vertex shader declaration validation with no shader code (fixed function). // In this case InitValidation() sets m_ReturnCode as appropriate. return; } // Loop through all the instructions while( *m_pCurrToken != D3DPS_END() ) { m_pCurrInst = AllocateNewInstruction(m_pCurrInst); // New instruction in linked list if( NULL == m_pCurrInst ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Out of memory." ); return; } if( NULL == m_pInstructionList ) m_pInstructionList = m_pCurrInst; if( !DecodeNextInstruction() ) return; // Skip comments if( m_pCurrInst->m_Type == D3DSIO_COMMENT ) { CBaseInstruction* pDeleteMe = m_pCurrInst; m_pCurrInst = m_pCurrInst->m_pPrevInst; if( pDeleteMe == m_pInstructionList ) m_pInstructionList = NULL; delete pDeleteMe; continue; } for( UINT i = 0; i < SHADER_INSTRUCTION_MAX_SRCPARAMS; i++ ) { m_bSrcParamError[i] = FALSE; } // Apply all the per-instruction rules - order the rule checks sensibly. // Note: Rules only return FALSE if they find an error that is so severe that it is impossible to // continue validation. if( !ApplyPerInstructionRules() ) return; } m_bSeenAllInstructions = TRUE; // Apply any rules that also need to run after all instructions seen. // // NOTE: It is possible to get here with m_pCurrInst == NULL, if there were no // instructions. So any rules you add here must be able to account for that // possiblity. // ApplyPostInstructionsRules(); // If no errors, then success! if( 0 == m_ErrorCount ) m_ReturnCode = D3D_OK; } //----------------------------------------------------------------------------- // CBaseShaderValidator::ParseCommentForAssemblerMessages //----------------------------------------------------------------------------- void CBaseShaderValidator::ParseCommentForAssemblerMessages(const DWORD* pComment) { if( !pComment ) return; // There must be at least 2 DWORDS in the comment if( (((*(pComment++)) & D3DSI_COMMENTSIZE_MASK) >> D3DSI_COMMENTSIZE_SHIFT) < 2 ) return; switch(*(pComment++)) { case MAKEFOURCC('F','I','L','E'): m_pLatestSpewFileName = (const char*)pComment; break; case MAKEFOURCC('L','I','N','E'): m_pLatestSpewLineNumber = pComment; break; } } //----------------------------------------------------------------------------- // CBaseShaderValidator::Spew //----------------------------------------------------------------------------- void CBaseShaderValidator::Spew( SPEW_TYPE SpewType, CBaseInstruction* pInst /* can be NULL */, const char* pszFormat, ... ) { int Length = 128; char* pBuffer = NULL; va_list marker; if( !m_pLog ) return; while( pBuffer == NULL ) { int BytesStored = 0; int BytesLeft = Length; char *pIndex = NULL; char* pErrorLocationText = NULL; pBuffer = new char[Length]; if( !pBuffer ) { OutputDebugString("Out of memory.\n"); return; } pIndex = pBuffer; // Code location text switch( SpewType ) { case SPEW_INSTRUCTION_ERROR: case SPEW_INSTRUCTION_WARNING: if( pInst ) pErrorLocationText = pInst->MakeInstructionLocatorString(); break; } if( pErrorLocationText ) { BytesStored = _snprintf( pIndex, BytesLeft - 1, pErrorLocationText ); if( BytesStored < 0 ) goto OverFlow; BytesLeft -= BytesStored; pIndex += BytesStored; } // Spew text prefix switch( SpewType ) { case SPEW_INSTRUCTION_ERROR: BytesStored = _snprintf( pIndex, BytesLeft - 1, "(Validation Error) " ); break; case SPEW_GLOBAL_ERROR: BytesStored = _snprintf( pIndex, BytesLeft - 1, "(Global Validation Error) " ); break; case SPEW_INSTRUCTION_WARNING: BytesStored = _snprintf( pIndex, BytesLeft - 1, "(Validation Warning) " ); break; case SPEW_GLOBAL_WARNING: BytesStored = _snprintf( pIndex, BytesLeft - 1, "(Global Validation Warning) " ); break; } if( BytesStored < 0 ) goto OverFlow; BytesLeft -= BytesStored; pIndex += BytesStored; // Formatted text va_start( marker, pszFormat ); BytesStored = _vsnprintf( pIndex, BytesLeft - 1, pszFormat, marker ); va_end( marker ); if( BytesStored < 0 ) goto OverFlow; BytesLeft -= BytesStored; pIndex += BytesStored; m_pLog->AppendText(pBuffer); delete [] pErrorLocationText; delete [] pBuffer; break; OverFlow: delete [] pErrorLocationText; delete [] pBuffer; pBuffer = NULL; Length = Length * 2; } } //----------------------------------------------------------------------------- // CBaseShaderValidator::MakeAffectedComponentsText // // Note that the string returned is STATIC. //----------------------------------------------------------------------------- char* CBaseShaderValidator::MakeAffectedComponentsText( DWORD ComponentMask, BOOL bColorLabels, BOOL bPositionLabels) { char* ColorLabels[4] = {"r/", "g/", "b/", "a/"}; char* PositionLabels[4] = {"x/", "y/", "z/", "w/"}; char* NumericLabels[4] = {"0 ", "1 ", "2 ", "3"}; // always used static char s_AffectedComponents[28]; // enough to hold "*r/x/0 *g/y/1 *b/z/2 *a/w/3" UINT LabelCount = 0; s_AffectedComponents[0] = '\0'; for( UINT i = 0; i < 4; i++ ) { if( COMPONENT_MASKS[i] & ComponentMask ) { strcat( s_AffectedComponents, "*" ); } if( bColorLabels ) strcat( s_AffectedComponents, ColorLabels[i] ); if( bPositionLabels ) strcat( s_AffectedComponents, PositionLabels[i] ); strcat( s_AffectedComponents, NumericLabels[i] ); // always used } return s_AffectedComponents; }