diff --git a/src/ObjWriting/Shader/D3D9ShaderAnalyser.cpp b/src/ObjWriting/Shader/D3D9ShaderAnalyser.cpp new file mode 100644 index 00000000..932d8641 --- /dev/null +++ b/src/ObjWriting/Shader/D3D9ShaderAnalyser.cpp @@ -0,0 +1,246 @@ +#include "D3D9ShaderAnalyser.h" + +#include + +#include "Utils/FileUtils.h" + +using namespace d3d9; + +namespace d3d9 +{ + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3d9types/ne-d3d9types-_d3dshader_instruction_opcode_type + static constexpr uint32_t OPCODE_COMMENT = 0xFFFE; + static constexpr uint32_t OPCODE_END = 0xFFFF; + + static constexpr uint32_t OPCODE_MASK = 0x0000FFFF; + + static constexpr uint32_t COMMENT_SIZE_MASK = 0xFFFF0000; + static constexpr uint32_t COMMENT_SIZE_SHIFT = 16; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxshader-constanttable + struct ConstantTable + { + uint32_t Size; + uint32_t Creator; + uint32_t Version; + uint32_t Constants; + uint32_t ConstantInfo; + uint32_t Flags; + uint32_t Target; + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxshader-constantinfo + struct ConstantInfo + { + uint32_t Name; + uint16_t RegisterSet; + uint16_t RegisterIndex; + uint16_t RegisterCount; + uint16_t Reserved; + uint32_t TypeInfo; + uint32_t DefaultValue; + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxshader-typeinfo + struct TypeInfo + { + uint16_t Class; + uint16_t Type; + uint16_t Rows; + uint16_t Columns; + uint16_t Elements; + uint16_t StructMembers; + uint32_t StructMemberInfo; + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxshader-structmemberinfo + struct StructMemberInfo + { + uint32_t Name; + uint32_t TypeInfo; + }; + + bool PopulateVersionInfo(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) + { + if (shaderByteCodeSize < sizeof(uint32_t)) + return false; + + const auto version = *shaderByteCode; + shaderInfo.m_version_minor = version & 0xFF; + shaderInfo.m_version_major = (version & 0xFF00) >> 8; + + switch ((version & 0xFFFF0000) >> 16) + { + case 0x4658: // FX + case 0x5458: // TX + case 0x7ffe: // ? + case 0x7fff: // ? + shaderInfo.m_type = ShaderType::UNKNOWN; // Valid according to wine + return true; + case 0xfffe: + shaderInfo.m_type = ShaderType::VERTEX_SHADER; + return true; + case 0xffff: + shaderInfo.m_type = ShaderType::PIXEL_SHADER; + return true; + default: + break; + } + + return false; + } + + bool FindComment(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize, const uint32_t magic, const char*& commentStart, size_t& commentSize) + { + const uint32_t* currentPos = shaderByteCode + 1; + size_t currentOffset = sizeof(uint32_t); + while (*currentPos != OPCODE_END && (currentOffset + sizeof(uint32_t) - 1) < shaderByteCodeSize) + { + const auto currentValue = *currentPos; + if ((currentValue & OPCODE_MASK) == OPCODE_COMMENT) + { + assert(currentOffset + sizeof(uint32_t) < shaderByteCodeSize); + if (currentOffset + sizeof(uint32_t) >= shaderByteCodeSize) + return false; + + const auto currentCommentSize = (currentValue & COMMENT_SIZE_MASK) >> COMMENT_SIZE_SHIFT; + + if (currentPos[1] == magic) + { + commentStart = reinterpret_cast(currentPos + 2); + commentSize = (currentCommentSize - 1) * sizeof(uint32_t); + return currentOffset + sizeof(uint32_t) * (currentCommentSize + 1) <= shaderByteCodeSize; + } + + currentPos += currentCommentSize; + currentOffset += currentCommentSize * sizeof(uint32_t); + } + + currentPos++; + currentOffset += sizeof(uint32_t); + assert((currentOffset + sizeof(uint32_t) - 1) < shaderByteCodeSize); + } + + return false; + } + + bool StringFitsInComment(const char* str, const char* commentStart, const size_t commentSize) + { + const auto strLen = strnlen(str, commentSize - (str - commentStart)); + return str[strLen] == '\0'; + } + + bool PopulateShaderConstantFromConstantInfo(ShaderConstant& shaderConstant, const char* commentStart, const size_t commentSize, const ConstantInfo& constantInfo) + { + if (constantInfo.Name) + { + const auto* constantName = commentStart + constantInfo.Name; + if (!StringFitsInComment(constantName, commentStart, commentSize)) + return false; + shaderConstant.m_name = std::string(constantName); + } + + shaderConstant.m_register_set = static_cast(constantInfo.RegisterSet); + if (shaderConstant.m_register_set >= RegisterSet::MAX) + return false; + + shaderConstant.m_register_index = constantInfo.RegisterIndex; + shaderConstant.m_register_count = constantInfo.RegisterCount; + + if(constantInfo.TypeInfo) + { + assert(commentStart + constantInfo.TypeInfo + sizeof(TypeInfo) <= commentStart + commentSize); + if (commentStart + constantInfo.TypeInfo + sizeof(TypeInfo) > commentStart + commentSize) + return false; + + const auto* typeInfo = reinterpret_cast(commentStart + constantInfo.TypeInfo); + + shaderConstant.m_class = static_cast(typeInfo->Class); + if (shaderConstant.m_class >= ParameterClass::MAX) + return false; + + shaderConstant.m_type = static_cast(typeInfo->Type); + if (shaderConstant.m_type >= ParameterType::MAX) + return false; + + shaderConstant.m_type_rows = typeInfo->Rows; + shaderConstant.m_type_columns = typeInfo->Columns; + shaderConstant.m_type_elements = typeInfo->Elements; + } + + return true; + } + + bool PopulateShaderInfoFromConstantTable(ShaderInfo& shaderInfo, const char* commentStart, const size_t commentSize, const ConstantTable& constantTable) + { + if (constantTable.Size != sizeof(ConstantTable)) + return false; + + if (constantTable.Creator) + { + const auto* creatorName = commentStart + constantTable.Creator; + if (!StringFitsInComment(creatorName, commentStart, commentSize)) + return false; + shaderInfo.m_creator = std::string(creatorName); + } + + if (constantTable.Target) + { + const auto* targetName = commentStart + constantTable.Target; + if (!StringFitsInComment(targetName, commentStart, commentSize)) + return false; + shaderInfo.m_target = std::string(targetName); + } + + if (constantTable.Constants > 0 && constantTable.ConstantInfo) + { + assert(commentStart + constantTable.ConstantInfo + sizeof(ConstantInfo) * constantTable.Constants <= commentStart + commentSize); + if (commentStart + constantTable.ConstantInfo + sizeof(ConstantInfo) * constantTable.Constants > commentStart + commentSize) + return false; + + const auto* constantInfos = reinterpret_cast(commentStart + constantTable.ConstantInfo); + for (auto constantInfoIndex = 0u; constantInfoIndex < constantTable.Constants; constantInfoIndex++) + { + ShaderConstant constant; + if (!PopulateShaderConstantFromConstantInfo(constant, commentStart, commentSize, constantInfos[constantInfoIndex])) + return false; + shaderInfo.m_constants.emplace_back(std::move(constant)); + } + } + + return true; + } + + bool PopulateShaderInfoFromShaderByteCode(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) + { + if (!PopulateVersionInfo(shaderInfo, shaderByteCode, shaderByteCodeSize)) + return false; + + const char* constantTableComment; + size_t constantTableCommentSize; + if (!FindComment(shaderByteCode, shaderByteCodeSize, FileUtils::MakeMagic32('C', 'T', 'A', 'B'), constantTableComment, constantTableCommentSize)) + return false; + + if (constantTableCommentSize < sizeof(ConstantTable)) + return false; + + const auto* constantTable = reinterpret_cast(constantTableComment); + if (!PopulateShaderInfoFromConstantTable(shaderInfo, constantTableComment, constantTableCommentSize, *constantTable)) + return false; + + return true; + } +} + +std::unique_ptr ShaderAnalyser::GetShaderInfo(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) +{ + if (shaderByteCode == nullptr || shaderByteCodeSize == 0) + return nullptr; + + auto shaderInfo = std::make_unique(); + + if (!PopulateShaderInfoFromShaderByteCode(*shaderInfo, shaderByteCode, shaderByteCodeSize)) + return nullptr; + + return shaderInfo; +} diff --git a/src/ObjWriting/Shader/D3D9ShaderAnalyser.h b/src/ObjWriting/Shader/D3D9ShaderAnalyser.h new file mode 100644 index 00000000..7e02ebd2 --- /dev/null +++ b/src/ObjWriting/Shader/D3D9ShaderAnalyser.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +namespace d3d9 +{ + enum class ShaderType + { + UNKNOWN, + PIXEL_SHADER, + VERTEX_SHADER + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxregister-set + enum class RegisterSet + { + BOOL, + INT_4, + FLOAT_4, + SAMPLER, + + // This entry only exist to mark the size of the enum and is not an actual valid value + MAX + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxparameter-class + enum class ParameterClass + { + SCALAR, + VECTOR, + MATRIX_ROWS, + MATRIX_COLUMNS, + OBJECT, + STRUCT, + + // This entry only exist to mark the size of the enum and is not an actual valid value + MAX + }; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxparameter-type + enum class ParameterType + { + VOID, + BOOL, + INT, + FLOAT, + STRING, + TEXTURE, + TEXTURE_1D, + TEXTURE_2D, + TEXTURE_3D, + TEXTURE_CUBE, + SAMPLER, + SAMPLER_1D, + SAMPLER_2D, + SAMPLER_3D, + SAMPLER_CUBE, + PIXEL_SHADER, + VERTEX_SHADER, + PIXEL_FRAGMENT, + VERTEX_FRAGMENT, + UNSUPPORTED, + + // This entry only exist to mark the size of the enum and is not an actual valid value + MAX + }; + + class ShaderConstant + { + public: + std::string m_name; + RegisterSet m_register_set{}; + unsigned m_register_index = 0; + unsigned m_register_count = 0; + ParameterClass m_class{}; + ParameterType m_type{}; + unsigned m_type_rows = 0; + unsigned m_type_columns = 0; + unsigned m_type_elements = 0; + }; + + class ShaderInfo + { + public: + ShaderType m_type = ShaderType::UNKNOWN; + unsigned m_version_major = 0; + unsigned m_version_minor = 0; + std::string m_creator; + std::string m_target; + std::vector m_constants; + }; + + class ShaderAnalyser + { + public: + static std::unique_ptr GetShaderInfo(const uint32_t* shaderByteCode, size_t shaderByteCodeSize); + }; +} \ No newline at end of file