From 9bffeb3c377ab258287492c35918f32733095e26 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 12 Jul 2021 17:56:43 +0100 Subject: [PATCH] Add a bytecode editor that can declare resources and add operations --- .../driver/shaders/dxbc/dxbc_bytecode.cpp | 16 +- renderdoc/driver/shaders/dxbc/dxbc_bytecode.h | 7 +- .../shaders/dxbc/dxbc_bytecode_editor.cpp | 735 ++++++++++++++++++ .../shaders/dxbc/dxbc_bytecode_editor.h | 169 ++++ .../shaders/dxbc/renderdoc_dxbc.vcxproj | 2 + .../dxbc/renderdoc_dxbc.vcxproj.filters | 2 + 6 files changed, 928 insertions(+), 3 deletions(-) create mode 100644 renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.cpp create mode 100644 renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp index 1c0261415..6957c7d87 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp @@ -26,7 +26,6 @@ #include "common/formatting.h" #include "os/os_specific.h" #include "dxbc_bytecode_ops.h" -#include "dxbc_container.h" static ShaderVariable makeReg(rdcstr name) { @@ -54,6 +53,21 @@ Program::Program(const byte *bytes, size_t length) m_Minor = VersionToken::MinorVersion.Get(cur[0]); } +Program::Program(const rdcarray &words) +{ + m_ProgramWords = words; + + if(m_ProgramWords.empty()) + return; + + uint32_t *begin = &m_ProgramWords.front(); + uint32_t *cur = begin; + + m_Type = VersionToken::ProgramType.Get(cur[0]); + m_Major = VersionToken::MajorVersion.Get(cur[0]); + m_Minor = VersionToken::MinorVersion.Get(cur[0]); +} + void HandleResourceArrayIndices(const rdcarray &indices, DXBC::ShaderInputBind &desc) { diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h index ec1a7ddd2..09368834a 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h @@ -1196,10 +1196,9 @@ public: void FetchComputeProperties(DXBC::Reflection *reflection); DXBC::Reflection *GuessReflection(); + const rdcarray &GetTokens() const { return m_ProgramWords; } rdcstr GetDebugStatus(); - rdcarray EncodeProgram(); - void SetReflection(const DXBC::Reflection *refl) { m_Reflection = refl; } void SetDebugInfo(const DXBC::IDebugInfo *debug) { m_DebugInfo = debug; } DXBC::ShaderType GetShaderType() const { return m_Type; } @@ -1227,7 +1226,11 @@ public: static bool UsesExtensionUAV(uint32_t slot, uint32_t space, const byte *bytes, size_t length); protected: + friend class Program; + + Program(const rdcarray &words); void DecodeProgram(); + rdcarray EncodeProgram(); void PostprocessVendorExtensions(); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.cpp b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.cpp new file mode 100644 index 000000000..bec004081 --- /dev/null +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.cpp @@ -0,0 +1,735 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "dxbc_bytecode_editor.h" +#include "dxbc_container.h" + +namespace DXBCBytecode +{ +DXBCBytecode::Operation Edit::oper(OpcodeType o, const rdcarray &operands) +{ + Operation ret; + ret.operation = o; + ret.operands = operands; + + // fxc doesn't like outputs that are selected, promote them to masked.. + if(!ret.operands.empty() && (ret.operands[0].flags & Operand::FLAG_SELECTED)) + { + ret.operands[0].flags = Operand::FLAG_MASKED; + ret.operands[0].numComponents = NUMCOMPS_4; + } + + return ret; +} + +ProgramEditor::ProgramEditor(const DXBC::DXBCContainer *container, bytebuf &outBlob) + : Program(container->GetDXBCByteCode()->GetTokens()), m_OutBlob(outBlob) +{ + m_SM51 = (m_Major == 0x5 && m_Minor == 0x1); + + m_OutBlob = container->GetShaderBlob(); + + DecodeProgram(); +} + +ProgramEditor::~ProgramEditor() +{ + DXBC::DXBCContainer::ReplaceDXBCBytecode(m_OutBlob, EncodeProgram()); +} + +/* + +this is the rough order of declarations. Sometimes the order is different so it's *unlikely* that +the global order matters strongly but we keep to it where possible just in case. + +E.g. geometry shaders declare outputs after temps, pixel shaders the other way around + +*/ + +static const rdcarray opcodeOrder = { + OPCODE_DCL_INPUT_CONTROL_POINT_COUNT, + + OPCODE_DCL_OUTPUT_CONTROL_POINT_COUNT, + + OPCODE_DCL_TESS_DOMAIN, + + OPCODE_DCL_TESS_PARTITIONING, + + OPCODE_DCL_TESS_OUTPUT_PRIMITIVE, + + OPCODE_DCL_HS_MAX_TESSFACTOR, + + OPCODE_DCL_GLOBAL_FLAGS, + + OPCODE_DCL_CONSTANT_BUFFER, + + OPCODE_DCL_SAMPLER, + + // sorted by space, then register. Types can be intermixed as a result + OPCODE_DCL_RESOURCE, OPCODE_DCL_RESOURCE_RAW, OPCODE_DCL_RESOURCE_STRUCTURED, + + // sorted by space, then register. Types can be intermixed as a result + OPCODE_DCL_UNORDERED_ACCESS_VIEW_TYPED, OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW, + OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED, + + OPCODE_DCL_FUNCTION_BODY, + + OPCODE_DCL_FUNCTION_TABLE, + + OPCODE_DCL_INTERFACE, + + // these can be mixed in signature order + OPCODE_DCL_INPUT_PS, OPCODE_DCL_INPUT_PS_SIV, + // last of the input group + OPCODE_DCL_INPUT_PS_SGV, + + // these can in any order + OPCODE_DCL_INPUT, OPCODE_DCL_INPUT_SIV, OPCODE_DCL_INPUT_SGV, + + // similarly in any order + OPCODE_DCL_OUTPUT, OPCODE_DCL_OUTPUT_SIV, OPCODE_DCL_OUTPUT_SGV, + + OPCODE_DCL_TEMPS, + + OPCODE_DCL_INDEX_RANGE, + + OPCODE_DCL_INDEXABLE_TEMP, + + OPCODE_DCL_THREAD_GROUP, + + OPCODE_DCL_THREAD_GROUP_SHARED_MEMORY_STRUCTURED, + + OPCODE_DCL_THREAD_GROUP_SHARED_MEMORY_RAW, + + OPCODE_DCL_GS_INPUT_PRIMITIVE, + + OPCODE_DCL_STREAM, + + // unknown + OPCODE_DCL_GS_INSTANCE_COUNT, + + OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY, + + // geometry outputs here + + OPCODE_DCL_MAX_OUTPUT_VERTEX_COUNT, + + // these happen in the separate phase declarations + OPCODE_DCL_HS_FORK_PHASE_INSTANCE_COUNT, OPCODE_DCL_HS_JOIN_PHASE_INSTANCE_COUNT, +}; + +size_t ProgramEditor::GetDeclarationPosition(OpcodeType op) +{ + const int32_t opBoundary = opcodeOrder.indexOf(op); + + // find the first declaration after resources + size_t i; + for(i = 0; i < m_Declarations.size(); i++) + { + // resources go before these declarations + int32_t idx = opcodeOrder.indexOf(m_Declarations[i].declaration); + + if(idx > opBoundary) + break; + } + + return i; +} + +uint32_t ProgramEditor::AddTemp() +{ + for(Declaration &decl : m_Declarations) + { + if(decl.declaration == OPCODE_DCL_TEMPS) + { + uint32_t ret = decl.numTemps; + decl.numTemps++; + return ret; + } + } + + Declaration decl; + decl.declaration = OPCODE_DCL_TEMPS; + decl.numTemps = 1; + + m_Declarations.insert(GetDeclarationPosition(OPCODE_DCL_TEMPS), decl); + + return 0; +} + +Edit::ResourceIdentifier ProgramEditor::DeclareResource(const ResourceDecl &desc, uint32_t space, + uint32_t regLow, uint32_t regHigh) +{ + Edit::ResourceIdentifier identifier = {0, ~0U}; + + Declaration decl; + + decl.operand.type = TYPE_RESOURCE; + + if(m_SM51) + { + decl.space = space; + decl.operand.indices.resize(3); + + // the next identifier is the one after all the UAVs, in SM51 they're assigned from 0... + // because they don't correspond to binding registers and are effectively bytecode-local + for(const Declaration &d : m_Declarations) + { + if(d.declaration == OPCODE_DCL_RESOURCE || d.declaration == OPCODE_DCL_RESOURCE_RAW || + d.declaration == OPCODE_DCL_RESOURCE_STRUCTURED) + identifier.first++; + } + + decl.operand.indices[0].absolute = true; + decl.operand.indices[0].index = identifier.first; + decl.operand.indices[1].absolute = true; + decl.operand.indices[1].index = regLow; + decl.operand.indices[2].absolute = true; + decl.operand.indices[2].index = regHigh; + + identifier.second = regLow; + } + else + { + decl.operand.indices.resize(1); + decl.operand.indices[0].absolute = true; + decl.operand.indices[0].index = regLow; + identifier.first = regLow; + } + + if(desc.raw) + { + decl.declaration = OPCODE_DCL_RESOURCE_RAW; + } + else if(desc.structured) + { + decl.declaration = OPCODE_DCL_RESOURCE_STRUCTURED; + + decl.structured.stride = desc.stride; + } + else + { + decl.declaration = OPCODE_DCL_RESOURCE; + + decl.resource.sampleCount = 0; + switch(desc.type) + { + case TextureType::Buffer: decl.resource.dim = RESOURCE_DIMENSION_BUFFER; break; + case TextureType::Texture1D: decl.resource.dim = RESOURCE_DIMENSION_TEXTURE1D; break; + case TextureType::Texture1DArray: + decl.resource.dim = RESOURCE_DIMENSION_TEXTURE1DARRAY; + break; + case TextureType::Texture2D: decl.resource.dim = RESOURCE_DIMENSION_TEXTURE2D; break; + case TextureType::Texture2DArray: + decl.resource.dim = RESOURCE_DIMENSION_TEXTURE2DARRAY; + break; + case TextureType::Texture2DMS: + case TextureType::Texture2DMSArray: + { + decl.resource.dim = desc.type == TextureType::Texture2DMSArray + ? RESOURCE_DIMENSION_TEXTURE2DMSARRAY + : RESOURCE_DIMENSION_TEXTURE2DMS; + decl.resource.sampleCount = desc.sampleCount; + break; + } + case TextureType::Texture3D: decl.resource.dim = RESOURCE_DIMENSION_TEXTURE3D; break; + case TextureType::TextureCube: decl.resource.dim = RESOURCE_DIMENSION_TEXTURECUBE; break; + case TextureType::TextureCubeArray: + decl.resource.dim = RESOURCE_DIMENSION_TEXTURECUBEARRAY; + break; + default: + RDCERR("Invalid texture type in declaration: %s", ToStr(desc.type).c_str()); + return {~0U, ~0U}; + } + + switch(desc.compType) + { + case CompType::Float: decl.resource.resType[0] = DXBC::RETURN_TYPE_FLOAT; break; + case CompType::UNorm: decl.resource.resType[0] = DXBC::RETURN_TYPE_UNORM; break; + case CompType::SNorm: decl.resource.resType[0] = DXBC::RETURN_TYPE_SNORM; break; + case CompType::UInt: decl.resource.resType[0] = DXBC::RETURN_TYPE_UINT; break; + case CompType::SInt: decl.resource.resType[0] = DXBC::RETURN_TYPE_SINT; break; + default: + RDCERR("Invalid component type in declaration: %s", ToStr(desc.compType).c_str()); + return {~0U, ~0U}; + } + + decl.resource.resType[1] = decl.resource.resType[0]; + decl.resource.resType[2] = decl.resource.resType[0]; + decl.resource.resType[3] = decl.resource.resType[0]; + } + + // add at the end of the resources. This may not preserve space/reg sorting depending on the + // declared space or registers (but most likely we will always declare with a high space to not + // stomp on the application's existing bindings) + m_Declarations.insert(GetDeclarationPosition(OPCODE_DCL_RESOURCE_STRUCTURED), decl); + + return identifier; +} + +Edit::ResourceIdentifier ProgramEditor::DeclareUAV(const ResourceDecl &desc, uint32_t space, + uint32_t regLow, uint32_t regHigh) +{ + Edit::ResourceIdentifier identifier = {0, ~0U}; + + Declaration decl; + + decl.operand.type = TYPE_UNORDERED_ACCESS_VIEW; + + if(m_SM51) + { + decl.space = space; + decl.operand.indices.resize(3); + + // the next identifier is the one after all the UAVs, in SM51 they're assigned from 0... + // because they don't correspond to binding registers and are effectively bytecode-local + for(const Declaration &d : m_Declarations) + { + if(d.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_TYPED || + d.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW || + d.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED) + identifier.first++; + } + + decl.operand.indices[0].absolute = true; + decl.operand.indices[0].index = identifier.first; + decl.operand.indices[1].absolute = true; + decl.operand.indices[1].index = regLow; + decl.operand.indices[2].absolute = true; + decl.operand.indices[2].index = regHigh; + + identifier.second = regLow; + } + else + { + decl.operand.indices.resize(1); + decl.operand.indices[0].absolute = true; + decl.operand.indices[0].index = regLow; + identifier.first = regLow; + } + + if(desc.raw) + { + decl.declaration = OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW; + + decl.raw.rov = desc.rov; + decl.raw.globallyCoherant = desc.globallyCoherant; + } + else if(desc.structured) + { + decl.declaration = OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED; + + decl.structured.stride = desc.stride; + + decl.structured.hasCounter = desc.hasCounter; + decl.structured.rov = desc.rov; + decl.structured.globallyCoherant = desc.globallyCoherant; + } + else + { + decl.declaration = OPCODE_DCL_UNORDERED_ACCESS_VIEW_TYPED; + + decl.uav_typed.rov = desc.rov; + decl.uav_typed.globallyCoherant = desc.globallyCoherant; + + switch(desc.type) + { + case TextureType::Buffer: decl.uav_typed.dim = RESOURCE_DIMENSION_BUFFER; break; + case TextureType::Texture1D: decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURE1D; break; + case TextureType::Texture1DArray: + decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURE1DARRAY; + break; + case TextureType::Texture2D: decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURE2D; break; + case TextureType::Texture2DArray: + decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURE2DARRAY; + break; + case TextureType::Texture3D: decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURE3D; break; + case TextureType::TextureCube: decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURECUBE; break; + case TextureType::TextureCubeArray: + decl.uav_typed.dim = RESOURCE_DIMENSION_TEXTURECUBEARRAY; + break; + default: + RDCERR("Invalid texture type in declaration: %s", ToStr(desc.type).c_str()); + return {~0U, ~0U}; + } + + switch(desc.compType) + { + case CompType::Float: decl.uav_typed.resType[0] = DXBC::RETURN_TYPE_FLOAT; break; + case CompType::UNorm: decl.uav_typed.resType[0] = DXBC::RETURN_TYPE_UNORM; break; + case CompType::SNorm: decl.uav_typed.resType[0] = DXBC::RETURN_TYPE_SNORM; break; + case CompType::UInt: decl.uav_typed.resType[0] = DXBC::RETURN_TYPE_UINT; break; + case CompType::SInt: decl.uav_typed.resType[0] = DXBC::RETURN_TYPE_SINT; break; + default: + RDCERR("Invalid component type in declaration: %s", ToStr(desc.compType).c_str()); + return {~0U, ~0U}; + } + + decl.uav_typed.resType[1] = decl.uav_typed.resType[0]; + decl.uav_typed.resType[2] = decl.uav_typed.resType[0]; + decl.uav_typed.resType[3] = decl.uav_typed.resType[0]; + } + + // add at the end of the UAVs. This may not preserve space/reg sorting depending on the declared + // space or registers (but most likely we will always declare with a high space to not stomp on + // the application's existing bindings) + m_Declarations.insert(GetDeclarationPosition(OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED), decl); + + return identifier; +} + +}; // namespace DXBCBytecode + +#if ENABLED(ENABLE_UNIT_TESTS) + +#include "catch/catch.hpp" + +#include "common/formatting.h" +#include "driver/dx/official/d3dcompiler.h" +#include "dxbc_compile.h" + +static bytebuf dxbccompile(const rdcstr &source, const rdcstr &profile = "ps_5_0", + uint32_t flags = D3DCOMPILE_OPTIMIZATION_LEVEL0 | + D3DCOMPILE_SKIP_OPTIMIZATION) +{ + HMODULE d3dcompiler = GetD3DCompiler(); + + if(!d3dcompiler) + return {}; + + pD3DCompile compileFunc = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile"); + + if(compileFunc == NULL) + { + RDCFATAL("Can't get D3DCompile from d3dcompiler_??.dll"); + } + + HRESULT hr = S_OK; + + ID3DBlob *byteBlob = NULL; + + ID3DBlob *errBlob; + hr = compileFunc(source.c_str(), source.size(), "main", NULL, NULL, "main", profile.c_str(), + flags, 0, &byteBlob, &errBlob); + + if(errBlob) + RDCLOG("%s", (char *)errBlob->GetBufferPointer()); + + REQUIRE(SUCCEEDED(hr)); + + bytebuf bytecode; + bytecode.assign((const byte *)byteBlob->GetBufferPointer(), byteBlob->GetBufferSize()); + + SAFE_RELEASE(byteBlob); + + return bytecode; +} + +static rdcstr make_source(const rdcstr &snippet) +{ + rdcstr source; + if(snippet.contains("main(")) + source = snippet; + else + source = R"( +cbuffer cbuf : register(b0) +{ + float4 cbuf_float4; + float cbuf_float; + uint cbuf_uint; +}; + +SamplerState samp : register(s0); +Texture2D tex : register(t0); +Texture2D tex2 : register(t1); +Texture2D tex3 : register(t2); +Texture2D tex4 : register(t3); + +float4 main(float input : INPUT) : SV_Target0 +{ + float4 ret = input.xxxx; +)" + snippet + + R"( + return ret; +} +)"; + + return source; +} + +static int substrCount(const rdcstr &haystack, const rdcstr &needle) +{ + int ret = 0; + int idx = 0; + + while(true) + { + idx = haystack.find(needle, idx); + + if(idx < 0) + break; + + ret++; + idx++; + } + + return ret; +} + +TEST_CASE("Test shader editing", "[dxbc]") +{ + SECTION("Test that making no changes means no binary changes") + { + // create snippets that affect the compilation since we don't have embedded source + rdcarray snippets = { + R"( +)", + R"( +ret.x = sin(ret.x); +)", + R"( +ret.xy = cos(ret.zw * ret.xy); +)", + R"( +ret.xy += sqrt(ret.z).xx; +)", + R"( +ret.zw += tex.Load(ret.xyz).yz; +)", + }; + + for(rdcstr snippet : snippets) + { + for(rdcstr profile : {"ps_5_0", "ps_5_1"}) + { + bytebuf bytecode = dxbccompile(make_source(snippet), profile); + + DXBC::DXBCContainer container(bytecode, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + bytebuf edited; + { + DXBCBytecode::ProgramEditor editor(&container, edited); + } + + CHECK(bytecode == edited); + } + } + } + + SECTION("Test adding more temporary registers") + { + rdcarray snippets = { + // no temps at all + R"( + float4 main(float input : INPUT) : SV_Target0 + { + return input.xxxx; + } +)", + // one temp for ret + R"( +)", + // 2 temps, for ret and temp + R"( + float4 temp = sqrt(input); + ret *= temp; +)", + }; + + for(size_t i = 0; i < snippets.size(); i++) + { + for(rdcstr profile : {"ps_5_0", "ps_5_1"}) + { + bytebuf bytecode = dxbccompile(make_source(snippets[i]), profile); + + DXBC::DXBCContainer container(bytecode, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_before = container.GetDXBCByteCode()->GetDisassembly(); + uint32_t temp_index = 123456; + + bytebuf edited; + { + DXBCBytecode::ProgramEditor editor(&container, edited); + + temp_index = editor.AddTemp(); + } + + DXBC::DXBCContainer container2(edited, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_after = container2.GetDXBCByteCode()->GetDisassembly(); + + CHECK(temp_index == (uint32_t)i); + if(i == 0) + { + CHECK(!disasm_before.contains("dcl_temp")); + } + else + { + CHECK(disasm_before.contains(StringFormat::Fmt("dcl_temps %u", i))); + } + + CHECK(disasm_after.contains(StringFormat::Fmt("dcl_temps %u", i + 1))); + } + } + }; + + SECTION("Test adding simple instructions") + { + for(rdcstr profile : {"ps_5_0", "ps_5_1"}) + { + bytebuf bytecode = dxbccompile(make_source(""), profile); + + DXBC::DXBCContainer container(bytecode, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_before = container.GetDXBCByteCode()->GetDisassembly(); + uint32_t t = 123456; + + bytebuf edited; + { + DXBCBytecode::ProgramEditor editor(&container, edited); + + t = editor.AddTemp(); + + CHECK(t == 1); + + using namespace DXBCBytecode; + using namespace DXBCBytecode::Edit; + + // mov r1.x, v0.x + editor.InsertOperation(0, oper(OPCODE_MOV, {temp(t).swizzle(0), input(0).swizzle(0)})); + // sqrt r1.y, r1.x + editor.InsertOperation(1, oper(OPCODE_SQRT, {temp(t).swizzle(1), temp(t).swizzle(0)})); + // mul r1.z, r1.x, r1.x + editor.InsertOperation( + 2, oper(OPCODE_MUL, {temp(t).swizzle(2), temp(t).swizzle(0), temp(t).swizzle(1)})); + + DXBCBytecode::Operation &op = editor.GetInstruction(3); + + REQUIRE(op.operands.size() == 2); + CHECK(op.operands[1].type == TYPE_INPUT); + + // using reswizzle will mean that the mask will get applied - i.e. if the original operand + // was + // .xyz_ then we'll reswizzle .zzzz into .zzz_ + // in practice we know this shader reads from v0.xxxx but let's test that this works as + // expected + op.operands[1] = temp(t) + .swizzle(2, 2, 2, 2) + .reswizzle(op.operands[1].comps[0], op.operands[1].comps[1], + op.operands[1].comps[2], op.operands[1].comps[3]); + } + + DXBC::DXBCContainer container2(edited, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_after = container2.GetDXBCByteCode()->GetDisassembly(); + + CHECK(!disasm_before.contains("sqrt ")); + CHECK(disasm_after.contains("mov r1.x, v0.x")); + CHECK(disasm_after.contains("sqrt r1.y, r1.x")); + CHECK(disasm_after.contains("mul r1.z, r1.x, r1.y")); + + CHECK(disasm_before.contains("mov r0.xyzw, v0.xxxx")); + CHECK(disasm_after.contains("mov r0.xyzw, r1.zzzz")); + } + }; + + SECTION("Test adding UAV and access instructions") + { + for(rdcstr profile : {"ps_5_0", "ps_5_1"}) + { + bytebuf bytecode = dxbccompile(make_source(R"( +uint3 uvm = uint3(ret.xyz); +ret += tex.Load(uvm); +uvm += uint3(1,2,3); +ret += tex2.Load(uvm); +ret += tex3.Load(uvm); +ret += tex2.Load(uvm); +ret += tex4.Load(uvm); +ret += tex2.Load(uvm); +)"), + profile); + + DXBC::DXBCContainer container(bytecode, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_before = container.GetDXBCByteCode()->GetDisassembly(); + + bytebuf edited; + { + DXBCBytecode::ProgramEditor editor(&container, edited); + + uint32_t t = editor.AddTemp(); + + DXBCBytecode::ResourceDecl desc; + desc.compType = CompType::UInt; + desc.type = TextureType::Buffer; + desc.raw = true; + + DXBCBytecode::Edit::ResourceIdentifier u = editor.DeclareUAV(desc, 12, 7, 7); + + uint32_t texOffset[] = {1, 19, 235, 7534, 8294, 67952}; + + for(size_t i = 0; i < editor.GetNumInstructions(); i++) + { + const DXBCBytecode::Operation &op = editor.GetInstruction(i); + + using namespace DXBCBytecode; + using namespace DXBCBytecode::Edit; + + if(op.operation == OPCODE_LD) + { + Operand coords = op.operands[1]; + uint32_t texIndex = (uint32_t)op.operands[2].indices[0].index; + + // add the x coord to the y coord of the load + editor.InsertOperation( + i++, oper(OPCODE_IADD, {temp(t).swizzle(0), coords.swizzle(0), coords.swizzle(1)})); + // add some value depending on which texture is being loaded from + editor.InsertOperation(i++, oper(OPCODE_IADD, {temp(t).swizzle(0), temp(t).swizzle(0), + imm(texOffset[texIndex])})); + editor.InsertOperation(i++, + oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(~0U)})); + } + } + } + + DXBC::DXBCContainer container2(edited, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U); + + rdcstr disasm_after = container2.GetDXBCByteCode()->GetDisassembly(); + + CHECK(substrCount(disasm_before, "ld_indexable") == 6); + CHECK(substrCount(disasm_before, "atomic_or") == 0); + CHECK(substrCount(disasm_before, "dcl_uav") == 0); + CHECK(substrCount(disasm_before, "iadd") == 1); + + CHECK(substrCount(disasm_after, "ld_indexable") == 6); + CHECK(substrCount(disasm_after, "atomic_or") == 6); + CHECK(substrCount(disasm_after, "dcl_uav") == 1); + CHECK(substrCount(disasm_after, "iadd") == 1 + 12); // two per lookup + + CHECK(disasm_after.contains("iadd r4.x, r4.x, l(7534, 0, 0, 0)")); + } + }; +} + +#endif diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h new file mode 100644 index 000000000..17822b2a4 --- /dev/null +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h @@ -0,0 +1,169 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include "dxbc_bytecode.h" + +namespace DXBC +{ +class DXBCContainer; +}; + +namespace DXBCBytecode +{ +namespace Edit +{ +inline Operand temp(uint32_t reg) +{ + Operand o; + o.type = TYPE_TEMP; + o.indices.resize(1); + o.indices[0].absolute = true; + o.indices[0].index = reg; + return o.swizzle(0, 1, 2, 3); +} + +inline Operand input(uint32_t reg) +{ + Operand o; + o.type = TYPE_INPUT; + o.indices.resize(1); + o.indices[0].absolute = true; + o.indices[0].index = reg; + return o.swizzle(0, 1, 2, 3); +} + +inline Operand imm(uint32_t val) +{ + Operand o; + o.type = TYPE_IMMEDIATE32; + o.values[0] = val; + return o.swizzle(0); +} + +inline Operand imm(uint32_t x, uint32_t y, uint32_t z, uint32_t w) +{ + Operand o; + o.type = TYPE_IMMEDIATE32; + o.values[0] = x; + o.values[1] = y; + o.values[2] = z; + o.values[3] = w; + return o.swizzle(0, 1, 2, 3); +} + +typedef rdcpair ResourceIdentifier; + +inline Operand res(ResourceIdentifier identifier, uint32_t idx = 0) +{ + Operand o; + o.type = TYPE_RESOURCE; + if(identifier.second == ~0U) + { + // SM5.0, just identifier + o.indices.resize(1); + o.indices[0].absolute = true; + o.indices[0].index = identifier.first; + } + else + { + // SM5.1 identifier and register + o.indices.resize(2); + o.indices[0].absolute = true; + o.indices[0].index = identifier.first; + o.indices[1].absolute = true; + o.indices[1].index = identifier.second + idx; + } + return o.swizzle(0, 1, 2, 3); +} + +inline Operand uav(ResourceIdentifier identifier, uint32_t idx = 0) +{ + Operand o; + o.type = TYPE_UNORDERED_ACCESS_VIEW; + if(identifier.second == ~0U) + { + // SM5.0, just identifier + o.indices.resize(1); + o.indices[0].absolute = true; + o.indices[0].index = identifier.first; + } + else + { + // SM5.1 identifier and register + o.indices.resize(2); + o.indices[0].absolute = true; + o.indices[0].index = identifier.first; + o.indices[1].absolute = true; + o.indices[1].index = identifier.second + idx; + } + return o.swizzle(0, 1, 2, 3); +} + +Operation oper(OpcodeType o, const rdcarray &operands); + +}; // namespace Edit + +struct ResourceDecl +{ + TextureType type; + CompType compType; + uint32_t sampleCount; + + bool structured = false; + uint32_t stride; + bool hasCounter, globallyCoherant, rov; + + bool raw = false; +}; + +class ProgramEditor : public Program +{ +public: + ProgramEditor(const DXBC::DXBCContainer *container, bytebuf &outBlob); + ~ProgramEditor(); + + uint32_t AddTemp(); + + Edit::ResourceIdentifier DeclareResource(const ResourceDecl &desc, uint32_t space, + uint32_t regLow, uint32_t regHigh); + Edit::ResourceIdentifier DeclareUAV(const ResourceDecl &desc, uint32_t space, uint32_t regLow, + uint32_t regHigh); + + void InsertOperation(size_t beforeIndex, const Operation &op) + { + m_Instructions.insert(beforeIndex, op); + } + + void RemoveOperation(size_t idx, size_t count = 1) { m_Instructions.erase(idx, count); } + Operation &GetInstruction(size_t idx) { return m_Instructions[idx]; }; +private: + bytebuf &m_OutBlob; + bool m_SM51 = false; + + size_t GetDeclarationPosition(OpcodeType op); +}; + +}; // namespace DXBCBytecode diff --git a/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj b/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj index 073db8f68..f4e005c92 100644 --- a/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj +++ b/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj @@ -102,6 +102,7 @@ + @@ -117,6 +118,7 @@ + diff --git a/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj.filters b/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj.filters index 699e8e5b2..8973efeda 100644 --- a/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj.filters +++ b/renderdoc/driver/shaders/dxbc/renderdoc_dxbc.vcxproj.filters @@ -14,6 +14,7 @@ + @@ -34,6 +35,7 @@ +