diff --git a/src/dxbc/dxbc_chunk_shex.cpp b/src/dxbc/dxbc_chunk_shex.cpp new file mode 100644 index 00000000..708391de --- /dev/null +++ b/src/dxbc/dxbc_chunk_shex.cpp @@ -0,0 +1,24 @@ +#include "dxbc_chunk_shex.h" + +namespace dxvk { + + DxbcShex::DxbcShex(DxbcReader reader) { + // The shader version and type are stored in a 32-bit unit, + // where the first byte contains the major and minor version + // numbers, and the high word contains the program type. + auto pVersion = reader.readu16() & 0xFF; + auto pType = reader.readEnum(); + m_version = DxbcProgramVersion(pVersion >> 4, pVersion & 0xF, pType); + + // Read the actual shader code as an array of DWORDs. + auto codeLength = reader.readu32() - 2; + m_code.resize(codeLength); + reader.read(m_code.data(), codeLength * sizeof(uint32_t)); + } + + + DxbcShex::~DxbcShex() { + + } + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_chunk_shex.h b/src/dxbc/dxbc_chunk_shex.h new file mode 100644 index 00000000..e38d3f5f --- /dev/null +++ b/src/dxbc/dxbc_chunk_shex.h @@ -0,0 +1,42 @@ +#pragma once + +#include "dxbc_common.h" +#include "dxbc_instruction.h" +#include "dxbc_reader.h" + +namespace dxvk { + + /** + * \brief Shader code chunk + * + * Stores the DXBC shader code itself, as well + * as some meta info about the shader, i.e. what + * type of shader this is. + */ + class DxbcShex : public RcObject { + + public: + + DxbcShex(DxbcReader reader); + ~DxbcShex(); + + DxbcProgramVersion version() const { + return m_version; + } + + DxbcInstructionIterator begin() const { + return DxbcInstructionIterator(m_code.data()); + } + + DxbcInstructionIterator end() const { + return DxbcInstructionIterator(m_code.data() + m_code.size()); + } + + private: + + DxbcProgramVersion m_version; + std::vector m_code; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_common.h b/src/dxbc/dxbc_common.h new file mode 100644 index 00000000..d78a121a --- /dev/null +++ b/src/dxbc/dxbc_common.h @@ -0,0 +1,70 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief DXBC Program type + * + * Defines the shader stage that a DXBC + * module has been compiled form. + */ + enum class DxbcProgramType : uint16_t { + PixelShader = 0, + VertexShader = 1, + GeometryShader = 2, + HullShader = 3, + DomainShader = 4, + ComputeShader = 5, + }; + + + /** + * \brief DXBC shader version info + * + * Stores the shader model version + * as well as the program type. + */ + class DxbcProgramVersion { + + public: + + DxbcProgramVersion() { } + DxbcProgramVersion( + uint8_t major, uint8_t minor, DxbcProgramType type) + : m_major(major), m_minor(minor), m_type(type) { } + + /** + * \brief Major version + * \returns Major version + */ + uint32_t major() const { + return m_major; + } + + /** + * \brief Minor version + * \returns Minor version + */ + uint32_t minor() const { + return m_minor; + } + + /** + * \brief Program type + * \returns Program type + */ + DxbcProgramType type() const { + return m_type; + } + + private: + + uint8_t m_major = 0; + uint8_t m_minor = 0; + DxbcProgramType m_type = DxbcProgramType::PixelShader; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_compiler.cpp b/src/dxbc/dxbc_compiler.cpp new file mode 100644 index 00000000..318f8c24 --- /dev/null +++ b/src/dxbc/dxbc_compiler.cpp @@ -0,0 +1,26 @@ +#include "dxbc_compiler.h" + +namespace dxvk { + + DxbcCompiler::DxbcCompiler(DxbcProgramVersion version) { + + } + + + DxbcCompiler::~DxbcCompiler() { + + } + + + void DxbcCompiler::processInstruction(DxbcInstruction ins) { + Logger::info(str::format( + static_cast(ins.opcode()))); + } + + + Rc DxbcCompiler::finalize() { + return new DxvkShader(VK_SHADER_STAGE_COMPUTE_BIT, + DxvkSpirvCodeBuffer(), 0, nullptr); + } + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_compiler.h b/src/dxbc/dxbc_compiler.h new file mode 100644 index 00000000..20a0d5cc --- /dev/null +++ b/src/dxbc/dxbc_compiler.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../dxvk/dxvk_shader.h" + +#include "dxbc_chunk_shex.h" + +namespace dxvk { + + /** + * \brief DXBC to SPIR-V compiler + * + * + */ + class DxbcCompiler { + + public: + + DxbcCompiler(DxbcProgramVersion version); + ~DxbcCompiler(); + + /** + * \brief Processes a single instruction + * \param [in] ins The instruction + */ + void processInstruction(DxbcInstruction ins); + + /** + * \brief Creates actual shader object + * + * Combines all information gatherd during the + * shader compilation into one shader object. + */ + Rc finalize(); + + private: + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_header.cpp b/src/dxbc/dxbc_header.cpp new file mode 100644 index 00000000..9b5f6989 --- /dev/null +++ b/src/dxbc/dxbc_header.cpp @@ -0,0 +1,30 @@ +#include "dxbc_header.h" + +namespace dxvk { + + DxbcHeader::DxbcHeader(DxbcReader& reader) { + // FourCC at the start of the file, must be 'DXBC' + DxbcTag fourcc = reader.readTag(); + + if (fourcc != "DXBC") + throw DxvkError("DxbcHeader::DxbcHeader: Invalid fourcc, expected 'DXBC'"); + + // Stuff we don't actually need to store + reader.skip(4 * sizeof(uint32_t)); // Check sum + reader.skip(1 * sizeof(uint32_t)); // Constant 1 + reader.skip(1 * sizeof(uint32_t)); // Bytecode length + + // Number of chunks in the file + uint32_t chunkCount = reader.readu32(); + + // Chunk offsets are stored immediately after + for (uint32_t i = 0; i < chunkCount; i++) + m_chunkOffsets.push_back(reader.readu32()); + } + + + DxbcHeader::~DxbcHeader() { + + } + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_header.h b/src/dxbc/dxbc_header.h new file mode 100644 index 00000000..8eca0d49 --- /dev/null +++ b/src/dxbc/dxbc_header.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "dxbc_reader.h" + +namespace dxvk { + + /** + * \brief DXBC header + * + * Stores information about the shader file itself + * and the data chunks stored inside the file. + */ + class DxbcHeader { + + public: + + DxbcHeader(DxbcReader& reader); + ~DxbcHeader(); + + /** + * \brief Number of chunks + * \returns Chunk count + */ + uint32_t numChunks() const { + return m_chunkOffsets.size(); + } + + /** + * \brief Chunk offset + * + * Retrieves the offset of a chunk, in + * bytes, from the start of the file. + * \param [in] chunkId Chunk index + * \returns Byte offset of that chunk + */ + uint32_t chunkOffset(uint32_t chunkId) const { + return m_chunkOffsets.at(chunkId); + } + + private: + + std::vector m_chunkOffsets; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_include.h b/src/dxbc/dxbc_include.h new file mode 100644 index 00000000..88f765e6 --- /dev/null +++ b/src/dxbc/dxbc_include.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../util/com/com_guid.h" +#include "../util/com/com_object.h" +#include "../util/com/com_pointer.h" + +#include "../util/log/log.h" +#include "../util/log/log_debug.h" + +#include "../util/rc/util_rc.h" +#include "../util/rc/util_rc_ptr.h" + +#include "../util/util_bit.h" +#include "../util/util_enum.h" +#include "../util/util_error.h" +#include "../util/util_string.h" diff --git a/src/dxbc/dxbc_instruction.h b/src/dxbc/dxbc_instruction.h new file mode 100644 index 00000000..2e0dae27 --- /dev/null +++ b/src/dxbc/dxbc_instruction.h @@ -0,0 +1,85 @@ +#pragma once + +#include "dxbc_opcode.h" + +namespace dxvk { + + /** + * \brief DXBC instruction + * + * Provides convenience methods to extract the + * opcode, instruction length, and instruction + * arguments from an instruction. + */ + class DxbcInstruction { + + public: + + DxbcInstruction() { } + DxbcInstruction(const uint32_t* code) + : m_code(code) { } + + /** + * \brief Instruction code + * \returns The operation code + */ + DxbcOpcode opcode() const { + return static_cast( + bit::extract(m_code[0])); + } + + /** + * \brief Instruction length + * + * Number of DWORDs for this instruction, + * including the initial opcode token. + * \returns Instruction length + */ + uint32_t length() const { + return this->opcode() != DxbcOpcode::CustomData + ? bit::extract(m_code[0]) + : m_code[1]; + } + + private: + + const uint32_t* m_code = nullptr; + + }; + + + /** + * \brief DXBC instruction iterator + * + * Iterator that walks over DXBC instructions. + * Instruction boundaries are easy to find as + * the length of each instruction is encoded + * in the opcode token, much like in SPIR-V. + */ + class DxbcInstructionIterator { + + public: + + DxbcInstructionIterator() { } + DxbcInstructionIterator(const uint32_t* code) + : m_code(code) { } + + DxbcInstructionIterator& operator ++ () { + m_code += DxbcInstruction(m_code).length(); + return *this; + } + + DxbcInstruction operator * () const { + return DxbcInstruction(m_code); + } + + bool operator == (const DxbcInstructionIterator& other) const { return m_code == other.m_code; } + bool operator != (const DxbcInstructionIterator& other) const { return m_code != other.m_code; } + + private: + + const uint32_t* m_code = nullptr; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_module.cpp b/src/dxbc/dxbc_module.cpp new file mode 100644 index 00000000..0461575b --- /dev/null +++ b/src/dxbc/dxbc_module.cpp @@ -0,0 +1,43 @@ +#include "dxbc_compiler.h" +#include "dxbc_module.h" + +namespace dxvk { + + DxbcModule::DxbcModule(DxbcReader& reader) + : m_header(reader) { + for (uint32_t i = 0; i < m_header.numChunks(); i++) { + + // The chunk tag is stored at the beginning of each chunk + auto chunkReader = reader.clone(m_header.chunkOffset(i)); + auto tag = chunkReader.readTag(); + + // The chunk size follows right after the four-character + // code. This does not include the eight bytes that are + // consumed by the FourCC and chunk length entry. + auto chunkLength = chunkReader.readu32() + 8; + chunkReader = chunkReader.resize(chunkLength); + + if ((tag == "SHDR") || (tag == "SHEX")) + m_shexChunk = new DxbcShex(chunkReader); + + } + } + + + DxbcModule::~DxbcModule() { + + } + + + Rc DxbcModule::compile() const { + if (m_shexChunk == nullptr) + throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk"); + + DxbcCompiler compiler(m_shexChunk->version()); + + for (auto ins : *m_shexChunk) + compiler.processInstruction(ins); + return compiler.finalize(); + } + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_module.h b/src/dxbc/dxbc_module.h new file mode 100644 index 00000000..bb0eb3d2 --- /dev/null +++ b/src/dxbc/dxbc_module.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../dxvk/dxvk_shader.h" + +#include "dxbc_chunk_shex.h" +#include "dxbc_header.h" +#include "dxbc_reader.h" + +// References used for figuring out DXBC: +// - https://github.com/tgjones/slimshader-cpp +// - Wine + +namespace dxvk { + + /** + * \brief DXBC shader module + * + * Reads the DXBC byte code and extracts information + * about the resource bindings and the instruction + * stream. A module can then be compiled to SPIR-V. + */ + class DxbcModule { + + public: + + DxbcModule(DxbcReader& reader); + ~DxbcModule(); + + /** + * \brief Compiles DXBC shader to SPIR-V module + * \returns The compiled DXVK shader object + */ + Rc compile() const; + + private: + + DxbcHeader m_header; + + Rc m_shexChunk; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_opcode.h b/src/dxbc/dxbc_opcode.h new file mode 100644 index 00000000..9f945a00 --- /dev/null +++ b/src/dxbc/dxbc_opcode.h @@ -0,0 +1,220 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief Instruction code listing + */ + enum class DxbcOpcode : uint32_t { + Add = 0, + And = 1, + Break = 2, + Breakc = 3, + Call = 4, + Callc = 5, + Case = 6, + Continue = 7, + Continuec = 8, + Cut = 9, + Default = 10, + DerivRtx = 11, + DerivRty = 12, + Discard = 13, + Div = 14, + Dp2 = 15, + Dp3 = 16, + Dp4 = 17, + Else = 18, + Emit = 19, + EmitThenCut = 20, + EndIf = 21, + EndLoop = 22, + EndSwitch = 23, + Eq = 24, + Exp = 25, + Frc = 26, + FtoI = 27, + FtoU = 28, + Ge = 29, + IAdd = 30, + If = 31, + IEq = 32, + IGe = 33, + ILt = 34, + IMad = 35, + IMax = 36, + IMin = 37, + IMul = 38, + INe = 39, + INeg = 40, + IShl = 41, + IShr = 42, + ItoF = 43, + Label = 44, + Ld = 45, + LdMs = 46, + Log = 47, + Loop = 48, + Lt = 49, + Mad = 50, + Min = 51, + Max = 52, + CustomData = 53, + Mov = 54, + Movc = 55, + Mul = 56, + Ne = 57, + Nop = 58, + Not = 59, + Or = 60, + ResInfo = 61, + Ret = 62, + Retc = 63, + RoundNe = 64, + RoundNi = 65, + RoundPi = 66, + RoundZ = 67, + Rsq = 68, + Sample = 69, + SampleC = 70, + SampleClz = 71, + SampleL = 72, + SampleD = 73, + SampleB = 74, + Sqrt = 75, + Switch = 76, + SinCos = 77, + UDiv = 78, + ULt = 79, + UGe = 80, + UMul = 81, + UMad = 82, + UMax = 83, + UMin = 84, + UShr = 85, + UtoF = 86, + Xor = 87, + DclResource = 88, + DclConstantBuffer = 89, + DclSampler = 90, + DclIndexRange = 91, + DclGsOutputPrimitiveTopology = 92, + DclGsInputPrimitive = 93, + DclMaxOutputVertexCount = 94, + DclInput = 95, + DclInputSgv = 96, + DclInputSiv = 97, + DclInputPs = 98, + DclInputPsSgv = 99, + DclInputPsSiv = 100, + DclOutput = 101, + DclOutputSgv = 102, + DclOutputSiv = 103, + DclTemps = 104, + DclIndexableTemp = 105, + DclGlobalFlags = 106, + Reserved0 = 107, + Lod = 108, + Gather4 = 109, + SamplePos = 110, + SampleInfo = 111, + Reserved1 = 112, + HsDecls = 113, + HsControlPointPhase = 114, + HsForkPhase = 115, + HsJoinPhase = 116, + EmitStream = 117, + CutStream = 118, + EmitThenCutStream = 119, + InterfaceCall = 120, + BufInfo = 121, + DerivRtxCoarse = 122, + DerivRtxFine = 123, + DerivRtyCoarse = 124, + DerivRtyFine = 125, + Gather4C = 126, + Gather4Po = 127, + Gather4PoC = 128, + Rcp = 129, + F32toF16 = 130, + F16toF32 = 131, + UAddc = 132, + USubb = 133, + CountBits = 134, + FirstBitHi = 135, + FirstBitLo = 136, + FirstBitShi = 137, + UBfe = 138, + IBfe = 139, + Bfi = 140, + BfRev = 141, + Swapc = 142, + DclStream = 143, + DclFunctionBody = 144, + DclFunctionTable = 145, + DclInterface = 146, + DclInputControlPointCount = 147, + DclOutputControlPointCount = 148, + DclTessDomain = 149, + DclTessPartitioning = 150, + DclTessOutputPrimitive = 151, + DclHsMaxTessFactor = 152, + DclHsForkPhaseInstanceCount = 153, + DclHsJoinPhaseInstanceCount = 154, + DclThreadGroup = 155, + DclUnorderedAccessViewTyped = 156, + DclUnorderedAccessViewRaw = 157, + DclUnorderedAccessViewStructured = 158, + DclThreadGroupSharedMemoryRaw = 159, + DclThreadGroupSharedMemoryStructured = 160, + DclResourceRaw = 161, + DclResourceStructured = 162, + LdUavTyped = 163, + StoreUavTyped = 164, + LdRaw = 165, + StoreRaw = 166, + LdStructured = 167, + StoreStructured = 168, + AtomicAnd = 169, + AtomicOr = 170, + AtomicXor = 171, + AtomicCmpStore = 172, + AtomicIAdd = 173, + AtomicIMax = 174, + AtomicIMin = 175, + AtomicUMax = 176, + AtomicUMin = 177, + ImmAtomicAlloc = 178, + ImmAtomicConsume = 179, + ImmAtomicIAdd = 180, + ImmAtomicAnd = 181, + ImmAtomicOr = 182, + ImmAtomicXor = 183, + ImmAtomicExch = 184, + ImmAtomicCmpExch = 185, + ImmAtomicImax = 186, + ImmAtomicImin = 187, + ImmAtomicUmax = 188, + ImmAtomicUmin = 189, + Sync = 190, + DAdd = 191, + DMax = 192, + DMin = 193, + DMul = 194, + DEq = 195, + DGe = 196, + DLt = 197, + DNe = 198, + DMov = 199, + DMovc = 200, + DtoF = 201, + FtoD = 202, + EvalSnapped = 203, + EvalSampleIndex = 204, + EvalCentroid = 205, + DclGsInstanceCount = 206, + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_reader.cpp b/src/dxbc/dxbc_reader.cpp new file mode 100644 index 00000000..b6fca61d --- /dev/null +++ b/src/dxbc/dxbc_reader.cpp @@ -0,0 +1,53 @@ +#include + +#include "dxbc_reader.h" + +namespace dxvk { + + DxbcTag DxbcReader::readTag() { + DxbcTag tag; + this->read(&tag, 4); + return tag; + } + + + std::string DxbcReader::readString() { + std::string result; + + while (m_data[m_pos] != '\0') + result.push_back(m_data[m_pos++]); + + m_pos++; + return result; + } + + + void DxbcReader::read(void* dst, size_t n) { + if (m_pos + n > m_size) + throw DxvkError("DxbcReader::read: Unexpected end of file"); + std::memcpy(dst, m_data + m_pos, n); + m_pos += n; + } + + + void DxbcReader::skip(size_t n) { + if (m_pos + n > m_size) + throw DxvkError("DxbcReader::skip: Unexpected end of file"); + m_pos += n; + } + + + DxbcReader DxbcReader::clone(size_t pos) const { + if (pos > m_size) + throw DxvkError("DxbcReader::clone: Invalid offset"); + return DxbcReader(m_data + pos, m_size - pos); + } + + + DxbcReader DxbcReader::resize(size_t size) const { + if (size > m_size) + throw DxvkError("DxbcReader::resize: Invalid size"); + return DxbcReader(m_data, size, m_pos); + } + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_reader.h b/src/dxbc/dxbc_reader.h new file mode 100644 index 00000000..46d08972 --- /dev/null +++ b/src/dxbc/dxbc_reader.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include "dxbc_tag.h" + +namespace dxvk { + + /** + * \brief DXBC bytecode reader + * + * Holds references to the shader byte code and + * provides methods to read + */ + class DxbcReader { + + public: + + DxbcReader(const char* data, size_t size) + : DxbcReader(data, size, 0) { } + + auto readu8 () { return this->readNum (); } + auto readu16() { return this->readNum(); } + auto readu32() { return this->readNum(); } + auto readu64() { return this->readNum(); } + + auto readi8 () { return this->readNum (); } + auto readi16() { return this->readNum (); } + auto readi32() { return this->readNum (); } + auto readi64() { return this->readNum (); } + + auto readf32() { return this->readNum (); } + auto readf64() { return this->readNum (); } + + template + auto readEnum() { + using Tx = std::underlying_type_t; + return static_cast(this->readNum()); + } + + DxbcTag readTag(); + + std::string readString(); + + void read(void* dst, size_t n); + + void skip(size_t n); + + DxbcReader clone(size_t pos) const; + + DxbcReader resize(size_t size) const; + + bool eof() const { + return m_pos >= m_size; + } + + private: + + DxbcReader(const char* data, size_t size, size_t pos) + : m_data(data), m_size(size), m_pos(pos) { } + + const char* m_data = nullptr; + size_t m_size = 0; + size_t m_pos = 0; + + template + T readNum() { + T result; + this->read(&result, sizeof(result)); + return result; + } + + }; + +} \ No newline at end of file diff --git a/src/dxbc/dxbc_tag.h b/src/dxbc/dxbc_tag.h new file mode 100644 index 00000000..2ba17509 --- /dev/null +++ b/src/dxbc/dxbc_tag.h @@ -0,0 +1,47 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief Four-character tag + * + * Used to identify chunks in the + * compiled DXBC file by name. + */ + class DxbcTag { + + public: + + DxbcTag() { + for (size_t i = 0; i < 4; i++) + m_chars[i] = '\0'; + } + + DxbcTag(const char* tag) { + for (size_t i = 0; i < 4; i++) + m_chars[i] = tag[i]; + } + + bool operator == (const DxbcTag& other) const { + bool result = true; + for (size_t i = 0; i < 4; i++) + result &= m_chars[i] == other.m_chars[i]; + return result; + } + + bool operator != (const DxbcTag& other) const { + return !this->operator == (other); + } + + const char* operator & () const { return m_chars; } + char* operator & () { return m_chars; } + + private: + + char m_chars[4]; + + }; + +} \ No newline at end of file diff --git a/src/dxbc/meson.build b/src/dxbc/meson.build new file mode 100644 index 00000000..219d9297 --- /dev/null +++ b/src/dxbc/meson.build @@ -0,0 +1,14 @@ +dxbc_src = files([ + 'dxbc_chunk_shex.cpp', + 'dxbc_compiler.cpp', + 'dxbc_header.cpp', + 'dxbc_module.cpp', + 'dxbc_reader.cpp', +]) + +dxbc_lib = static_library('dxbc', dxbc_src, + include_directories : [ dxvk_include_path ]) + +dxbc_dep = declare_dependency( + link_with : [ dxbc_lib ], + include_directories : [ dxvk_include_path, include_directories('.') ]) diff --git a/src/meson.build b/src/meson.build index 5938df46..84a5d1ef 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,5 @@ subdir('util') subdir('dxvk') subdir('dxgi') +subdir('dxbc') subdir('d3d11') \ No newline at end of file diff --git a/src/util/util_bit.h b/src/util/util_bit.h new file mode 100644 index 00000000..15b7496a --- /dev/null +++ b/src/util/util_bit.h @@ -0,0 +1,10 @@ +#pragma once + +namespace dxvk::bit { + + template + constexpr T extract(T value) { + return (value >> Fst) & ~(~T(0) << (Lst - Fst + 1)); + } + +} \ No newline at end of file diff --git a/src/util/util_string.h b/src/util/util_string.h index 366c393b..9704c528 100644 --- a/src/util/util_string.h +++ b/src/util/util_string.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -20,4 +22,8 @@ namespace dxvk::str { return stream.str(); } + inline std::string fromws(const std::wstring& ws) { + return std::wstring_convert>().to_bytes(ws); + } + }