|
|
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) Microsoft Corporation, 2000.
//
// vshdrval.cpp
//
// Direct3D Reference Device - VertexShader validation
//
///////////////////////////////////////////////////////////////////////////////
#include "pch.cpp"
#pragma hdrstop
// Use these macros when looking at CVSInstruction derived members of the current instruction (CBaseInstruction)
#define _CURR_VS_INST ((CVSInstruction*)m_pCurrInst)
#define _PREV_VS_INST (m_pCurrInst?((CVSInstruction*)(m_pCurrInst->m_pPrevInst)):NULL)
//-----------------------------------------------------------------------------
// VertexShader Validation Rule Coverage
//
// Below is the list of rules in "DX8 VertexShader Version Specification",
// matched to the function(s) in this file which enforce them.
// Note that the mapping from rules to funtions can be 1->n or n->1
//
// Generic Rules
// -------------
//
// VS-G1: Rule_oPosWritten
// VS-G2: Rule_ValidAddressRegWrite
//
// Vertex Shader Version 1.0 Rules
// ------------------------------
//
// VS.1.0-1: Rule_ValidAddressRegWrite
//
// Vertex Shader Version 1.1 Rules
// ------------------------------
//
// VS.1.1-1: Rule_ValidInstructionCount
// VS.1.1-2: Rule_ValidAddressRegWrite, Rule_ValidSrcParams
// VS.1.1-3: Rule_ValidFRCInstruction
// VS.1.1-4: ?
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// CVSInstruction::CalculateComponentReadMasks(DWORD dwVersion)
//-----------------------------------------------------------------------------
void CVSInstruction::CalculateComponentReadMasks(DWORD dwVersion) { for( UINT i = 0; i < m_SrcParamCount; i++ ) { DWORD PostSwizzleComponentReadMask = 0; switch( m_Type ) { case D3DSIO_ADD: case D3DSIO_FRC: case D3DSIO_MAD: case D3DSIO_MAX: case D3DSIO_MIN: case D3DSIO_MOV: case D3DSIO_MUL: case D3DSIO_SLT: case D3DSIO_SGE: PostSwizzleComponentReadMask = m_DstParam.m_WriteMask; // per-component ops.
break; case D3DSIO_DP3: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2; break; case D3DSIO_DP4: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3; break; case D3DSIO_LIT: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_3; break; case D3DSIO_DST: if( 0 == i ) PostSwizzleComponentReadMask = D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2; else if( 1 == i ) PostSwizzleComponentReadMask = D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_3; break; case D3DSIO_EXP: case D3DSIO_LOG: case D3DSIO_EXPP: case D3DSIO_LOGP: case D3DSIO_RCP: case D3DSIO_RSQ: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_3; break; case D3DSIO_M3x2: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2; break; case D3DSIO_M3x3: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2; break; case D3DSIO_M3x4: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2; break; case D3DSIO_M4x3: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3; break; case D3DSIO_M4x4: PostSwizzleComponentReadMask = D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1 | D3DSP_WRITEMASK_2 | D3DSP_WRITEMASK_3; break; case D3DSIO_NOP: default: break; }
// Now that we know which components of the source will be used by the instruction,
// we need to figure out which components of the actual source register need to be read to provide the data,
// taking into account source component swizzling.
m_SrcParam[i].m_ComponentReadMask = 0; for( UINT j = 0; j < 4; j++ ) { if( PostSwizzleComponentReadMask & COMPONENT_MASKS[j] ) m_SrcParam[i].m_ComponentReadMask |= COMPONENT_MASKS[(m_SrcParam[i].m_SwizzleShift >> (D3DVS_SWIZZLE_SHIFT + j*2)) & 3]; } } }
//-----------------------------------------------------------------------------
// CVShaderValidator::CVShaderValidator
//-----------------------------------------------------------------------------
CVShaderValidator::CVShaderValidator( const DWORD* pCode, const DWORD* pDecl, 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_pDecl = pDecl; m_bFixedFunction = pDecl && !pCode; if( pCaps ) { m_dwMaxVertexShaderConst = pCaps->MaxVertexShaderConst; m_bIgnoreConstantInitializationChecks = FALSE; } else { m_dwMaxVertexShaderConst = 0; m_bIgnoreConstantInitializationChecks = TRUE; }
m_pTempRegFile = NULL; m_pInputRegFile = NULL; m_pConstRegFile = NULL; m_pAddrRegFile = NULL; m_pTexCrdOutputRegFile = NULL; m_pAttrOutputRegFile = NULL; m_pRastOutputRegFile = NULL;
if( NULL == pCode && NULL == pDecl ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Version Token: Code and declaration pointers passed into shader vertex shader validator cannot both be NULL."); return; }
if( !m_bBaseInitOk ) return;
ValidateShader(); // If successful, m_ReturnCode will be set to S_OK.
// Call GetStatus() on this object to determine validation outcome.
}
//-----------------------------------------------------------------------------
// CVShaderValidator::~CVShaderValidator
//-----------------------------------------------------------------------------
CVShaderValidator::~CVShaderValidator() { delete m_pTempRegFile; delete m_pInputRegFile; delete m_pConstRegFile; delete m_pAddrRegFile; delete m_pTexCrdOutputRegFile; delete m_pAttrOutputRegFile; delete m_pRastOutputRegFile; }
//-----------------------------------------------------------------------------
// CVShaderValidator::AllocateNewInstruction
//-----------------------------------------------------------------------------
CBaseInstruction* CVShaderValidator::AllocateNewInstruction(CBaseInstruction*pPrevInst) { return new CVSInstruction((CVSInstruction*)pPrevInst); }
//-----------------------------------------------------------------------------
// CVShaderValidator::DecodeNextInstruction
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::DecodeNextInstruction() { m_pCurrInst->m_Type = (D3DSHADER_INSTRUCTION_OPCODE_TYPE)(*m_pCurrToken & D3DSI_OPCODE_MASK);
if( m_pCurrInst->m_Type == D3DSIO_COMMENT ) { 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; }
// 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);
m_SpewInstructionCount++; // only used for spew, not for any limits
m_pCurrInst->m_SpewInstructionCount = m_SpewInstructionCount;
DWORD dwReservedBits = VS_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) & VS_DSTPARAM_TOKEN_RESERVED_MASK ) { Spew(SPEW_INSTRUCTION_ERROR,m_pCurrInst,"Reserved bit(s) set in destination parameter token! Aborting validation."); return FALSE; } m_pCurrToken++; }
// Decode src param(s)
while (*m_pCurrToken & (1L<<31)) { (m_pCurrInst->m_SrcParamCount)++; if( (m_pCurrInst->m_DstParamCount + m_pCurrInst->m_SrcParamCount) > 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) & VS_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.
m_pCurrInst->CalculateComponentReadMasks(m_Version);
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::InitValidation
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::InitValidation() { if( m_bFixedFunction ) { m_pTempRegFile = new CRegisterFile(0,FALSE,0,TRUE);// #regs, bWritable, max# reads/instruction, pre-shader initialized
m_pInputRegFile = new CRegisterFile(17,FALSE,0,TRUE); m_pConstRegFile = new CRegisterFile(0,FALSE,0,TRUE); m_pAddrRegFile = new CRegisterFile(0,FALSE,0,TRUE); m_pTexCrdOutputRegFile = new CRegisterFile(0,FALSE,0,TRUE); m_pAttrOutputRegFile = new CRegisterFile(0,FALSE,0,TRUE); m_pRastOutputRegFile = new CRegisterFile(0,FALSE,0,TRUE); } else { if( m_pCaps ) { if( (m_pCaps->VertexShaderVersion & 0x0000FFFF) < (m_Version & 0x0000FFFF) ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Version Token: Vertex shader version %d.%d is too high for device. Maximum supported version is %d.%d. Aborting shader validation.", D3DSHADER_VERSION_MAJOR(m_Version),D3DSHADER_VERSION_MINOR(m_Version), D3DSHADER_VERSION_MAJOR(m_pCaps->VertexShaderVersion),D3DSHADER_VERSION_MINOR(m_pCaps->VertexShaderVersion)); return FALSE; } }
switch( m_Version >> 16 ) { case 0xffff: Spew( SPEW_GLOBAL_ERROR, NULL, "Version token: 0x%x indicates a pixel shader. Vertex shader version token must be of the form 0xfffe****.", m_Version); return FALSE; case 0xfffe: break; // vertexshader - ok.
default: Spew( SPEW_GLOBAL_ERROR, NULL, "Version Token: 0x%x is invalid. Vertex shader version token must be of the form 0xfffe****. Aborting vertex shader validation.", m_Version); return FALSE; }
switch(m_Version) { case D3DVS_VERSION(1,0): // DX8
m_pTempRegFile = new CRegisterFile(12,TRUE,3,FALSE);// #regs, bWritable, max# reads/instruction, pre-shader initialized
m_pInputRegFile = new CRegisterFile(16,FALSE,1,TRUE); if( m_bIgnoreConstantInitializationChecks ) m_pConstRegFile = new CRegisterFile(0,FALSE,1,TRUE); // still creating register file so we can validate number of read ports
else m_pConstRegFile = new CRegisterFile(m_dwMaxVertexShaderConst,FALSE,1,TRUE); m_pAddrRegFile = new CRegisterFile(0,TRUE,0,FALSE); m_pTexCrdOutputRegFile = new CRegisterFile(8,TRUE,0,FALSE); m_pAttrOutputRegFile = new CRegisterFile(2,TRUE,0,FALSE); m_pRastOutputRegFile = new CRegisterFile(3,TRUE,0,FALSE); break; case D3DVS_VERSION(1,1): // DX8
m_pTempRegFile = new CRegisterFile(12,TRUE,3,FALSE);// #regs, bWritable, max# reads/instruction, pre-shader initialized
m_pInputRegFile = new CRegisterFile(16,FALSE,1,TRUE); if( m_bIgnoreConstantInitializationChecks ) m_pConstRegFile = new CRegisterFile(0,FALSE,1,TRUE); // still creating register file so we can validate number of read ports
else m_pConstRegFile = new CRegisterFile(m_dwMaxVertexShaderConst,FALSE,1,TRUE); m_pAddrRegFile = new CRegisterFile(1,TRUE,0,FALSE); m_pTexCrdOutputRegFile = new CRegisterFile(8,TRUE,0,FALSE); m_pAttrOutputRegFile = new CRegisterFile(2,TRUE,0,FALSE); m_pRastOutputRegFile = new CRegisterFile(3,TRUE,0,FALSE); break; default: Spew( SPEW_GLOBAL_ERROR, NULL, "Version Token: %d.%d is not a supported vertex shader version. Aborting vertex shader validation.", D3DSHADER_VERSION_MAJOR(m_Version),D3DSHADER_VERSION_MINOR(m_Version)); return FALSE; }
}
if( NULL == m_pTempRegFile || NULL == m_pInputRegFile || NULL == m_pConstRegFile || NULL == m_pAddrRegFile || NULL == m_pTexCrdOutputRegFile || NULL == m_pAttrOutputRegFile || NULL == m_pRastOutputRegFile ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Out of memory."); return FALSE; }
ValidateDeclaration(); // no matter what happens here, we can continue checking shader code, if present.
if( m_bFixedFunction ) // no shader code - fixed function, so we only validate declaration
{ if( 0 == m_ErrorCount ) m_ReturnCode = S_OK;
return FALSE; // returning false just makes validation stop here (not for indicating success/failure of validation)
}
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::ValidateDeclaration
//-----------------------------------------------------------------------------
void CVShaderValidator::ValidateDeclaration() { if( !m_pDecl ) // no shader declaration passed in.
return;
DXGASSERT(m_pInputRegFile);
typedef struct _NORMAL_GEN { UINT DestReg; UINT SourceReg; UINT TokenNum; } NORMAL_GEN;
const DWORD* pCurrToken = m_pDecl; DWORD MaxStreams = 0; UINT TokenNum = 1; UINT NumInputRegs = m_pInputRegFile->GetNumRegs(); BOOL bInStream = FALSE; BOOL* pVertexStreamDeclared = NULL; BOOL bInTessStream = FALSE; BOOL bTessStreamDeclared = FALSE; BOOL bAtLeastOneDataDefinition = FALSE; NORMAL_GEN* pNormalGenOperations = new NORMAL_GEN[m_pInputRegFile->GetNumRegs()]; UINT NumNormalGenOperations = 0; BOOL bErrorInForLoop = FALSE;
if( NULL == pNormalGenOperations ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Out of memory. Aborting shader decl. validation."); m_ErrorCount++; goto Exit; } DXGASSERT(m_pConstRegFile && m_pInputRegFile); // if we have a declaration, we better have these two register files
DXGASSERT(!m_bIgnoreConstantInitializationChecks); // we better have d3d8 caps if we have a decl to verify!
if( m_pCaps ) // only validate stream numbers when caps present
{ MaxStreams = m_pCaps->MaxStreams; if( MaxStreams > 0 ) { pVertexStreamDeclared = new BOOL[MaxStreams]; if( NULL == pVertexStreamDeclared ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Out of memory. Aborting shader decl. validation."); m_ErrorCount++; goto Exit; }
for( UINT i = 0; i < MaxStreams; i++ ) pVertexStreamDeclared[i] = FALSE; } }
// The constructor for the input register file assumed that the input regs were initialized,
// but now that we are parsing a shader declaration,
// we can check initialization of input registers.
for( UINT i = 0; i < 4; i++ ) { for( UINT j = 0; j < m_pInputRegFile->GetNumRegs(); j++ ) m_pInputRegFile->m_pAccessHistory[i][j].m_bPreShaderInitialized = FALSE; } // Now parse the declaration.
while( D3DVSD_END() != *pCurrToken ) { DWORD Token = *pCurrToken; switch( (Token & D3DVSD_TOKENTYPEMASK) >> D3DVSD_TOKENTYPESHIFT ) { case D3DVSD_TOKEN_NOP: break; case D3DVSD_TOKEN_STREAM: { UINT StreamNum = (Token & D3DVSD_STREAMNUMBERMASK) >> D3DVSD_STREAMNUMBERSHIFT; bInTessStream = (Token & D3DVSD_STREAMTESSMASK) >> D3DVSD_STREAMTESSSHIFT; bInStream = !bInTessStream; bAtLeastOneDataDefinition = FALSE;
if( bInStream ) { if( m_pCaps && (StreamNum >= MaxStreams) ) { if( MaxStreams ) Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream number %d is out of range. Max allowed is %d. Aborting shader decl. validation.", TokenNum, StreamNum, m_pCaps->MaxStreams - 1); else Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream number %d is out of range. There are no streams available. Aborting shader decl. validation.", TokenNum, StreamNum, m_pCaps->MaxStreams - 1);
m_ErrorCount++; goto Exit; } } else if( StreamNum > 0 ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream number must not be specified for tesselator stream.", TokenNum); m_ErrorCount++; }
if( bInStream && pVertexStreamDeclared ) { if( TRUE == pVertexStreamDeclared[StreamNum] ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream number %d has already been declared. Aborting shader decl. validation.", TokenNum, StreamNum ); m_ErrorCount++; goto Exit; } pVertexStreamDeclared[StreamNum] = TRUE; }
if( bInTessStream ) { if( bTessStreamDeclared ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Tesselation stream has already been declared. Aborting shader decl. validation.", TokenNum); m_ErrorCount++; goto Exit; } bTessStreamDeclared = TRUE; }
break; } case D3DVSD_TOKEN_STREAMDATA: if( !bInStream ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Cannot set stream input without first setting stream #. Aborting shader decl. validation.", TokenNum); m_ErrorCount++; goto Exit; } if( (Token & D3DVSD_DATALOADTYPEMASK) >> D3DVSD_DATALOADTYPESHIFT ) // SKIP
{ if( m_bFixedFunction ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: SKIP not permitted in fixed-function declarations.", TokenNum); m_ErrorCount++; break; } } else { UINT RegNum = (Token & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT; if( RegNum >= m_pInputRegFile->GetNumRegs() ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Invalid reg num: %d. Max allowed is %d.", TokenNum, RegNum, m_pInputRegFile->GetNumRegs() - 1); m_ErrorCount++; break; } switch( (Token & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT ) { case D3DVSDT_FLOAT1: case D3DVSDT_FLOAT2: case D3DVSDT_FLOAT3: case D3DVSDT_FLOAT4: case D3DVSDT_D3DCOLOR: case D3DVSDT_UBYTE4: case D3DVSDT_SHORT2: case D3DVSDT_SHORT4: break; default: Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Unrecognized stream data type.", TokenNum); m_ErrorCount++; break; }
bErrorInForLoop = FALSE; for( UINT i = 0; i < 4; i++ ) { if( TRUE == m_pInputRegFile->m_pAccessHistory[i][RegNum].m_bPreShaderInitialized ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Input register %d already declared.", TokenNum, RegNum); m_ErrorCount++; bErrorInForLoop = TRUE; break; } m_pInputRegFile->m_pAccessHistory[i][RegNum].m_bPreShaderInitialized = TRUE; } if( bErrorInForLoop ) break;
bAtLeastOneDataDefinition = TRUE; } break; case D3DVSD_TOKEN_TESSELLATOR: { if( !bInTessStream ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Cannot set tesselator stream input without first setting tesselator stream #. Aborting shader decl. validation.", TokenNum); m_ErrorCount++; goto Exit; }
DWORD InRegNum = (Token & D3DVSD_VERTEXREGINMASK) >> D3DVSD_VERTEXREGINSHIFT; DWORD RegNum = (Token & D3DVSD_VERTEXREGMASK) >> D3DVSD_VERTEXREGSHIFT; BOOL bNormalGen = !(Token & 0x10000000); // TODO: Why isnt there a const for this in the d3d api headers?
if( RegNum >= m_pInputRegFile->GetNumRegs() ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Invalid reg num: %d. Max allowed is %d.", TokenNum, RegNum, m_pInputRegFile->GetNumRegs() - 1); m_ErrorCount++; break; } if( bNormalGen ) { if( InRegNum >= m_pInputRegFile->GetNumRegs() ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Invalid input reg num: %d. Max allowed is %d.", TokenNum, InRegNum, m_pInputRegFile->GetNumRegs() - 1); m_ErrorCount++; break; }
bErrorInForLoop = FALSE; for( UINT i = 0; i < NumNormalGenOperations; i++ ) { if( pNormalGenOperations[i].DestReg == RegNum ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Duplicate declaration of input register %d as destination for normal generation.", TokenNum, RegNum ); m_ErrorCount++; bErrorInForLoop = TRUE; break; } } if( bErrorInForLoop ) break;
// Defer checking of initialization of inputs for normal gen until the entire declaration has been seen.
// Also, defer setting of normal gen destination reg. to initialized,
// in order to disallow normal generation loops.
pNormalGenOperations[NumNormalGenOperations].DestReg = RegNum; pNormalGenOperations[NumNormalGenOperations].SourceReg = InRegNum; pNormalGenOperations[NumNormalGenOperations].TokenNum = TokenNum; // used later for spew
NumNormalGenOperations++; } else { if( ((Token & D3DVSD_DATATYPEMASK) >> D3DVSD_DATATYPESHIFT) != D3DVSDT_FLOAT2 ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Tess datatype must be FLOAT2 for UV generation.", TokenNum); m_ErrorCount++; break; }
if( InRegNum > 0 ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Input register number must not be specified (does not apply) for UV tesselation.", TokenNum); m_ErrorCount++; break; }
for( UINT i = 0; i < 4; i++ ) { if( TRUE == m_pInputRegFile->m_pAccessHistory[i][RegNum].m_bPreShaderInitialized ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Input register %d already declared.", TokenNum, RegNum); m_ErrorCount++; break; } m_pInputRegFile->m_pAccessHistory[i][RegNum].m_bPreShaderInitialized = TRUE; } } bAtLeastOneDataDefinition = TRUE; break; } case D3DVSD_TOKEN_CONSTMEM: { DWORD ConstCount = (Token & D3DVSD_CONSTCOUNTMASK) >> D3DVSD_CONSTCOUNTSHIFT; DWORD MaxOffset = ((Token & D3DVSD_CONSTADDRESSMASK) >> D3DVSD_CONSTADDRESSSHIFT) + ConstCount; DWORD NumConstRegs = m_pConstRegFile->GetNumRegs(); DXGASSERT(NumConstRegs > 0); if( (bInStream || bInTessStream) && !bAtLeastOneDataDefinition ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream selector token must be followed by at least one stream data definition token.", TokenNum); m_ErrorCount++; } if( 0 == NumConstRegs ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Write to const register %d is not valid. There are no constant registers available.", TokenNum,MaxOffset ); } else if( MaxOffset > NumConstRegs ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Write to const register %d is out of range. Max offset is %d.", TokenNum,MaxOffset,m_pConstRegFile->GetNumRegs() - 1 ); m_ErrorCount++; } pCurrToken += ConstCount*4; bInStream = bInTessStream = FALSE; break; } case D3DVSD_TOKEN_EXT: pCurrToken += ((Token & D3DVSD_EXTCOUNTMASK) >> D3DVSD_EXTCOUNTSHIFT); if( (bInStream || bInTessStream) && !bAtLeastOneDataDefinition ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Stream selector token must be followed by at least one stream data definition token.", TokenNum); m_ErrorCount++; } bInStream = bInTessStream = FALSE; break; default: Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Unrecognized stream declaration token. Aborting shader decl. validation.", TokenNum); m_ErrorCount++; goto Exit; } pCurrToken++; }
// Make sure inputs to normal gen operations have been initialized
for( UINT i = 0; i < NumNormalGenOperations; i++ ) { for( UINT Component = 0; Component < 4; Component++ ) { if( FALSE == m_pInputRegFile->m_pAccessHistory[Component][pNormalGenOperations[i].SourceReg].m_bPreShaderInitialized ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token %d: Source input register %d for normal generation has not been declared.", pNormalGenOperations[i].TokenNum, pNormalGenOperations[i].SourceReg); m_ErrorCount++; break; } } }
// Set outputs of normal gen operations to initialized
for( UINT i = 0; i < NumNormalGenOperations; i++ ) { for( UINT Component = 0; Component < 4; Component++ ) { if( TRUE == m_pInputRegFile->m_pAccessHistory[Component][pNormalGenOperations[i].DestReg].m_bPreShaderInitialized ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Declaration Token #%d: Input reg %d specified as destination for normal generation is already declared elsewhere.", pNormalGenOperations[i].TokenNum, pNormalGenOperations[i].DestReg); m_ErrorCount++; break; } m_pInputRegFile->m_pAccessHistory[Component][pNormalGenOperations[i].DestReg].m_bPreShaderInitialized = TRUE; } }
Exit: if( pVertexStreamDeclared ) delete [] pVertexStreamDeclared; if( pNormalGenOperations ) delete [] pNormalGenOperations; }
//-----------------------------------------------------------------------------
// CVShaderValidator::ApplyPerInstructionRules
//
// Returns FALSE if shader validation must terminate.
// Returns TRUE if validation may proceed to next instruction.
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::ApplyPerInstructionRules() { if( ! Rule_InstructionRecognized() ) return FALSE; // Bail completely on unrecognized instr.
if( ! Rule_InstructionSupportedByVersion() ) goto EXIT; if( ! Rule_ValidParamCount() ) goto EXIT; if( ! Rule_ValidSrcParams() ) goto EXIT; if( ! Rule_SrcInitialized() ) goto EXIT; // needs to be before ValidDstParam()
if( ! Rule_ValidAddressRegWrite() ) goto EXIT; if( ! Rule_ValidDstParam() ) goto EXIT; if( ! Rule_ValidFRCInstruction() ) goto EXIT; if( ! Rule_ValidRegisterPortUsage() ) goto EXIT; if( ! Rule_ValidInstructionCount() ) goto EXIT; EXIT: return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::ApplyPostInstructionsRules
//-----------------------------------------------------------------------------
void CVShaderValidator::ApplyPostInstructionsRules() { Rule_ValidInstructionCount(); // see if we went over the limits
Rule_oPosWritten(); }
//-----------------------------------------------------------------------------
//
// Per Instruction Rules
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_InstructionRecognized
//
// ** Rule:
// Is the instruction opcode known? (regardless of shader version)
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// FALSE when instruction not recognized.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_InstructionRecognized() { switch(m_pCurrInst->m_Type) { case D3DSIO_MOV: case D3DSIO_ADD: case D3DSIO_MAD: case D3DSIO_MUL: case D3DSIO_RCP: case D3DSIO_RSQ: case D3DSIO_DP3: case D3DSIO_DP4: case D3DSIO_MIN: case D3DSIO_MAX: case D3DSIO_SLT: case D3DSIO_SGE: case D3DSIO_EXPP: case D3DSIO_LOGP: case D3DSIO_LIT: case D3DSIO_DST: case D3DSIO_M4x4: case D3DSIO_M4x3: case D3DSIO_M3x4: case D3DSIO_M3x3: case D3DSIO_M3x2: case D3DSIO_FRC: case D3DSIO_EXP: case D3DSIO_LOG: case D3DSIO_END: case D3DSIO_NOP: return TRUE; // instruction recognized - ok.
}
// if we get here, the instruction is not recognized
Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Unrecognized instruction. Aborting vertex shader validation." ); m_ErrorCount++; return FALSE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_InstructionSupportedByVersion
//
// ** Rule:
// Is the instruction supported by the current pixel shader version?
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// FALSE when instruction not supported by version.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_InstructionSupportedByVersion() { if( D3DVS_VERSION(1,0) <= m_Version ) // 1.0 and above
{ switch(m_pCurrInst->m_Type) { case D3DSIO_MOV: case D3DSIO_ADD: case D3DSIO_MAD: case D3DSIO_MUL: case D3DSIO_RCP: case D3DSIO_RSQ: case D3DSIO_DP3: case D3DSIO_DP4: case D3DSIO_MIN: case D3DSIO_MAX: case D3DSIO_SLT: case D3DSIO_SGE: case D3DSIO_EXPP: case D3DSIO_LOGP: case D3DSIO_LIT: case D3DSIO_DST: case D3DSIO_M4x4: case D3DSIO_M4x3: case D3DSIO_M3x4: case D3DSIO_M3x3: case D3DSIO_M3x2: case D3DSIO_FRC: case D3DSIO_EXP: case D3DSIO_LOG: return TRUE; // instruction supported - ok.
} }
switch(m_pCurrInst->m_Type) { case D3DSIO_END: case D3DSIO_NOP: return TRUE; // instruction supported - ok.
}
// if we get here, the instruction is not supported.
Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Instruction not supported by version %d.%d vertex shader.", D3DSHADER_VERSION_MAJOR(m_Version),D3DSHADER_VERSION_MINOR(m_Version)); m_ErrorCount++; return FALSE; // no more checks on this instruction
}
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidParamCount
//
// ** Rule:
// Is the parameter count correct for the instruction?
// The count includes dest + source parameters.
//
// DEF is a special case that is treated as having only 1 dest parameter,
// even though there are also 4 source parameters. The 4 source params for DEF
// are immediate float values, so there is nothing to check, and no way of
// knowing whether or not those parameter tokens were actually present in the
// token list - all the validator can do is skip over 4 DWORDS (which it does).
//
// ** When to call:
// Per instruction.
//
// ** Returns:
//
// FALSE when the parameter count is incorrect.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidParamCount() { BOOL bBadParamCount = FALSE;
if ((m_pCurrInst->m_DstParamCount + m_pCurrInst->m_SrcParamCount) > SHADER_INSTRUCTION_MAX_PARAMS) bBadParamCount = TRUE; switch (m_pCurrInst->m_Type) { case D3DSIO_NOP: bBadParamCount = (m_pCurrInst->m_DstParamCount != 0) || (m_pCurrInst->m_SrcParamCount != 0); break; case D3DSIO_EXP: case D3DSIO_EXPP: case D3DSIO_FRC: case D3DSIO_LOG: case D3DSIO_LOGP: case D3DSIO_LIT: case D3DSIO_MOV: case D3DSIO_RCP: case D3DSIO_RSQ: bBadParamCount = (m_pCurrInst->m_DstParamCount != 1) || (m_pCurrInst->m_SrcParamCount != 1); break; case D3DSIO_ADD: case D3DSIO_DP3: case D3DSIO_DP4: case D3DSIO_DST: case D3DSIO_M3x2: case D3DSIO_M3x3: case D3DSIO_M3x4: case D3DSIO_M4x3: case D3DSIO_M4x4: case D3DSIO_MAX: case D3DSIO_MIN: case D3DSIO_MUL: case D3DSIO_SGE: case D3DSIO_SLT: bBadParamCount = (m_pCurrInst->m_DstParamCount != 1) || (m_pCurrInst->m_SrcParamCount != 2); break; case D3DSIO_MAD: bBadParamCount = (m_pCurrInst->m_DstParamCount != 1) || (m_pCurrInst->m_SrcParamCount != 3); break; }
if (bBadParamCount) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid parameter count." ); m_ErrorCount++; return FALSE; // no more checks on this instruction
}
return TRUE;
}
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidSrcParams
//
// ** Rule:
// For each source parameter,
// Source register type must be D3DSPR_TEMP/_INPUT/_CONST.
// Register # must be within range for register type,
// including the special case where matrix macro ops read source reg# + offset.
// Modifier must be D3DSPSM_NONE or _NEG.
// If version is < 1.1, addressmode must be absolute.
// If the register type is not _CONST, addressmode must be absolute.
// If relative addressing is used for constants, a0.x must be referenced.
// Swizzle cannot be used for vector*matrix instructions.
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// Always TRUE.
//
// Errors in any of the source parameters causes m_bSrcParamError[i]
// to be TRUE, so later rules that only apply when a particular source
// parameter was valid know whether they need to execute or not.
// e.g. Rule_SrcInitialized.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidSrcParams() // could break this down for more granularity
{ for( UINT i = 0; i < m_pCurrInst->m_SrcParamCount; i++ ) { DXGASSERT(i < 3); BOOL bFoundSrcError = FALSE; SRCPARAM* pSrcParam = &(m_pCurrInst->m_SrcParam[i]); UINT ValidRegNum = 0; BOOL bSkipOutOfRangeCheck = FALSE; char* SourceName[3] = {"first", "second", "third"}; switch(pSrcParam->m_RegType) { case D3DSPR_TEMP: ValidRegNum = m_pTempRegFile->GetNumRegs(); break; case D3DSPR_INPUT: ValidRegNum = m_pInputRegFile->GetNumRegs(); break; case D3DSPR_CONST: if(m_bIgnoreConstantInitializationChecks) bSkipOutOfRangeCheck = TRUE; else ValidRegNum = m_pConstRegFile->GetNumRegs(); break; default: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid reg type for %s source param.", SourceName[i]); m_ErrorCount++; bFoundSrcError = TRUE; }
if( (!bFoundSrcError) && (!bSkipOutOfRangeCheck)) { UINT NumConsecutiveRegistersUsed = 1; if( 1 == i ) { switch( m_pCurrInst->m_Type ) { case D3DSIO_M3x2: NumConsecutiveRegistersUsed = 2; break; case D3DSIO_M3x3: NumConsecutiveRegistersUsed = 3; break; case D3DSIO_M3x4: NumConsecutiveRegistersUsed = 4; break; case D3DSIO_M4x3: NumConsecutiveRegistersUsed = 3; break; case D3DSIO_M4x4: NumConsecutiveRegistersUsed = 4; break; } }
if((pSrcParam->m_RegNum >= ValidRegNum) && (D3DVS_ADDRMODE_ABSOLUTE == pSrcParam->m_AddressMode)) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid reg num: %d for %s source param. Max allowed for this type is %d.", pSrcParam->m_RegNum, SourceName[i], ValidRegNum - 1); m_ErrorCount++; bFoundSrcError = TRUE; } else if( NumConsecutiveRegistersUsed > 1 ) { if( pSrcParam->m_RegNum + NumConsecutiveRegistersUsed - 1 >= ValidRegNum ) { if( !((D3DSPR_CONST == pSrcParam->m_RegType) && (D3DVS_ADDRMODE_RELATIVE == pSrcParam->m_AddressMode)) ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Reg num: %d for %s source param on matrix instruction causes attempt to access out of range register number %d. Max allowed for this type is %d.", pSrcParam->m_RegNum, SourceName[i], pSrcParam->m_RegNum + NumConsecutiveRegistersUsed - 1, ValidRegNum - 1); } m_ErrorCount++; bFoundSrcError = TRUE; } } }
switch( pSrcParam->m_SrcMod ) { case D3DSPSM_NEG: if( 1 == i ) { switch( m_pCurrInst->m_Type ) { case D3DSIO_M3x2: case D3DSIO_M3x3: case D3DSIO_M3x4: case D3DSIO_M4x3: case D3DSIO_M4x4: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Cannot negate second source parameter to vector*matrix instructions."); m_ErrorCount++; bFoundSrcError = TRUE; break; } } break; case D3DSPSM_NONE: break; default: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid src mod for %s source param.", SourceName[i]); m_ErrorCount++; bFoundSrcError = TRUE; } if( pSrcParam->m_AddressMode != D3DVS_ADDRMODE_ABSOLUTE && ( m_Version < D3DVS_VERSION(1,1) || pSrcParam->m_RegType != D3DSPR_CONST ) ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Address mode must be absolute (%s source param).", SourceName[i]); m_ErrorCount++; bFoundSrcError = TRUE; } if( (pSrcParam->m_AddressMode == D3DVS_ADDRMODE_RELATIVE) && (D3DSPR_CONST == pSrcParam->m_RegType) ) { if( pSrcParam->m_RelativeAddrComponent != D3DSP_WRITEMASK_0 ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Relative addressing of constant register must reference a0.x only.", SourceName[i]); m_ErrorCount++; bFoundSrcError = TRUE; } }
if( pSrcParam->m_SwizzleShift != D3DSP_NOSWIZZLE ) { if( 1 == i ) { switch( m_pCurrInst->m_Type ) { case D3DSIO_M3x2: case D3DSIO_M3x3: case D3DSIO_M3x4: case D3DSIO_M4x3: case D3DSIO_M4x4: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Cannot swizzle second source parameter to vector*matrix instructions."); m_ErrorCount++; bFoundSrcError = TRUE; break; } } }
if( bFoundSrcError ) { m_bSrcParamError[i] = TRUE; // needed in Rule_SrcInitialized
} }
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_SrcInitialized
//
// ** Rule:
// for each source parameter,
// The register type must be _TEMP, _INPUT or _CONST.
// Certain components of the register need to have been initialized, depending
// on what the instruction is and also taking into account the source swizzle.
// For reads of the _CONST register file, do no validation.
//
// ** When to call:
// Per instruction. This rule must be called before Rule_ValidDstParam().
//
// ** Returns:
// Always TRUE.
//
// NOTE: This rule also updates the access history to indicate reads of the
// affected components of each source register.
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_SrcInitialized() { DSTPARAM* pDstParam = &(m_pCurrInst->m_DstParam);
for( UINT i = 0; i < m_pCurrInst->m_SrcParamCount; i++ ) { SRCPARAM* pSrcParam = &(m_pCurrInst->m_SrcParam[i]); UINT RegNum = pSrcParam->m_RegNum; CRegisterFile* pRegFile = NULL; char* RegChar = NULL; UINT NumConsecutiveRegistersUsed = 1; // more than one for matrix mul macros.
DWORD RelativeAddrComponent = 0;
if( m_bSrcParamError[i] ) continue;
switch( pSrcParam->m_RegType ) { case D3DSPR_TEMP: pRegFile = m_pTempRegFile; RegChar = "r"; break; case D3DSPR_INPUT: pRegFile = m_pInputRegFile; RegChar = "v"; break; case D3DSPR_CONST: if( D3DVS_ADDRMODE_RELATIVE == pSrcParam->m_AddressMode ) { // make sure a0 was initialized.
pRegFile = m_pAddrRegFile; RegChar = "a"; RegNum = 0; RelativeAddrComponent = pSrcParam->m_RelativeAddrComponent; break; } continue; // no validation for const register reads (no need to update access history either).
} if( !pRegFile ) continue;
if( 1 == i ) { switch( m_pCurrInst->m_Type ) { case D3DSIO_M3x2: NumConsecutiveRegistersUsed = 2; break; case D3DSIO_M3x3: NumConsecutiveRegistersUsed = 3; break; case D3DSIO_M3x4: NumConsecutiveRegistersUsed = 4; break; case D3DSIO_M4x3: NumConsecutiveRegistersUsed = 3; break; case D3DSIO_M4x4: NumConsecutiveRegistersUsed = 4; break; } } // check for read of uninitialized components
for( UINT j = 0; j < (RelativeAddrComponent?1:NumConsecutiveRegistersUsed); j++ ) // will loop for macro matrix instructions
{ DWORD UninitializedComponentsMask = 0; UINT NumUninitializedComponents = 0;
for( UINT k = 0; k < 4; k++ ) { if( (RelativeAddrComponent ? RelativeAddrComponent : pSrcParam->m_ComponentReadMask) & COMPONENT_MASKS[k] ) { if( NULL == pRegFile->m_pAccessHistory[k][RegNum + j].m_pMostRecentWriter && !pRegFile->m_pAccessHistory[k][RegNum + j].m_bPreShaderInitialized ) { NumUninitializedComponents++; UninitializedComponentsMask |= COMPONENT_MASKS[k]; } }
}
if( NumUninitializedComponents ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Read of uninitialized component%s(*) in %s%d: %s", NumUninitializedComponents > 1 ? "s" : "", RegChar, RegNum + j, MakeAffectedComponentsText(UninitializedComponentsMask,FALSE,TRUE)); m_ErrorCount++; }
// Update register file to indicate READ.
// Multiple reads of the same register component by the current instruction
// will only be logged as one read in the access history.
for( UINT k = 0; k < 4; k++ ) { #define PREV_READER(_CHAN,_REG) \
((NULL == pRegFile->m_pAccessHistory[_CHAN][_REG].m_pMostRecentReader) ? NULL :\ pRegFile->m_pAccessHistory[_CHAN][_REG].m_pMostRecentReader->m_pInst) if((RelativeAddrComponent ? RelativeAddrComponent : pSrcParam->m_ComponentReadMask) & COMPONENT_MASKS[k]) { if( PREV_READER(k,RegNum) != m_pCurrInst ) { if( !pRegFile->m_pAccessHistory[k][RegNum].NewAccess(m_pCurrInst,FALSE) ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Out of memory"); m_ErrorCount++; } } } } } } return TRUE; } //-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidAddressRegWrite
//
// ** Rule:
// Address register may only be written by MOV, and only for version >= 1.1.
// Register format must be a0.x
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// Always TRUE
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidAddressRegWrite() { DSTPARAM* pDstParam = &(m_pCurrInst->m_DstParam);
if( pDstParam->m_bParamUsed ) { if( D3DSPR_ADDR == pDstParam->m_RegType ) { if( m_Version < D3DVS_VERSION(1,1) ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Address register not available for vertex shader version %d.%d. Version 1.1 required.", D3DSHADER_VERSION_MAJOR(m_Version),D3DSHADER_VERSION_MINOR(m_Version) ); m_ErrorCount++; } if( D3DSIO_MOV == m_pCurrInst->m_Type ) { if( 0 != pDstParam->m_RegNum || D3DSP_WRITEMASK_0 != pDstParam->m_WriteMask || D3DSPDM_NONE != pDstParam->m_DstMod || DSTSHIFT_NONE != pDstParam->m_DstShift ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Format for address register must be a0.x." ); m_ErrorCount++; } } else { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Only the mov instruction is allowed to write to the address register." ); m_ErrorCount++; } } } return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidDstParam
//
// ** Rule:
// Dst register type must be temp/addr/rastout/attrout/texcrdout,
// and reg num must be within range for register type.
//
// There can be no dst modifiers or shifts with vertex shaders.
//
// The writemask cannot be 'none'.
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// Always TRUE.
//
// NOTE: After checking the dst parameter, if no error was found,
// the write to the appropriate component(s) of the destination register
// is recorded by this function, so subsequent rules may check for previous
// write to registers.
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidDstParam() // could break this down for more granularity
{ BOOL bFoundDstError = FALSE; DSTPARAM* pDstParam = &(m_pCurrInst->m_DstParam); UINT RegNum = pDstParam->m_RegNum;
if( pDstParam->m_bParamUsed ) { UINT ValidRegNum = 0;
BOOL bWritable = FALSE; switch( pDstParam->m_RegType ) { case D3DSPR_TEMP: bWritable = m_pTempRegFile->IsWritable(); //(TRUE)
ValidRegNum = m_pTempRegFile->GetNumRegs(); break; case D3DSPR_ADDR: bWritable = m_pAddrRegFile->IsWritable(); //(TRUE)
ValidRegNum = m_pAddrRegFile->GetNumRegs(); break; case D3DSPR_RASTOUT: bWritable = m_pRastOutputRegFile->IsWritable(); //(TRUE)
ValidRegNum = m_pRastOutputRegFile->GetNumRegs(); break; case D3DSPR_ATTROUT: bWritable = m_pAttrOutputRegFile->IsWritable(); //(TRUE)
ValidRegNum = m_pAttrOutputRegFile->GetNumRegs(); break; case D3DSPR_TEXCRDOUT: bWritable = m_pTexCrdOutputRegFile->IsWritable(); //(TRUE)
ValidRegNum = m_pTexCrdOutputRegFile->GetNumRegs(); break; }
if( !bWritable || !ValidRegNum ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid reg type for dest param." ); m_ErrorCount++; bFoundDstError = TRUE; } else if( RegNum >= ValidRegNum ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Invalid dest reg num: %d. Max allowed for this reg type is %d.", RegNum, ValidRegNum - 1); m_ErrorCount++; bFoundDstError = TRUE; }
switch( pDstParam->m_DstMod ) { case D3DSPDM_NONE: break; default: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Dst modifiers not allowed for vertex shaders." ); m_ErrorCount++; bFoundDstError = TRUE; }
switch( pDstParam->m_DstShift ) { case DSTSHIFT_NONE: break; default: Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Dest shifts not allowed for vertex shaders." ); m_ErrorCount++; bFoundDstError = TRUE; }
if( 0 == pDstParam->m_WriteMask ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Dest write mask cannot be empty." ); m_ErrorCount++; bFoundDstError = TRUE; }
// Update register file to indicate write.
if( !bFoundDstError ) { CRegisterFile* pRegFile = NULL; switch( pDstParam->m_RegType ) { case D3DSPR_TEMP: pRegFile = m_pTempRegFile; break; case D3DSPR_ADDR: pRegFile = m_pAddrRegFile; break; case D3DSPR_RASTOUT: pRegFile = m_pRastOutputRegFile; break; case D3DSPR_ATTROUT: pRegFile = m_pAttrOutputRegFile; break; case D3DSPR_TEXCRDOUT: pRegFile = m_pTexCrdOutputRegFile; break; }
if( pRegFile ) { if( pDstParam->m_WriteMask & D3DSP_WRITEMASK_0 ) pRegFile->m_pAccessHistory[0][RegNum].NewAccess(m_pCurrInst,TRUE);
if( pDstParam->m_WriteMask & D3DSP_WRITEMASK_1 ) pRegFile->m_pAccessHistory[1][RegNum].NewAccess(m_pCurrInst,TRUE);
if( pDstParam->m_WriteMask & D3DSP_WRITEMASK_2 ) pRegFile->m_pAccessHistory[2][RegNum].NewAccess(m_pCurrInst,TRUE);
if( pDstParam->m_WriteMask & D3DSP_WRITEMASK_3 ) pRegFile->m_pAccessHistory[3][RegNum].NewAccess(m_pCurrInst,TRUE); } } }
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidFRCInstruction
//
// ** Rule:
// The only valid write masks for the FRC instruction are .y and .xy
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// Always TRUE.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidFRCInstruction() { if( NULL == m_pCurrInst ) return TRUE;
if( D3DSIO_FRC == m_pCurrInst->m_Type ) { if( ( (D3DSP_WRITEMASK_0 | D3DSP_WRITEMASK_1) != m_pCurrInst->m_DstParam.m_WriteMask ) && ( D3DSP_WRITEMASK_1 != m_pCurrInst->m_DstParam.m_WriteMask ) ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "The only valid write masks for the FRC instruction are .xy and .y." ); m_ErrorCount++; } }
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidRegisterPortUsage
//
// ** Rule:
// Each register class (TEMP,TEXTURE,INPUT,CONST) may only appear as parameters
// in an individual instruction up to a maximum number of times.
//
// In additon there is special treatment for constant registers:
// - absolute and relative addressing of constants cannot be combined
// - relative addressing of constants can be used more than once in an
// instruction, as long as each instance is identical
//
// For matrix ops,
// - multiple constant registers of any type (including relative offset)
// can never be paired as sources
// - multiple input registers (same or different) can never be paired as sources
//
// ** When to call:
// Per instruction.
//
// ** Returns:
// Always TRUE.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidRegisterPortUsage() { UINT TempRegAccessCount = 0; UINT TempRegAccess[SHADER_INSTRUCTION_MAX_SRCPARAMS]; UINT InputRegAccessCount = 0; UINT InputRegAccess[SHADER_INSTRUCTION_MAX_SRCPARAMS]; UINT ConstRegAccessCount = 0; // mad r0, c0, c0, c1 counts as *2* const reg accesses
UINT ConstRegAccess[SHADER_INSTRUCTION_MAX_SRCPARAMS];
BOOL bMatrixOp = FALSE; BOOL bSeenRelativeAddr = FALSE; UINT SeenRelativeAddrBase = 0; DWORD SeenRelativeAddrComp = 0; BOOL bSeenAbsoluteAddr = FALSE; UINT NumConsecutiveRegistersUsed = 1; UINT NumConstRegs = 0; // mad r0, c0, c0, c1 counts as *3* const reg accesses with this variable
UINT NumInputRegs = 0; // mad r0, v0, v0, v1 counts as *3* input reg accesses with this variable
switch( m_pCurrInst->m_Type ) { case D3DSIO_M3x2: NumConsecutiveRegistersUsed = 2; bMatrixOp = TRUE; break; case D3DSIO_M3x3: NumConsecutiveRegistersUsed = 3; bMatrixOp = TRUE; break; case D3DSIO_M3x4: NumConsecutiveRegistersUsed = 4; bMatrixOp = TRUE; break; case D3DSIO_M4x3: NumConsecutiveRegistersUsed = 3; bMatrixOp = TRUE; break; case D3DSIO_M4x4: NumConsecutiveRegistersUsed = 4; bMatrixOp = TRUE; break; default: break; }
for( UINT i = 0; i < SHADER_INSTRUCTION_MAX_SRCPARAMS; i++ ) { D3DSHADER_PARAM_REGISTER_TYPE RegType; UINT RegNum;
if( !m_pCurrInst->m_SrcParam[i].m_bParamUsed ) continue; RegType = m_pCurrInst->m_SrcParam[i].m_RegType; RegNum = m_pCurrInst->m_SrcParam[i].m_RegNum;
UINT* pCount = NULL; UINT* pAccess = NULL; switch( RegType ) { case D3DSPR_TEMP: pCount = &TempRegAccessCount; pAccess = TempRegAccess; break; case D3DSPR_INPUT: NumInputRegs++; pCount = &InputRegAccessCount; pAccess = InputRegAccess; break; case D3DSPR_CONST: NumConstRegs++; pCount = &ConstRegAccessCount; pAccess = ConstRegAccess;
if( D3DVS_ADDRMODE_RELATIVE == m_pCurrInst->m_SrcParam[i].m_AddressMode ) { if( bSeenAbsoluteAddr ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Absolute and relative addressing of constant registers cannot be combined in one instruction."); m_ErrorCount++; } else if( bSeenRelativeAddr && ((SeenRelativeAddrBase != RegNum) || (SeenRelativeAddrComp != m_pCurrInst->m_SrcParam[i].m_RelativeAddrComponent))) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Different relative addressing of constant registers cannot be combined in one instruction."); m_ErrorCount++; }
bSeenRelativeAddr = TRUE; SeenRelativeAddrBase = RegNum; SeenRelativeAddrComp = m_pCurrInst->m_SrcParam[i].m_RelativeAddrComponent; } else { if( bSeenRelativeAddr ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Absolute and relative addressing of constant registers cannot be combined in one instruction."); m_ErrorCount++; } bSeenAbsoluteAddr = TRUE; } break; }
if( pCount && pAccess ) { BOOL bNewRegNumberAccessed = TRUE; for( UINT j = 0; j < *pCount; j++ ) { if( RegNum == pAccess[j] ) { bNewRegNumberAccessed = FALSE; break; } } if( bNewRegNumberAccessed ) { pAccess[*pCount] = RegNum; (*pCount)++; } } }
if( TempRegAccessCount > m_pTempRegFile->GetNumReadPorts() ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "%d different temp registers (r#) read by instruction. Max. different temp registers readable per instruction is %d.", TempRegAccessCount, m_pTempRegFile->GetNumReadPorts()); m_ErrorCount++; }
if( InputRegAccessCount > m_pInputRegFile->GetNumReadPorts() ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "%d different input registers (v#) read by instruction. Max. different input registers readable per instruction is %d.", InputRegAccessCount, m_pInputRegFile->GetNumReadPorts()); m_ErrorCount++; }
if( ConstRegAccessCount > m_pConstRegFile->GetNumReadPorts() ) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "%d different constant registers (c#) read by instruction. Max. different constant registers readable per instruction is %d.", ConstRegAccessCount, m_pConstRegFile->GetNumReadPorts()); m_ErrorCount++; }
if( bMatrixOp ) { if(1 < NumConstRegs) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Multiple constant registers cannot be read by a matrix op."); m_ErrorCount++; } if(1 < NumInputRegs) { Spew( SPEW_INSTRUCTION_ERROR, m_pCurrInst, "Multiple input registers cannot be read by a matrix op."); m_ErrorCount++; } }
return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_ValidInstructionCount
//
// ** Rule:
// Make sure instruction count for vertex shader version has not been exceeded.
//
// Nop, and comments (already stripped) do not count towards the limit.
//
// ** When to call:
// Per instruction AND after all instructions seen.
//
// ** Returns:
// Always TRUE.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_ValidInstructionCount() { static UINT s_OpCount; static UINT s_MaxTotalOpCount;
if( NULL == m_pCurrInst->m_pPrevInst ) // First instruction - initialize static vars
{ s_OpCount = 0;
switch(m_Version) { case D3DVS_VERSION(1,0): case D3DVS_VERSION(1,1): default: s_MaxTotalOpCount = 128; break; } }
if( m_bSeenAllInstructions ) { if( s_OpCount > s_MaxTotalOpCount ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Number of instruction slots used too high: %d. Max. allowed is %d.", s_OpCount, s_MaxTotalOpCount); m_ErrorCount++; } return TRUE; }
switch( m_pCurrInst->m_Type ) { case D3DSIO_NOP: s_OpCount += 0; break; case D3DSIO_ADD: case D3DSIO_DP3: case D3DSIO_DP4: case D3DSIO_DST: case D3DSIO_EXPP: case D3DSIO_LIT: case D3DSIO_LOGP: case D3DSIO_MAD: case D3DSIO_MAX: case D3DSIO_MIN: case D3DSIO_MOV: case D3DSIO_MUL: case D3DSIO_RCP: case D3DSIO_RSQ: case D3DSIO_SGE: case D3DSIO_SLT: s_OpCount += 1; break; case D3DSIO_M3x2: s_OpCount += 2; break; case D3DSIO_FRC: case D3DSIO_M3x3: case D3DSIO_M4x3: s_OpCount += 3; break; case D3DSIO_M3x4: case D3DSIO_M4x4: s_OpCount += 4; break; case D3DSIO_EXP: case D3DSIO_LOG: s_OpCount += 10; break; } return TRUE; }
//-----------------------------------------------------------------------------
// CVShaderValidator::Rule_oPosWritten
//
// ** Rule:
// First two channels (x,y) of oPos output register must be written.
//
// ** When to call:
// After all instructions have been seen.
//
// ** Returns:
// Always TRUE.
//
//-----------------------------------------------------------------------------
BOOL CVShaderValidator::Rule_oPosWritten() { UINT NumUninitializedComponents = 0; DWORD UninitializedComponentsMask = 0;
for( UINT i = 0; i < 2; i++ ) // looking at component 0 (X) and component 1 (Y)
{ if( NULL == m_pRastOutputRegFile->m_pAccessHistory[i][0].m_pMostRecentWriter ) { NumUninitializedComponents++; UninitializedComponentsMask |= COMPONENT_MASKS[i]; } } if( 1 == NumUninitializedComponents ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Vertex shader must minimally write first two (x,y) components of oPos output register. Affected component%s(*): %s", NumUninitializedComponents > 1 ? "s" : "", MakeAffectedComponentsText(UninitializedComponentsMask,FALSE,TRUE)); m_ErrorCount++; } else if( 2 == NumUninitializedComponents ) { Spew( SPEW_GLOBAL_ERROR, NULL, "Vertex shader must minimally write first two (x,y) components of oPos output register."); m_ErrorCount++; }
return TRUE; }
//-----------------------------------------------------------------------------
//
// CVShaderValidator Wrapper Functions
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ValidateVertexShaderInternal
//-----------------------------------------------------------------------------
BOOL ValidateVertexShaderInternal( const DWORD* pCode, const DWORD* pDecl, const D3DCAPS8* pCaps ) { CVShaderValidator Validator(pCode,pDecl,pCaps,0); return SUCCEEDED(Validator.GetStatus()) ? TRUE : FALSE; }
//-----------------------------------------------------------------------------
// ValidateVertexShader
//
// Don't forget to call "free" on the buffer returned in ppBuf.
//-----------------------------------------------------------------------------
HRESULT WINAPI ValidateVertexShader( const DWORD* pCode, const DWORD* pDecl, const D3DCAPS8* pCaps, const DWORD Flags, char** const ppBuf ) { CVShaderValidator Validator(pCode,pDecl,pCaps,Flags); if( ppBuf ) { *ppBuf = (char*)HeapAlloc(GetProcessHeap(), 0, Validator.GetRequiredLogBufferSize()); if( NULL == *ppBuf ) OutputDebugString("Out of memory.\n"); else Validator.WriteLogToBuffer(*ppBuf); } return Validator.GetStatus(); }
|