Skip to content

Commit

Permalink
[d3d9] Implement several IDirect3DShaderValidator9 validations
Browse files Browse the repository at this point in the history
Co-Authored-By: Joshua Ashton <[email protected]>
Co-Authored-By: Paul Gofman <[email protected]>
  • Loading branch information
3 people committed Oct 24, 2024
1 parent 640379e commit 61e342b
Showing 1 changed file with 203 additions and 15 deletions.
218 changes: 203 additions & 15 deletions src/d3d9/d3d9_shader_validator.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,56 @@

#include "d3d9_include.h"

#include "../dxso/dxso_header.h"
#include "../dxso/dxso_decoder.h"

namespace dxvk {

enum class D3D9ShaderValidatorMessage : uint32_t {
BeginOutOfOrder = 0xeb,
InstructionOutOfOrder = 0xec,
InstructionEndOfShader = 0xed,
InstructionNullArgs = 0xee,
BadVersionTokenLength = 0xef,
BadVersionTokenType = 0xf0,
BadEndToken = 0xf1,
EndOutOfOrder = 0xf2,
MissingEndToken = 0xf3,
BadInputRegisterDeclaration = 0x12c,
BadInputRegister = 0x167,
BadInstructionLength = 0x21e,
};

enum class D3D9ShaderValidatorState {
Begin,
ValidatingHeader,
ValidatingInstructions,
EndOfShader,
Error,
};

using D3D9ShaderValidatorCallback = HRESULT(STDMETHODCALLTYPE *)(
const char* pFile,
UINT Line,
DWORD Unknown,
D3D9ShaderValidatorMessage MessageID,
const char* pMessage,
void* pUserData);

class IDirect3DShaderValidator9 : public IUnknown {

public:

virtual HRESULT STDMETHODCALLTYPE Begin(
void* pCallback,
void* pUserParam,
DWORD Unknown) = 0;
D3D9ShaderValidatorCallback pCallback,
void* pUserParam,
DWORD Unknown) = 0;

virtual HRESULT STDMETHODCALLTYPE Instruction(
const char* pUnknown1,
UINT Unknown2,
const DWORD* pInstruction,
DWORD InstructionLength) = 0;
const DWORD* pdwInst,
DWORD cdw) = 0;

virtual HRESULT STDMETHODCALLTYPE End() = 0;

Expand All @@ -37,32 +71,186 @@ namespace dxvk {


HRESULT STDMETHODCALLTYPE Begin(
void* pCallback,
void* pUserParam,
DWORD Unknown) {
Logger::debug("D3D9ShaderValidator::Begin: Stub");
D3D9ShaderValidatorCallback pCallback,
void* pUserData,
DWORD Unknown) {
if (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 Instruction(
const char* pUnknown1,
UINT Unknown2,
const DWORD* pInstruction,
DWORD InstructionLength) {
Logger::debug("D3D9ShaderValidator::Instruction: Stub");
const char* pFile,
UINT Line,
const DWORD* pdwInst,
DWORD cdw) {
if (m_state == D3D9ShaderValidatorState::Begin) {
return ErrorCallback(pFile, Line, 0,
D3D9ShaderValidatorMessage::InstructionOutOfOrder,
"IDirect3DShaderValidator9::Instruction called out of order. ::Begin must be called first.");
} else if (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 (m_state == D3D9ShaderValidatorState::Error) {
return E_FAIL;
}

if (pdwInst == nullptr || !cdw) {
return ErrorCallback(pFile, Line, 0,
D3D9ShaderValidatorMessage::InstructionNullArgs,
"IDirect3DShaderValidator9::Instruction called with NULL == pdwInst or 0 == cdw.");
}

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);

// 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 on native
//
/*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 (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."));*/

switch (instContext.instruction.opcode) {
case DxsoOpcode::Comment:
case DxsoOpcode::Def:
case DxsoOpcode::DefB:
case DxsoOpcode::DefI:
break;

default:
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;

// a maximum of 10 inputs are supported with PS 3.0
if (m_isPixelShader && regType == D3DSPR_INPUT && regIndex >= 10) {
Logger::warn(str::format("IDirect3DShaderValidator9::Instruction: invalid input regIndex ", regIndex));
return ErrorCallback(pFile, Line, 0x2,
instContext.instruction.opcode == DxsoOpcode::Dcl ?
D3D9ShaderValidatorMessage::BadInputRegisterDeclaration :
D3D9ShaderValidatorMessage::BadInputRegister,
str::format("IDirect3DShaderValidator9::Instruction: Invalid number of PS input registers specified. Aborting validation."));
}
}
}

return D3D_OK;
}


HRESULT STDMETHODCALLTYPE End() {
Logger::debug("D3D9ShaderValidator::End: Stub");
if (m_state == D3D9ShaderValidatorState::Error) {
return E_FAIL;
} else if (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 (m_state != D3D9ShaderValidatorState::EndOfShader) {
return ErrorCallback(nullptr, 0, 0,
D3D9ShaderValidatorMessage::MissingEndToken,
"Shader missing end token.");
}

m_state = D3D9ShaderValidatorState::Begin;
m_callback = nullptr;
m_userData = nullptr;
m_ctx = nullptr;

return D3D_OK;
}

private:

HRESULT ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) {
if (cdw != 1) {
return ErrorCallback(pFile, Line, 0x6,
D3D9ShaderValidatorMessage::BadVersionTokenLength,
"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,
"Bad version token. It indicates neither a pixel shader nor a vertex shader.");
}

const uint32_t majorVersion = (headerToken >> 8) & 0xff;
const uint32_t minorVersion = headerToken & 0xff;
m_ctx = std::make_unique<DxsoDecodeContext>(DxsoProgramInfo{ programType, minorVersion, majorVersion });
m_state = D3D9ShaderValidatorState::ValidatingInstructions;

return D3D_OK;
}

HRESULT ValidateEndToken(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) {
// Reached the end token.
if (cdw != 1) {
return ErrorCallback(pFile, Line, 0x6,
D3D9ShaderValidatorMessage::BadEndToken,
"Bad end token. DWORD count > 1 given. Expected DWORD count to be 1 for end token.");
}

m_state = D3D9ShaderValidatorState::EndOfShader;

return D3D_OK;
}

HRESULT 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);
m_state = D3D9ShaderValidatorState::Error;
return E_FAIL;
}

bool m_isPixelShader = false;

D3D9ShaderValidatorState m_state = D3D9ShaderValidatorState::Begin;
D3D9ShaderValidatorCallback m_callback = nullptr;
void* m_userData = nullptr;

std::unique_ptr<DxsoDecodeContext> m_ctx;
};

}

0 comments on commit 61e342b

Please sign in to comment.