1837 lines
69 KiB
C++
1837 lines
69 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// 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();
|
|
}
|