-
Notifications
You must be signed in to change notification settings - Fork 884
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[d3d9] Refactor D3D9ShaderValidator and add config option support
- Loading branch information
1 parent
1eb0c68
commit f7f9d3c
Showing
3 changed files
with
213 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
#include "d3d9_shader_validator.h" | ||
|
||
namespace dxvk { | ||
|
||
HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::QueryInterface(REFIID riid, void** ppvObject) { | ||
if (ppvObject == nullptr) | ||
return E_POINTER; | ||
|
||
*ppvObject = ref(this); | ||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::Begin( | ||
D3D9ShaderValidatorCallback pCallback, | ||
void* pUserData, | ||
DWORD Unknown) { | ||
if (unlikely(m_state != D3D9ShaderValidatorState::Begin)) { | ||
return ErrorCallback(nullptr, -1, 0, | ||
D3D9ShaderValidatorMessage::BeginOutOfOrder, | ||
"IDirect3DShaderValidator9::Begin called out of order. ::End must be called first."); | ||
} | ||
|
||
m_callback = pCallback; | ||
m_userData = pUserData; | ||
m_state = D3D9ShaderValidatorState::ValidatingHeader; | ||
|
||
return D3D_OK; | ||
} | ||
|
||
|
||
HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::Instruction( | ||
const char* pFile, | ||
UINT Line, | ||
const DWORD* pdwInst, | ||
DWORD cdw) { | ||
if (unlikely(pdwInst == nullptr || !cdw)) { | ||
return ErrorCallback(pFile, Line, 0, | ||
D3D9ShaderValidatorMessage::InstructionNullArgs, | ||
"IDirect3DShaderValidator9::Instruction called with NULL == pdwInst or 0 == cdw."); | ||
} | ||
|
||
if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { | ||
return ErrorCallback(pFile, Line, 0, | ||
D3D9ShaderValidatorMessage::InstructionOutOfOrder, | ||
"IDirect3DShaderValidator9::Instruction called out of order. ::Begin must be called first."); | ||
} else if (unlikely(m_state == D3D9ShaderValidatorState::EndOfShader)) { | ||
return ErrorCallback(pFile, Line, 0, | ||
D3D9ShaderValidatorMessage::InstructionEndOfShader, | ||
"IDirect3DShaderValidator9::Instruction called out of order. After end token there should be no more instructions. Call ::End next."); | ||
} else if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { | ||
return E_FAIL; | ||
} | ||
|
||
if (m_state == D3D9ShaderValidatorState::ValidatingHeader) | ||
return ValidateHeader(pFile, Line, pdwInst, cdw); | ||
|
||
DxsoCodeIter pdwInstIter{ reinterpret_cast<const uint32_t*>(pdwInst) }; | ||
bool isEndToken = !m_ctx->decodeInstruction(pdwInstIter); | ||
const DxsoInstructionContext instContext = m_ctx->getInstructionContext(); | ||
|
||
if (isEndToken) | ||
return ValidateEndToken(pFile, Line, pdwInst, cdw); | ||
|
||
// TODO: DxsoDecodeContext::decodeInstructionLength() does not currently appear | ||
// to return the correct token length in many cases, and as such dwordLength | ||
// will not be equal to cdw in many situations that are expected to pass validation | ||
// | ||
/*Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: opcode ", instContext.instruction.opcode)); | ||
// + 1 to account for the opcode... | ||
uint32_t dwordLength = instContext.instruction.tokenLength + 1; | ||
Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: cdw ", cdw)); | ||
Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: dwordLength ", dwordLength)); | ||
if (unlikely(cdw != dwordLength)) { | ||
return ErrorCallback(pFile, Line, 0x2, | ||
D3D9ShaderValidatorMessage::BadInstructionLength, | ||
str::format("Instruction length specified for instruction (", cdw, ") does not match the token count encountered (", dwordLength, "). Aborting validation.")); | ||
}*/ | ||
|
||
// a maximum of 10 inputs are supported with PS 3.0 (validation required by The Void) | ||
if (m_isPixelShader && m_majorVersion == 3) { | ||
switch (instContext.instruction.opcode) { | ||
case DxsoOpcode::Comment: | ||
case DxsoOpcode::Def: | ||
case DxsoOpcode::DefB: | ||
case DxsoOpcode::DefI: | ||
break; | ||
|
||
default: | ||
// Iterate over register tokens. Bit 31 of register tokens is always 1. | ||
for (uint32_t instNum = 1; pdwInst[instNum] & 0x80000000; instNum++) { | ||
DWORD regType = ((pdwInst[instNum] & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT) | ||
| ((pdwInst[instNum] & D3DSP_REGTYPE_MASK2) >> D3DSP_REGTYPE_SHIFT2); | ||
DWORD regIndex = pdwInst[instNum] & D3DSP_REGNUM_MASK; | ||
|
||
if (unlikely(regType == static_cast<DWORD>(DxsoRegisterType::Input) && regIndex >= 10)) { | ||
Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: Found register index ", regIndex)); | ||
return ErrorCallback(pFile, Line, 0x2, | ||
instContext.instruction.opcode == DxsoOpcode::Dcl ? | ||
D3D9ShaderValidatorMessage::BadInputRegisterDeclaration : | ||
D3D9ShaderValidatorMessage::BadInputRegister, | ||
"IDirect3DShaderValidator9::Instruction: Invalid number of PS input registers specified. Aborting validation."); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return D3D_OK; | ||
} | ||
|
||
|
||
HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::End() { | ||
if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { | ||
return E_FAIL; | ||
} else if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { | ||
return ErrorCallback(nullptr, 0, 0, | ||
D3D9ShaderValidatorMessage::EndOutOfOrder, | ||
"IDirect3DShaderValidator9::End called out of order. Call to ::Begin, followed by calls to ::Instruction must occur first."); | ||
} else if (unlikely(m_state != D3D9ShaderValidatorState::EndOfShader)) { | ||
return ErrorCallback(nullptr, 0, 0, | ||
D3D9ShaderValidatorMessage::MissingEndToken, | ||
"IDirect3DShaderValidator9::End: Shader missing end token."); | ||
} | ||
|
||
m_state = D3D9ShaderValidatorState::Begin; | ||
m_isPixelShader = false; | ||
m_majorVersion = 0; | ||
m_minorVersion = 0; | ||
m_callback = nullptr; | ||
m_userData = nullptr; | ||
m_ctx = nullptr; | ||
|
||
return D3D_OK; | ||
} | ||
|
||
|
||
HRESULT D3D9ShaderValidator::ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { | ||
if (unlikely(cdw != 1)) { | ||
return ErrorCallback(pFile, Line, 0x6, | ||
D3D9ShaderValidatorMessage::BadVersionTokenLength, | ||
"IDirect3DShaderValidator9::Instruction: Bad version token. DWORD count > 1 given. Expected DWORD count to be 1 for version token."); | ||
} | ||
|
||
DxsoReader reader = { reinterpret_cast<const char*>(pdwInst) }; | ||
uint32_t headerToken = reader.readu32(); | ||
uint32_t shaderType = headerToken & 0xffff0000; | ||
DxsoProgramType programType; | ||
|
||
if (shaderType == 0xffff0000) { // Pixel Shader | ||
programType = DxsoProgramTypes::PixelShader; | ||
m_isPixelShader = true; | ||
} else if (shaderType == 0xfffe0000) { // Vertex Shader | ||
programType = DxsoProgramTypes::VertexShader; | ||
m_isPixelShader = false; | ||
} else { | ||
return ErrorCallback(pFile, Line, 0x6, | ||
D3D9ShaderValidatorMessage::BadVersionTokenType, | ||
"IDirect3DShaderValidator9::Instruction: Bad version token. It indicates neither a pixel shader nor a vertex shader."); | ||
} | ||
|
||
m_majorVersion = (headerToken >> 8) & 0xff; | ||
m_minorVersion = headerToken & 0xff; | ||
m_ctx = std::make_unique<DxsoDecodeContext>(DxsoProgramInfo{ programType, m_minorVersion, m_majorVersion }); | ||
m_state = D3D9ShaderValidatorState::ValidatingInstructions; | ||
|
||
const char* shaderTypeOutput = m_isPixelShader ? "PS" : "VS"; | ||
Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: Validating ", | ||
shaderTypeOutput, " version ", m_majorVersion, ".", m_minorVersion)); | ||
|
||
return D3D_OK; | ||
} | ||
|
||
|
||
HRESULT D3D9ShaderValidator::ValidateEndToken(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { | ||
// Reached the end token. | ||
if (unlikely(cdw != 1)) { | ||
return ErrorCallback(pFile, Line, 0x6, | ||
D3D9ShaderValidatorMessage::BadEndToken, | ||
"IDirect3DShaderValidator9::Instruction: Bad end token. DWORD count > 1 given. Expected DWORD count to be 1 for end token."); | ||
} | ||
|
||
m_state = D3D9ShaderValidatorState::EndOfShader; | ||
|
||
return D3D_OK; | ||
} | ||
|
||
|
||
HRESULT D3D9ShaderValidator::ErrorCallback( | ||
const char* pFile, | ||
UINT Line, | ||
DWORD Unknown, | ||
D3D9ShaderValidatorMessage MessageID, | ||
const std::string& Message) { | ||
if (m_callback) | ||
m_callback(pFile, Line, Unknown, MessageID, Message.c_str(), m_userData); | ||
|
||
// TODO: Consider switching this to debug, once we're | ||
// confident the implementation doesn't cause any issues | ||
Logger::warn(Message); | ||
|
||
m_state = D3D9ShaderValidatorState::Error; | ||
|
||
return E_FAIL; | ||
} | ||
|
||
} |
Oops, something went wrong.