mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-31 03:41:01 +00:00
2590 lines
90 KiB
C++
2590 lines
90 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2023 Baldur Karlsson
|
|
* Copyright (c) 2014 Crytek
|
|
*
|
|
* 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_container.h"
|
|
#include <algorithm>
|
|
#include "api/app/renderdoc_app.h"
|
|
#include "common/common.h"
|
|
#include "core/settings.h"
|
|
#include "driver/shaders/dxil/dxil_bytecode.h"
|
|
#include "lz4/lz4.h"
|
|
#include "md5/md5.h"
|
|
#include "replay/replay_driver.h"
|
|
#include "serialise/serialiser.h"
|
|
#include "dxbc_bytecode.h"
|
|
|
|
#include "driver/dx/official/d3dcompiler.h"
|
|
|
|
// this is extern so that it can be shared with vulkan
|
|
RDOC_EXTERN_CONFIG(rdcarray<rdcstr>, DXBC_Debug_SearchDirPaths);
|
|
|
|
namespace DXBC
|
|
{
|
|
struct RDEFCBufferVariable
|
|
{
|
|
uint32_t nameOffset;
|
|
|
|
uint32_t startOffset; // start offset in bytes of this variable in the cbuffer
|
|
uint32_t size; // size in bytes of this type
|
|
uint32_t flags;
|
|
|
|
uint32_t typeOffset; // offset to a RDEFCBufferType
|
|
uint32_t defaultValueOffset; // offset to [size] bytes where the default value can be found, or
|
|
// 0 for no default value
|
|
|
|
uint32_t unknown[4]; // this is only present for RDEFHeader.targetVersion >= 0x500. In earlier
|
|
// versions, this is not in the file.
|
|
};
|
|
|
|
struct RDEFCBuffer
|
|
{
|
|
uint32_t nameOffset; // relative to the same offset base position as others in this chunk -
|
|
// after FourCC and chunk length.
|
|
|
|
DXBC::CountOffset variables;
|
|
uint32_t size; // size in bytes of this cbuffer
|
|
uint32_t flags;
|
|
uint32_t type;
|
|
|
|
// followed immediately by [variables.count] RDEFCBufferVariables
|
|
};
|
|
|
|
// mostly for nested structures
|
|
struct RDEFCBufferChildType
|
|
{
|
|
uint32_t nameOffset;
|
|
uint32_t typeOffset; // offset to a RDEFCBufferType
|
|
uint32_t memberOffset; // byte offset in the parent structure - not a file offset
|
|
};
|
|
|
|
struct RDEFCBufferType
|
|
{
|
|
uint16_t varClass; // D3D_SHADER_VARIABLE_CLASS
|
|
uint16_t varType; // D3D_SHADER_VARIABLE_TYPE
|
|
|
|
uint16_t rows;
|
|
uint16_t cols;
|
|
|
|
uint16_t numElems;
|
|
uint16_t numMembers;
|
|
|
|
uint32_t memberOffset; // offset to [numMembers] RDEFCBufferChildTypes that point to the member
|
|
// types
|
|
|
|
// my own guessing - not in wine structures
|
|
// looks like these are only present for RD11 shaders
|
|
uint32_t unknown[4];
|
|
|
|
uint32_t nameOffset; // offset to type name
|
|
};
|
|
|
|
// this isn't a proper chunk, it's the file header before all the chunks.
|
|
struct FileHeader
|
|
{
|
|
uint32_t fourcc; // "DXBC"
|
|
uint32_t hashValue[4]; // unknown hash function and data
|
|
uint32_t containerVersion;
|
|
uint32_t fileLength;
|
|
uint32_t numChunks;
|
|
// uint32 chunkOffsets[numChunks]; follows
|
|
};
|
|
|
|
struct ILDNHeader
|
|
{
|
|
uint16_t Flags;
|
|
uint16_t NameLength;
|
|
char Name[1];
|
|
};
|
|
|
|
enum class HASHFlags : uint32_t
|
|
{
|
|
INCLUDES_SOURCE = 0x1,
|
|
};
|
|
|
|
struct HASHHeader
|
|
{
|
|
HASHFlags Flags;
|
|
uint32_t hashValue[4];
|
|
};
|
|
|
|
struct RDEFHeader
|
|
{
|
|
//////////////////////////////////////////////////////
|
|
// offsets are relative to this position in the file.
|
|
// NOT the end of this structure. Note this differs
|
|
// from the SDBG chunk, but matches the SIGN chunks
|
|
|
|
// note that these two actually come in the opposite order after
|
|
// this header. So cbuffers offset will be higher than resources
|
|
// offset
|
|
CountOffset cbuffers;
|
|
CountOffset resources;
|
|
|
|
uint16_t targetVersion; // 0x0501 is the latest.
|
|
uint16_t targetShaderStage; // 0xffff for pixel shaders, 0xfffe for vertex shaders
|
|
|
|
uint32_t flags;
|
|
uint32_t creatorOffset; // null terminated ascii string
|
|
|
|
uint32_t unknown[8]; // this is only present for targetVersion >= 0x500. In earlier versions,
|
|
// this is not in the file.
|
|
};
|
|
|
|
struct RDEFResource
|
|
{
|
|
uint32_t nameOffset; // relative to the same offset base position as others in this chunk -
|
|
// after FourCC and chunk length.
|
|
|
|
uint32_t type;
|
|
uint32_t retType;
|
|
uint32_t dimension;
|
|
int32_t sampleCount;
|
|
uint32_t bindPoint;
|
|
uint32_t bindCount;
|
|
uint32_t flags;
|
|
|
|
// this is only present for RDEFHeader.targetVersion >= 0x501.
|
|
uint32_t space;
|
|
// the ID seems to be a 0-based name fxc generates to refer to the object.
|
|
// We don't use it, and it's easy enough to re-generate
|
|
uint32_t ID;
|
|
};
|
|
|
|
struct SIGNHeader
|
|
{
|
|
//////////////////////////////////////////////////////
|
|
// offsets are relative to this position in the file.
|
|
// NOT the end of this structure. Note this differs
|
|
// from the SDBG chunk, but matches the RDEF chunk
|
|
|
|
uint32_t numElems;
|
|
uint32_t unknown;
|
|
|
|
// followed by SIGNElement elements[numElems]; - note that SIGNElement's size depends on the type.
|
|
// for OSG5 you should use SIGNElement7
|
|
};
|
|
|
|
struct PRIVHeader
|
|
{
|
|
uint32_t fourcc; // "PRIV"
|
|
uint32_t chunkLength; // length of this chunk
|
|
|
|
GUID debugInfoGUID; // GUID/magic number, since PRIV data could be used for something else.
|
|
// Set to the value of RENDERDOC_ShaderDebugMagicValue from
|
|
// renderdoc_app.h which can also be used as a GUID to set the path
|
|
// at runtime via SetPrivateData (see documentation)
|
|
|
|
static const GUID RENDERDOC_ShaderDebugMagicValue;
|
|
|
|
void *data;
|
|
};
|
|
|
|
const GUID PRIVHeader::RENDERDOC_ShaderDebugMagicValue = RENDERDOC_ShaderDebugMagicValue_struct;
|
|
|
|
struct SIGNElement
|
|
{
|
|
uint32_t nameOffset; // relative to the same offset base position as others in similar chunks -
|
|
// after FourCC and chunk length.
|
|
|
|
uint32_t semanticIdx;
|
|
SVSemantic systemType;
|
|
uint32_t componentType;
|
|
uint32_t registerNum;
|
|
|
|
byte mask;
|
|
byte rwMask;
|
|
uint16_t unused;
|
|
};
|
|
|
|
struct SIGNElement7
|
|
{
|
|
uint32_t stream;
|
|
SIGNElement elem;
|
|
};
|
|
|
|
enum MinimumPrecision
|
|
{
|
|
PRECISION_DEFAULT,
|
|
PRECISION_FLOAT16,
|
|
PRECISION_FLOAT10,
|
|
PRECISION_UNUSED,
|
|
PRECISION_SINT16,
|
|
PRECISION_UINT16,
|
|
PRECISION_ANY16,
|
|
PRECISION_ANY10,
|
|
|
|
NUM_PRECISIONS,
|
|
};
|
|
|
|
struct SIGNElement1
|
|
{
|
|
uint32_t stream;
|
|
SIGNElement elem;
|
|
MinimumPrecision precision;
|
|
};
|
|
|
|
static const uint32_t STATSizeDX10 = 29 * 4; // either 29 uint32s
|
|
static const uint32_t STATSizeDX11 = 37 * 4; // or 37 uint32s
|
|
|
|
ShaderBuiltin GetSystemValue(SVSemantic systemValue)
|
|
{
|
|
switch(systemValue)
|
|
{
|
|
case SVNAME_UNDEFINED: return ShaderBuiltin::Undefined;
|
|
case SVNAME_POSITION: return ShaderBuiltin::Position;
|
|
case SVNAME_CLIP_DISTANCE: return ShaderBuiltin::ClipDistance;
|
|
case SVNAME_CULL_DISTANCE: return ShaderBuiltin::CullDistance;
|
|
case SVNAME_RENDER_TARGET_ARRAY_INDEX: return ShaderBuiltin::RTIndex;
|
|
case SVNAME_VIEWPORT_ARRAY_INDEX: return ShaderBuiltin::ViewportIndex;
|
|
case SVNAME_VERTEX_ID: return ShaderBuiltin::VertexIndex;
|
|
case SVNAME_PRIMITIVE_ID: return ShaderBuiltin::PrimitiveIndex;
|
|
case SVNAME_INSTANCE_ID: return ShaderBuiltin::InstanceIndex;
|
|
case SVNAME_IS_FRONT_FACE: return ShaderBuiltin::IsFrontFace;
|
|
case SVNAME_SAMPLE_INDEX: return ShaderBuiltin::MSAASampleIndex;
|
|
case SVNAME_FINAL_QUAD_EDGE_TESSFACTOR0:
|
|
case SVNAME_FINAL_QUAD_EDGE_TESSFACTOR1:
|
|
case SVNAME_FINAL_QUAD_EDGE_TESSFACTOR2:
|
|
case SVNAME_FINAL_QUAD_EDGE_TESSFACTOR3: return ShaderBuiltin::OuterTessFactor;
|
|
case SVNAME_FINAL_QUAD_INSIDE_TESSFACTOR0:
|
|
case SVNAME_FINAL_QUAD_INSIDE_TESSFACTOR1: return ShaderBuiltin::InsideTessFactor;
|
|
case SVNAME_FINAL_TRI_EDGE_TESSFACTOR0:
|
|
case SVNAME_FINAL_TRI_EDGE_TESSFACTOR1:
|
|
case SVNAME_FINAL_TRI_EDGE_TESSFACTOR2: return ShaderBuiltin::OuterTessFactor;
|
|
case SVNAME_FINAL_TRI_INSIDE_TESSFACTOR: return ShaderBuiltin::InsideTessFactor;
|
|
case SVNAME_FINAL_LINE_DETAIL_TESSFACTOR: return ShaderBuiltin::OuterTessFactor;
|
|
case SVNAME_FINAL_LINE_DENSITY_TESSFACTOR: return ShaderBuiltin::InsideTessFactor;
|
|
case SVNAME_BARYCENTRICS: return ShaderBuiltin::Barycentrics;
|
|
case SVNAME_SHADINGRATE: return ShaderBuiltin::PackedFragRate;
|
|
case SVNAME_CULLPRIMITIVE: return ShaderBuiltin::CullPrimitive;
|
|
case SVNAME_TARGET: return ShaderBuiltin::ColorOutput;
|
|
case SVNAME_DEPTH: return ShaderBuiltin::DepthOutput;
|
|
case SVNAME_COVERAGE: return ShaderBuiltin::MSAACoverage;
|
|
case SVNAME_DEPTH_GREATER_EQUAL: return ShaderBuiltin::DepthOutputGreaterEqual;
|
|
case SVNAME_DEPTH_LESS_EQUAL: return ShaderBuiltin::DepthOutputLessEqual;
|
|
case SVNAME_STENCIL_REF: return ShaderBuiltin::StencilReference;
|
|
case SVNAME_INNER_COVERAGE: return ShaderBuiltin::IsFullyCovered;
|
|
}
|
|
|
|
return ShaderBuiltin::Undefined;
|
|
}
|
|
|
|
rdcstr TypeName(CBufferVariableType desc)
|
|
{
|
|
rdcstr ret;
|
|
|
|
char *type = "";
|
|
switch(desc.varType)
|
|
{
|
|
case VarType::Bool: type = "bool"; break;
|
|
case VarType::SInt: type = "int"; break;
|
|
case VarType::Float: type = "float"; break;
|
|
case VarType::Double: type = "double"; break;
|
|
case VarType::UInt: type = "uint"; break;
|
|
case VarType::UByte: type = "ubyte"; break;
|
|
case VarType::Unknown: type = "void"; break;
|
|
default: RDCERR("Unexpected type in RDEF variable type %d", type);
|
|
}
|
|
|
|
if(desc.varClass == CLASS_OBJECT)
|
|
RDCERR("Unexpected object in RDEF variable type");
|
|
else if(desc.varClass == CLASS_INTERFACE_CLASS)
|
|
RDCERR("Unexpected iface class in RDEF variable type");
|
|
else if(desc.varClass == CLASS_INTERFACE_POINTER)
|
|
ret = type;
|
|
else if(desc.varClass == CLASS_STRUCT)
|
|
ret = "<unnamed>";
|
|
else
|
|
{
|
|
if(desc.rows > 1)
|
|
{
|
|
ret = StringFormat::Fmt("%s%dx%d", type, desc.rows, desc.cols);
|
|
}
|
|
else if(desc.cols > 1)
|
|
{
|
|
ret = StringFormat::Fmt("%s%d", type, desc.cols);
|
|
}
|
|
else
|
|
{
|
|
ret = type;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
CBufferVariableType DXBCContainer::ParseRDEFType(const RDEFHeader *h, const byte *chunkContents,
|
|
uint32_t typeOffset)
|
|
{
|
|
if(m_Variables.find(typeOffset) != m_Variables.end())
|
|
return m_Variables[typeOffset];
|
|
|
|
const RDEFCBufferType *type = (const RDEFCBufferType *)(chunkContents + typeOffset);
|
|
|
|
CBufferVariableType ret;
|
|
|
|
ret.varClass = (VariableClass)type->varClass;
|
|
ret.cols = RDCMAX(1U, (uint32_t)type->cols);
|
|
ret.elements = RDCMAX(1U, (uint32_t)type->numElems);
|
|
ret.rows = RDCMAX(1U, (uint32_t)type->rows);
|
|
|
|
switch((VariableType)type->varType)
|
|
{
|
|
// DXBC treats all cbuffer variables as 32-bit regardless of declaration
|
|
case DXBC::VARTYPE_MIN12INT:
|
|
case DXBC::VARTYPE_MIN16INT:
|
|
case DXBC::VARTYPE_INT: ret.varType = VarType::SInt; break;
|
|
case DXBC::VARTYPE_BOOL: ret.varType = VarType::Bool; break;
|
|
case DXBC::VARTYPE_MIN16UINT:
|
|
case DXBC::VARTYPE_UINT: ret.varType = VarType::UInt; break;
|
|
case DXBC::VARTYPE_INT64:
|
|
case DXBC::VARTYPE_UINT64:
|
|
case DXBC::VARTYPE_DOUBLE: ret.varType = VarType::Double; break;
|
|
case DXBC::VARTYPE_FLOAT:
|
|
case DXBC::VARTYPE_MIN8FLOAT:
|
|
case DXBC::VARTYPE_MIN10FLOAT:
|
|
case DXBC::VARTYPE_MIN16FLOAT: ret.varType = VarType::Float; break;
|
|
// new types are actually 16-bit, though alignment is still the same as 32-bit
|
|
case DXBC::VARTYPE_INT16: ret.varType = VarType::SShort; break;
|
|
case DXBC::VARTYPE_UINT16: ret.varType = VarType::UShort; break;
|
|
case DXBC::VARTYPE_FLOAT16: ret.varType = VarType::Half; break;
|
|
default: ret.varType = VarType::Float; break;
|
|
}
|
|
|
|
ret.name = TypeName(ret);
|
|
|
|
if(ret.name == "interface")
|
|
{
|
|
if(h->targetVersion >= 0x500 && type->nameOffset > 0)
|
|
{
|
|
ret.name += " " + rdcstr((const char *)chunkContents + type->nameOffset);
|
|
}
|
|
else
|
|
{
|
|
ret.name += StringFormat::Fmt(" unnamed_iface_0x%08x", typeOffset);
|
|
}
|
|
}
|
|
|
|
// rename unnamed structs to have valid identifiers as type name
|
|
if(ret.name.contains("<unnamed>"))
|
|
{
|
|
if(h->targetVersion >= 0x500 && type->nameOffset > 0)
|
|
{
|
|
ret.name = (const char *)chunkContents + type->nameOffset;
|
|
}
|
|
else
|
|
{
|
|
ret.name = StringFormat::Fmt("unnamed_struct_0x%08x", typeOffset);
|
|
}
|
|
}
|
|
|
|
if(type->memberOffset)
|
|
{
|
|
const RDEFCBufferChildType *members =
|
|
(const RDEFCBufferChildType *)(chunkContents + type->memberOffset);
|
|
|
|
ret.members.reserve(type->numMembers);
|
|
|
|
ret.bytesize = 0;
|
|
|
|
for(int32_t j = 0; j < type->numMembers; j++)
|
|
{
|
|
CBufferVariable v;
|
|
|
|
v.name = (const char *)(chunkContents + members[j].nameOffset);
|
|
v.type = ParseRDEFType(h, chunkContents, members[j].typeOffset);
|
|
v.offset = members[j].memberOffset;
|
|
|
|
ret.bytesize = v.offset + v.type.bytesize;
|
|
|
|
ret.members.push_back(v);
|
|
}
|
|
|
|
ret.bytesize *= RDCMAX(1U, ret.elements);
|
|
}
|
|
else
|
|
{
|
|
// matrices take up a full vector for each column or row depending which is major, regardless of
|
|
// the other dimension
|
|
if(ret.varClass == CLASS_MATRIX_COLUMNS)
|
|
{
|
|
ret.bytesize = VarTypeByteSize(ret.varType) * ret.cols * 4 * RDCMAX(1U, ret.elements);
|
|
}
|
|
else if(ret.varClass == CLASS_MATRIX_ROWS)
|
|
{
|
|
ret.bytesize = VarTypeByteSize(ret.varType) * ret.rows * 4 * RDCMAX(1U, ret.elements);
|
|
}
|
|
else
|
|
{
|
|
// arrays also take up a full vector for each element
|
|
if(ret.elements > 1)
|
|
ret.bytesize = VarTypeByteSize(ret.varType) * 4 * RDCMAX(1U, ret.elements);
|
|
else
|
|
ret.bytesize = VarTypeByteSize(ret.varType) * ret.rows * ret.cols;
|
|
}
|
|
}
|
|
|
|
m_Variables[typeOffset] = ret;
|
|
return ret;
|
|
}
|
|
|
|
D3D_PRIMITIVE_TOPOLOGY DXBCContainer::GetOutputTopology()
|
|
{
|
|
if(m_OutputTopology == D3D_PRIMITIVE_TOPOLOGY_UNDEFINED)
|
|
{
|
|
m_OutputTopology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
|
|
|
|
if(m_DXBCByteCode)
|
|
m_OutputTopology = m_DXBCByteCode->GetOutputTopology();
|
|
else if(m_DXILByteCode)
|
|
m_OutputTopology = m_DXILByteCode->GetOutputTopology();
|
|
}
|
|
|
|
return m_OutputTopology;
|
|
}
|
|
|
|
D3D_PRIMITIVE_TOPOLOGY DXBCContainer::GetOutputTopology(const void *ByteCode, size_t ByteCodeLength)
|
|
{
|
|
const FileHeader *header = (const FileHeader *)ByteCode;
|
|
|
|
const byte *data = (const byte *)ByteCode; // just for convenience
|
|
|
|
if(ByteCode == NULL || ByteCodeLength == 0)
|
|
return D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCodeLength)
|
|
return D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
|
|
|
const uint32_t *chunkOffsets = (const uint32_t *)(header + 1); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
const uint32_t *fourcc = (const uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
const uint32_t *chunkSize = (const uint32_t *)(fourcc + 1);
|
|
|
|
const byte *chunkContents = (const byte *)(chunkSize + 1);
|
|
|
|
if(*fourcc == FOURCC_SHEX || *fourcc == FOURCC_SHDR)
|
|
return DXBCBytecode::Program::GetOutputTopology(chunkContents, *chunkSize);
|
|
}
|
|
|
|
return D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
|
}
|
|
|
|
const rdcstr &DXBCContainer::GetDisassembly()
|
|
{
|
|
if(m_Disassembly.empty())
|
|
{
|
|
rdcstr globalFlagsString;
|
|
|
|
const rdcstr commentString = m_DXBCByteCode ? "//" : ";";
|
|
|
|
if(m_GlobalFlags != GlobalShaderFlags::None)
|
|
{
|
|
globalFlagsString += commentString + " Note: shader requires additional functionality:\n";
|
|
|
|
if(m_GlobalFlags & GlobalShaderFlags::DoublePrecision)
|
|
globalFlagsString += commentString + " Double-precision floating point\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::RawStructured)
|
|
globalFlagsString += commentString + " Raw and Structured buffers\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::UAVsEveryStage)
|
|
globalFlagsString += commentString + " UAVs at every shader stage\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::UAVCount64)
|
|
globalFlagsString += commentString + " 64 UAV slots\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::MinPrecision)
|
|
globalFlagsString += commentString + " Minimum-precision data types\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::DoubleExtensions11_1)
|
|
globalFlagsString += commentString + " Double-precision extensions for 11.1\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::ShaderExtensions11_1)
|
|
globalFlagsString += commentString + " Shader extensions for 11.1\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::ComparisonFilter)
|
|
globalFlagsString += commentString + " Comparison filtering for feature level 9\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::TiledResources)
|
|
globalFlagsString += commentString + " Tiled resources\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::PSOutStencilref)
|
|
globalFlagsString += commentString + " PS Output Stencil Ref\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::PSInnerCoverage)
|
|
globalFlagsString += commentString + " PS Inner Coverage\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::TypedUAVAdditional)
|
|
globalFlagsString += commentString + " Typed UAV Load Additional Formats\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::RasterOrderViews)
|
|
globalFlagsString += commentString + " Raster Ordered UAVs\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::ArrayIndexFromVert)
|
|
globalFlagsString += commentString +
|
|
" SV_RenderTargetArrayIndex or SV_ViewportArrayIndex from any "
|
|
"shader feeding rasterizer\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::WaveOps)
|
|
globalFlagsString += commentString + " Wave level operations\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::Int64)
|
|
globalFlagsString += commentString + " 64-Bit integer\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::ViewInstancing)
|
|
globalFlagsString += commentString + " View Instancing\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::Barycentrics)
|
|
globalFlagsString += commentString + " Barycentrics\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::NativeLowPrecision)
|
|
globalFlagsString += commentString + " Use native low precision\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::ShadingRate)
|
|
globalFlagsString += commentString + " Shading Rate\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::Raytracing1_1)
|
|
globalFlagsString += commentString + " Raytracing tier 1.1 features\n";
|
|
if(m_GlobalFlags & GlobalShaderFlags::SamplerFeedback)
|
|
globalFlagsString += commentString + " Sampler feedback\n";
|
|
globalFlagsString += commentString + "\n";
|
|
}
|
|
|
|
if(m_DXBCByteCode)
|
|
{
|
|
m_Disassembly = StringFormat::Fmt("Shader hash %08x-%08x-%08x-%08x\n\n", m_Hash[0], m_Hash[1],
|
|
m_Hash[2], m_Hash[3]);
|
|
|
|
if(m_GlobalFlags != GlobalShaderFlags::None)
|
|
m_Disassembly += globalFlagsString;
|
|
|
|
if(!m_DebugFileName.empty())
|
|
m_Disassembly += StringFormat::Fmt("// Debug name: %s\n", m_DebugFileName.c_str());
|
|
|
|
if(m_ShaderExt.second != ~0U)
|
|
m_Disassembly += "// Vendor shader extensions in use\n";
|
|
|
|
m_Disassembly += m_DXBCByteCode->GetDisassembly();
|
|
}
|
|
else if(m_DXILByteCode)
|
|
{
|
|
m_Disassembly.clear();
|
|
|
|
#if DISABLED(DXC_COMPATIBLE_DISASM)
|
|
if(m_GlobalFlags != GlobalShaderFlags::None)
|
|
m_Disassembly += globalFlagsString;
|
|
|
|
if(!m_DebugFileName.empty())
|
|
m_Disassembly += StringFormat::Fmt("; shader debug name: %s\n", m_DebugFileName.c_str());
|
|
|
|
if(m_ShaderExt.second != ~0U)
|
|
m_Disassembly += "; Vendor shader extensions in use\n";
|
|
|
|
m_Disassembly += "; shader hash: ";
|
|
byte *hashBytes = (byte *)m_Hash;
|
|
for(size_t i = 0; i < sizeof(m_Hash); i++)
|
|
m_Disassembly += StringFormat::Fmt("%02x", hashBytes[i]);
|
|
m_Disassembly += "\n\n";
|
|
#endif
|
|
|
|
m_Disassembly += m_DXILByteCode->GetDisassembly();
|
|
}
|
|
}
|
|
|
|
return m_Disassembly;
|
|
}
|
|
|
|
void DXBCContainer::FillTraceLineInfo(ShaderDebugTrace &trace) const
|
|
{
|
|
if(m_DXBCByteCode)
|
|
{
|
|
trace.instInfo.resize(m_DXBCByteCode->GetNumInstructions());
|
|
for(size_t i = 0; i < m_DXBCByteCode->GetNumInstructions(); i++)
|
|
{
|
|
const DXBCBytecode::Operation &op = m_DXBCByteCode->GetInstruction(i);
|
|
|
|
trace.instInfo[i].instruction = (uint32_t)i;
|
|
|
|
if(m_DebugInfo)
|
|
m_DebugInfo->GetLineInfo(i, op.offset, trace.instInfo[i].lineInfo);
|
|
|
|
// we add some number of lines for the header we added with shader hash, debug name, etc on
|
|
// top of what the bytecode disassembler did
|
|
|
|
// 2 minimum for the shader hash we always print
|
|
uint32_t extraLines = 2;
|
|
if(!m_DebugFileName.empty())
|
|
extraLines++;
|
|
if(m_ShaderExt.second != ~0U)
|
|
extraLines++;
|
|
|
|
if(m_GlobalFlags != GlobalShaderFlags::None)
|
|
extraLines += (uint32_t)Bits::CountOnes((uint32_t)m_GlobalFlags) + 2;
|
|
|
|
if(op.line > 0)
|
|
trace.instInfo[i].lineInfo.disassemblyLine = extraLines + op.line;
|
|
|
|
if(m_DebugInfo)
|
|
m_DebugInfo->GetLocals(this, i, op.offset, trace.instInfo[i].sourceVars);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DXBCContainer::StripChunk(bytebuf &ByteCode, uint32_t fourcc)
|
|
{
|
|
FileHeader *header = (FileHeader *)ByteCode.data();
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCode.size())
|
|
return;
|
|
|
|
uint32_t *chunkOffsets =
|
|
(uint32_t *)(ByteCode.data() + sizeof(FileHeader)); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t offs = chunkOffsets[chunkIdx];
|
|
|
|
uint32_t *chunkFourcc = (uint32_t *)(ByteCode.data() + offs);
|
|
uint32_t *chunkSize = (uint32_t *)(chunkFourcc + 1);
|
|
|
|
if(*chunkFourcc == fourcc)
|
|
{
|
|
// the size of the whole chunk that we're erasing is the chunk's size itself, plus 8 bytes for
|
|
// fourcc+size
|
|
uint32_t size = 8 + *chunkSize;
|
|
for(uint32_t c = chunkIdx; c < header->numChunks; c++)
|
|
chunkOffsets[c] = chunkOffsets[c + 1] - size;
|
|
|
|
header->numChunks--;
|
|
header->fileLength -= size;
|
|
|
|
// all chunk offsets (before and after) and file size decrement by a uint32, because we're
|
|
// going to remove a chunkoffset as well which is before them all
|
|
for(uint32_t c = 0; c < header->numChunks; c++)
|
|
chunkOffsets[c] -= sizeof(uint32_t);
|
|
header->fileLength -= sizeof(uint32_t);
|
|
|
|
// erase the chunk itself
|
|
ByteCode.erase(offs, size);
|
|
// remove the chunk offset
|
|
ByteCode.erase(sizeof(FileHeader) + header->numChunks * sizeof(uint32_t), 4);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
HashContainer(ByteCode.data(), ByteCode.size());
|
|
}
|
|
|
|
void DXBCContainer::ReplaceChunk(bytebuf &ByteCode, uint32_t fourcc, const byte *replacement,
|
|
size_t size)
|
|
{
|
|
FileHeader *header = (FileHeader *)ByteCode.data();
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCode.size())
|
|
return;
|
|
|
|
uint32_t *chunkOffsets =
|
|
(uint32_t *)(ByteCode.data() + sizeof(FileHeader)); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t offs = chunkOffsets[chunkIdx];
|
|
|
|
uint32_t *chunkFourcc = (uint32_t *)(ByteCode.data() + offs);
|
|
uint32_t *chunkSize = (uint32_t *)(chunkFourcc + 1);
|
|
|
|
if(*chunkFourcc == fourcc)
|
|
{
|
|
int32_t diff = int32_t(size) - int32_t(*chunkSize);
|
|
|
|
*chunkSize = (uint32_t)size;
|
|
|
|
if(diff == 0)
|
|
{
|
|
memcpy(ByteCode.data() + offs + 8, replacement, size);
|
|
}
|
|
else if(diff > 0)
|
|
{
|
|
const byte *replaceBytes = (const byte *)replacement;
|
|
ByteCode.insert(offs + 8, replaceBytes, diff);
|
|
memcpy(ByteCode.data() + offs + 8 + diff, replaceBytes + diff, size - diff);
|
|
}
|
|
else if(diff < 0)
|
|
{
|
|
ByteCode.erase(offs + 8, -diff);
|
|
memcpy(ByteCode.data() + offs + 8, replacement, size);
|
|
}
|
|
|
|
// fixup offsets of chunks after this point
|
|
header = (FileHeader *)ByteCode.data();
|
|
chunkOffsets = (uint32_t *)(ByteCode.data() + sizeof(FileHeader));
|
|
|
|
header->fileLength += diff;
|
|
|
|
chunkIdx++;
|
|
|
|
for(; chunkIdx < header->numChunks; chunkIdx++)
|
|
chunkOffsets[chunkIdx] += diff;
|
|
|
|
HashContainer(ByteCode.data(), ByteCode.size());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const byte *DXBCContainer::FindChunk(const bytebuf &ByteCode, uint32_t fourcc, size_t &size)
|
|
{
|
|
const FileHeader *header = (const FileHeader *)ByteCode.data();
|
|
|
|
size = 0;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return NULL;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCode.size())
|
|
return NULL;
|
|
|
|
const uint32_t *chunkOffsets =
|
|
(const uint32_t *)(ByteCode.data() + sizeof(FileHeader)); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t offs = chunkOffsets[chunkIdx];
|
|
|
|
const uint32_t *chunkFourcc = (uint32_t *)(ByteCode.data() + offs);
|
|
const uint32_t *chunkSize = (uint32_t *)(chunkFourcc + 1);
|
|
|
|
if(*chunkFourcc == fourcc)
|
|
{
|
|
size = *chunkSize;
|
|
return ByteCode.data() + offs + 8;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void DXBCContainer::GetHash(uint32_t hash[4], const void *ByteCode, size_t BytecodeLength)
|
|
{
|
|
if(BytecodeLength < sizeof(FileHeader) || ByteCode == NULL)
|
|
{
|
|
memset(hash, 0, sizeof(uint32_t) * 4);
|
|
return;
|
|
}
|
|
|
|
const byte *data = (byte *)ByteCode; // just for convenience
|
|
|
|
FileHeader *header = (FileHeader *)ByteCode;
|
|
|
|
memset(hash, 0, sizeof(uint32_t) * 4);
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return;
|
|
|
|
if(header->fileLength != (uint32_t)BytecodeLength)
|
|
return;
|
|
|
|
memcpy(hash, header->hashValue, sizeof(header->hashValue));
|
|
|
|
uint32_t *chunkOffsets = (uint32_t *)(header + 1); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
uint32_t *chunkSize = (uint32_t *)(fourcc + 1);
|
|
|
|
char *chunkContents = (char *)(chunkSize + 1);
|
|
|
|
if(*fourcc == FOURCC_HASH)
|
|
{
|
|
HASHHeader *hashHeader = (HASHHeader *)chunkContents;
|
|
|
|
memcpy(hash, hashHeader->hashValue, sizeof(hashHeader->hashValue));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DXBCContainer::IsHashedContainer(const void *ByteCode, size_t BytecodeLength)
|
|
{
|
|
if(BytecodeLength < sizeof(FileHeader) || ByteCode == NULL)
|
|
return false;
|
|
|
|
const FileHeader *header = (const FileHeader *)ByteCode;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return false;
|
|
|
|
if(header->fileLength != (uint32_t)BytecodeLength)
|
|
return false;
|
|
|
|
if(header->hashValue[0] != 0 || header->hashValue[1] != 0 || header->hashValue[2] != 0 ||
|
|
header->hashValue[3] != 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DXBCContainer::HashContainer(void *ByteCode, size_t BytecodeLength)
|
|
{
|
|
if(BytecodeLength < sizeof(FileHeader) || ByteCode == NULL)
|
|
return false;
|
|
|
|
FileHeader *header = (FileHeader *)ByteCode;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return false;
|
|
|
|
if(header->fileLength != (uint32_t)BytecodeLength)
|
|
return false;
|
|
|
|
MD5_CTX md5ctx = {};
|
|
MD5_Init(&md5ctx);
|
|
|
|
// the hashable data starts immediately after the hash.
|
|
byte *data = (byte *)&header->containerVersion;
|
|
uint32_t length = uint32_t(BytecodeLength - offsetof(FileHeader, containerVersion));
|
|
|
|
// we need to know the number of bits for putting in the trailing padding.
|
|
uint32_t numBits = length * 8;
|
|
uint32_t numBitsPart2 = (numBits >> 2) | 1;
|
|
|
|
// MD5 works on 64-byte chunks, process the first set of whole chunks, leaving 0-63 bytes left
|
|
// over
|
|
uint32_t leftoverLength = length % 64;
|
|
MD5_Update(&md5ctx, data, length - leftoverLength);
|
|
|
|
data += length - leftoverLength;
|
|
|
|
uint32_t block[16] = {};
|
|
RDCCOMPILE_ASSERT(sizeof(block) == 64, "Block is not properly sized for MD5 round");
|
|
|
|
// normally MD5 finishes by appending a 1 bit to the bitstring. Since we are only appending bytes
|
|
// this would be an 0x80 byte (the first bit is considered to be the MSB). Then it pads out with
|
|
// zeroes until it has 56 bytes in the last block and appends appends the message length as a
|
|
// 64-bit integer as the final part of that block.
|
|
// in other words, normally whatever is leftover from the actual message gets one byte appended,
|
|
// then if there's at least 8 bytes left we'll append the length. Otherwise we pad that block with
|
|
// 0s and create a new block with the length at the end.
|
|
// Or as the original RFC/spec says: padding is always performed regardless of whether the
|
|
// original buffer already ended in exactly a 56 byte block.
|
|
//
|
|
// The DXBC finalisation is slightly different (previous work suggests this is due to a bug in the
|
|
// original implementation and it was maybe intended to be exactly MD5?):
|
|
//
|
|
// The length provided in the padding block is not 64-bit properly: the second dword with the high
|
|
// bits is instead the number of nybbles(?) with 1 OR'd on. The length is also split, so if it's
|
|
// in
|
|
// a padding block the low bits are in the first dword and the upper bits in the last. If there's
|
|
// no padding block the low dword is passed in first before the leftovers of the message and then
|
|
// the upper bits at the end.
|
|
|
|
// if the leftovers uses at least 56, we can't fit both the trailing 1 and the 64-bit length, so
|
|
// we need a padding block and then our own block for the length.
|
|
if(leftoverLength >= 56)
|
|
{
|
|
// pass in the leftover data padded out to 64 bytes with zeroes
|
|
MD5_Update(&md5ctx, data, leftoverLength);
|
|
|
|
block[0] = 0x80; // first padding bit is 1
|
|
MD5_Update(&md5ctx, block, 64 - leftoverLength);
|
|
|
|
// the final block contains the number of bits in the first dword, and the weird upper bits
|
|
block[0] = numBits;
|
|
block[15] = numBitsPart2;
|
|
|
|
// process this block directly, we're replacing the call to MD5_Final here manually
|
|
MD5_Update(&md5ctx, block, 64);
|
|
}
|
|
else
|
|
{
|
|
// the leftovers mean we can put the padding inside the final block. But first we pass the "low"
|
|
// number of bits:
|
|
MD5_Update(&md5ctx, &numBits, sizeof(numBits));
|
|
|
|
if(leftoverLength)
|
|
MD5_Update(&md5ctx, data, leftoverLength);
|
|
|
|
uint32_t paddingBytes = 64 - leftoverLength - 4;
|
|
|
|
// prepare the remainder of this block, starting with the 0x80 padding start right after the
|
|
// leftovers and the first part of the bit length above.
|
|
block[0] = 0x80;
|
|
// then add the remainder of the 'length' here in the final part of the block
|
|
memcpy(((byte *)block) + paddingBytes - 4, &numBitsPart2, 4);
|
|
|
|
MD5_Update(&md5ctx, block, paddingBytes);
|
|
}
|
|
|
|
header->hashValue[0] = md5ctx.a;
|
|
header->hashValue[1] = md5ctx.b;
|
|
header->hashValue[2] = md5ctx.c;
|
|
header->hashValue[3] = md5ctx.d;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DXBCContainer::UsesExtensionUAV(uint32_t slot, uint32_t space, const void *ByteCode,
|
|
size_t BytecodeLength)
|
|
{
|
|
if(slot == ~0U && space == ~0U)
|
|
return false;
|
|
|
|
const FileHeader *header = (const FileHeader *)ByteCode;
|
|
|
|
const byte *data = (const byte *)ByteCode; // just for convenience
|
|
|
|
if(ByteCode == NULL || BytecodeLength == 0)
|
|
return false;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return false;
|
|
|
|
if(header->fileLength != (uint32_t)BytecodeLength)
|
|
return false;
|
|
|
|
const uint32_t *chunkOffsets = (const uint32_t *)(header + 1); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
const uint32_t *fourcc = (const uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
const uint32_t *chunkSize = (const uint32_t *)(fourcc + 1);
|
|
|
|
const byte *chunkContents = (const byte *)(chunkSize + 1);
|
|
|
|
if(*fourcc == FOURCC_SHEX || *fourcc == FOURCC_SHDR)
|
|
return DXBCBytecode::Program::UsesExtensionUAV(slot, space, chunkContents, *chunkSize);
|
|
|
|
// far too expensive to figure out if a DXIL blob references the shader UAV. Just assume it does
|
|
// - this is only as an opportunistic thing to avoid requiring vendor extensions on programs
|
|
// that initialise but don't use them. If a user is bothering with DXIL they deserve what they
|
|
// get.
|
|
if(*fourcc == FOURCC_DXIL || *fourcc == FOURCC_ILDB)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DXBCContainer::CheckForDebugInfo(const void *ByteCode, size_t ByteCodeLength)
|
|
{
|
|
FileHeader *header = (FileHeader *)ByteCode;
|
|
|
|
char *data = (char *)ByteCode; // just for convenience
|
|
|
|
if(ByteCode == NULL || ByteCodeLength == 0)
|
|
return false;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return false;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCodeLength)
|
|
return false;
|
|
|
|
uint32_t *chunkOffsets = (uint32_t *)(header + 1); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
|
|
if(*fourcc == FOURCC_SDBG || *fourcc == FOURCC_SPDB || *fourcc == FOURCC_ILDB)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DXBCContainer::CheckForDXIL(const void *ByteCode, size_t ByteCodeLength)
|
|
{
|
|
FileHeader *header = (FileHeader *)ByteCode;
|
|
|
|
char *data = (char *)ByteCode; // just for convenience
|
|
|
|
if(ByteCode == NULL || ByteCodeLength == 0)
|
|
return false;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return false;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCodeLength)
|
|
return false;
|
|
|
|
uint32_t *chunkOffsets = (uint32_t *)(header + 1); // right after the header
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
|
|
if(*fourcc == FOURCC_ILDB || *fourcc == FOURCC_DXIL)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
rdcstr DXBCContainer::GetDebugBinaryPath(const void *ByteCode, size_t ByteCodeLength)
|
|
{
|
|
rdcstr debugPath;
|
|
FileHeader *header = (FileHeader *)ByteCode;
|
|
|
|
char *data = (char *)ByteCode; // just for convenience
|
|
|
|
if(ByteCode == NULL || ByteCodeLength == 0)
|
|
return debugPath;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return debugPath;
|
|
|
|
if(header->fileLength != (uint32_t)ByteCodeLength)
|
|
return debugPath;
|
|
|
|
uint32_t *chunkOffsets = (uint32_t *)(header + 1); // right after the header
|
|
|
|
// prefer RenderDoc's magic value which pre-dated D3D's support
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
|
|
if(*fourcc == FOURCC_PRIV)
|
|
{
|
|
PRIVHeader *privHeader = (PRIVHeader *)fourcc;
|
|
if(privHeader->debugInfoGUID == PRIVHeader::RENDERDOC_ShaderDebugMagicValue)
|
|
{
|
|
const char *pathData = (char *)&privHeader->data;
|
|
size_t pathLength = strnlen(pathData, privHeader->chunkLength);
|
|
|
|
if(privHeader->chunkLength == (sizeof(GUID) + pathLength + 1))
|
|
{
|
|
debugPath.append(pathData, pathLength);
|
|
return debugPath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
if(*fourcc == FOURCC_ILDN)
|
|
{
|
|
const ILDNHeader *h = (const ILDNHeader *)(fourcc + 2);
|
|
|
|
debugPath.append(h->Name, h->NameLength);
|
|
return debugPath;
|
|
}
|
|
}
|
|
|
|
return debugPath;
|
|
}
|
|
|
|
void DXBCContainer::TryFetchSeparateDebugInfo(bytebuf &byteCode, const rdcstr &debugInfoPath)
|
|
{
|
|
if(!CheckForDebugInfo((const void *)&byteCode[0], byteCode.size()))
|
|
{
|
|
rdcstr originalPath = debugInfoPath;
|
|
|
|
if(originalPath.empty())
|
|
originalPath = GetDebugBinaryPath((const void *)&byteCode[0], byteCode.size());
|
|
|
|
if(!originalPath.empty())
|
|
{
|
|
bool lz4 = false;
|
|
|
|
if(!strncmp(originalPath.c_str(), "lz4#", 4))
|
|
{
|
|
originalPath = originalPath.substr(4);
|
|
lz4 = true;
|
|
}
|
|
// could support more if we're willing to compile in the decompressor
|
|
|
|
FILE *originalShaderFile = NULL;
|
|
|
|
const rdcarray<rdcstr> &searchPaths = DXBC_Debug_SearchDirPaths();
|
|
|
|
size_t numSearchPaths = searchPaths.size();
|
|
|
|
rdcstr foundPath;
|
|
|
|
// keep searching until we've exhausted all possible path options, or we've found a file that
|
|
// opens
|
|
while(originalShaderFile == NULL && !originalPath.empty())
|
|
{
|
|
// while we haven't found a file, keep trying through the search paths. For i==0
|
|
// check the path on its own, in case it's an absolute path.
|
|
for(size_t i = 0; originalShaderFile == NULL && i <= numSearchPaths; i++)
|
|
{
|
|
if(i == 0)
|
|
{
|
|
originalShaderFile = FileIO::fopen(originalPath, FileIO::ReadBinary);
|
|
foundPath = originalPath;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const rdcstr &searchPath = searchPaths[i - 1];
|
|
foundPath = searchPath + "/" + originalPath;
|
|
originalShaderFile = FileIO::fopen(foundPath, FileIO::ReadBinary);
|
|
}
|
|
}
|
|
|
|
if(originalShaderFile == NULL)
|
|
{
|
|
// the "documented" behaviour for D3D debug info names is that when presented with a
|
|
// relative path containing subfolders like foo/bar/blah.pdb then we should first try to
|
|
// append it to all search paths as-is, then strip off the top-level subdirectory to get
|
|
// bar/blah.pdb and try that in all search directories, and keep going. So if we got here
|
|
// and didn't open a file, try to strip off the the top directory and continue.
|
|
int32_t offs = originalPath.find_first_of("\\/");
|
|
|
|
// if we couldn't find a directory separator there's nothing to do, stop looking
|
|
if(offs == -1)
|
|
break;
|
|
|
|
// otherwise strip up to there and keep going
|
|
originalPath.erase(0, offs + 1);
|
|
}
|
|
}
|
|
|
|
if(originalShaderFile == NULL)
|
|
return;
|
|
|
|
FileIO::fseek64(originalShaderFile, 0L, SEEK_END);
|
|
uint64_t originalShaderSize = FileIO::ftell64(originalShaderFile);
|
|
FileIO::fseek64(originalShaderFile, 0, SEEK_SET);
|
|
|
|
if(lz4 || originalShaderSize >= byteCode.size())
|
|
{
|
|
bytebuf debugBytecode;
|
|
|
|
debugBytecode.resize((size_t)originalShaderSize);
|
|
FileIO::fread(&debugBytecode[0], sizeof(byte), (size_t)originalShaderSize,
|
|
originalShaderFile);
|
|
|
|
if(lz4)
|
|
{
|
|
rdcarray<byte> decompressed;
|
|
|
|
// first try decompressing to 1MB flat
|
|
decompressed.resize(100 * 1024);
|
|
|
|
int ret = LZ4_decompress_safe((const char *)&debugBytecode[0], (char *)&decompressed[0],
|
|
(int)debugBytecode.size(), (int)decompressed.size());
|
|
|
|
if(ret < 0)
|
|
{
|
|
// if it failed, either source is corrupt or we didn't allocate enough space.
|
|
// Just allocate 255x compressed size since it can't need any more than that.
|
|
decompressed.resize(255 * debugBytecode.size());
|
|
|
|
ret = LZ4_decompress_safe((const char *)&debugBytecode[0], (char *)&decompressed[0],
|
|
(int)debugBytecode.size(), (int)decompressed.size());
|
|
|
|
if(ret < 0)
|
|
{
|
|
RDCERR("Failed to decompress LZ4 data from %s", foundPath.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
RDCASSERT(ret > 0, ret);
|
|
|
|
// we resize and memcpy instead of just doing .swap() because that would
|
|
// transfer over the over-large pessimistic capacity needed for decompression
|
|
debugBytecode.resize(ret);
|
|
memcpy(&debugBytecode[0], &decompressed[0], debugBytecode.size());
|
|
}
|
|
|
|
if(IsPDBFile(&debugBytecode[0], debugBytecode.size()))
|
|
{
|
|
UnwrapEmbeddedPDBData(debugBytecode);
|
|
m_DebugShaderBlob = debugBytecode;
|
|
}
|
|
else if(CheckForDebugInfo((const void *)&debugBytecode[0], debugBytecode.size()))
|
|
{
|
|
byteCode.swap(debugBytecode);
|
|
}
|
|
}
|
|
|
|
FileIO::fclose(originalShaderFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPath, GraphicsAPI api,
|
|
uint32_t shaderExtReg, uint32_t shaderExtSpace)
|
|
{
|
|
RDCEraseEl(m_ShaderStats);
|
|
|
|
m_ShaderBlob = ByteCode;
|
|
|
|
TryFetchSeparateDebugInfo(m_ShaderBlob, debugInfoPath);
|
|
|
|
// just for convenience
|
|
byte *data = (byte *)m_ShaderBlob.data();
|
|
byte *debugData = (byte *)m_DebugShaderBlob.data();
|
|
|
|
FileHeader *header = (FileHeader *)data;
|
|
FileHeader *debugHeader = (FileHeader *)debugData;
|
|
|
|
if(header->fourcc != FOURCC_DXBC)
|
|
return;
|
|
|
|
if(header->fileLength != (uint32_t)m_ShaderBlob.size())
|
|
return;
|
|
|
|
if(debugHeader && debugHeader->fourcc != FOURCC_DXBC)
|
|
debugHeader = NULL;
|
|
|
|
if(debugHeader && debugHeader->fileLength != m_DebugShaderBlob.size())
|
|
debugHeader = NULL;
|
|
|
|
memcpy(m_Hash, header->hashValue, sizeof(m_Hash));
|
|
|
|
// default to vertex shader to support blobs without RDEF chunks (e.g. used with
|
|
// input layouts if they're super stripped down)
|
|
m_Type = DXBC::ShaderType::Vertex;
|
|
|
|
uint32_t *chunkOffsets = (uint32_t *)(header + 1); // right after the header
|
|
uint32_t *debugChunkOffsets = debugHeader ? (uint32_t *)(debugHeader + 1) : NULL;
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
const uint32_t *fourcc = (const uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
const uint32_t *chunkSize = (const uint32_t *)(fourcc + 1);
|
|
|
|
const byte *chunkContents = (const byte *)(chunkSize + 1);
|
|
|
|
if(*fourcc == FOURCC_RDEF)
|
|
{
|
|
if(*chunkSize < offsetof(RDEFHeader, unknown))
|
|
{
|
|
RDCERR("Invalid RDEF chunk encountered: size %u", *chunkSize);
|
|
continue;
|
|
}
|
|
|
|
const RDEFHeader *h = (const RDEFHeader *)chunkContents;
|
|
|
|
// for target version 0x500, unknown[0] is FOURCC_RD11.
|
|
// for 0x501 it's "\x13\x13\D%"
|
|
|
|
m_Reflection = new Reflection;
|
|
|
|
if(h->targetShaderStage == 0xffff)
|
|
m_Type = DXBC::ShaderType::Pixel;
|
|
else if(h->targetShaderStage == 0xfffe)
|
|
m_Type = DXBC::ShaderType::Vertex;
|
|
|
|
else if(h->targetShaderStage == 0x4753) // 'GS'
|
|
m_Type = DXBC::ShaderType::Geometry;
|
|
|
|
else if(h->targetShaderStage == 0x4853) // 'HS'
|
|
m_Type = DXBC::ShaderType::Hull;
|
|
else if(h->targetShaderStage == 0x4453) // 'DS'
|
|
m_Type = DXBC::ShaderType::Domain;
|
|
else if(h->targetShaderStage == 0x4353) // 'CS'
|
|
m_Type = DXBC::ShaderType::Compute;
|
|
|
|
m_Reflection->SRVs.reserve(h->resources.count);
|
|
m_Reflection->UAVs.reserve(h->resources.count);
|
|
m_Reflection->Samplers.reserve(h->resources.count);
|
|
|
|
struct CBufferBind
|
|
{
|
|
uint32_t reg, space, bindCount, identifier;
|
|
};
|
|
|
|
std::map<rdcstr, CBufferBind> cbufferbinds;
|
|
|
|
uint32_t resourceStride = sizeof(RDEFResource);
|
|
|
|
// versions before 5.1 don't have the space and ID
|
|
if(h->targetVersion < 0x501)
|
|
{
|
|
resourceStride -= sizeof(RDEFResource) - offsetof(RDEFResource, space);
|
|
}
|
|
|
|
for(int32_t i = 0; i < h->resources.count; i++)
|
|
{
|
|
RDEFResource *res =
|
|
(RDEFResource *)(chunkContents + h->resources.offset + i * resourceStride);
|
|
|
|
ShaderInputBind desc;
|
|
|
|
desc.name = (const char *)(chunkContents + res->nameOffset);
|
|
desc.type = (ShaderInputBind::InputType)res->type;
|
|
desc.space = h->targetVersion >= 0x501 ? res->space : 0;
|
|
desc.reg = res->bindPoint;
|
|
desc.bindCount = res->bindCount;
|
|
desc.retType = (DXBC::ResourceRetType)res->retType;
|
|
desc.dimension = (ShaderInputBind::Dimension)res->dimension;
|
|
|
|
// Bindless resources report a bind count of 0 from the shader bytecode, but many other
|
|
// places in this codebase assume ~0U means bindless. Patch it up now.
|
|
if(h->targetVersion >= 0x501 && desc.bindCount == 0)
|
|
desc.bindCount = ~0U;
|
|
|
|
// component count seem to be in these lower bits of flags.
|
|
desc.numComps = 1 + ((res->flags & 0xC) >> 2);
|
|
|
|
// for cbuffers the names can be duplicated, so handle this by assuming
|
|
// the order will match between binding declaration and cbuffer declaration
|
|
// and append _s onto each subsequent buffer name
|
|
if(desc.IsCBuffer())
|
|
{
|
|
rdcstr cname = desc.name;
|
|
|
|
while(cbufferbinds.find(cname) != cbufferbinds.end())
|
|
cname += "_";
|
|
|
|
CBufferBind cb;
|
|
cb.space = desc.space;
|
|
cb.reg = desc.reg;
|
|
cb.bindCount = desc.bindCount;
|
|
cb.identifier = h->targetVersion >= 0x501 ? res->ID : desc.reg;
|
|
cbufferbinds[cname] = cb;
|
|
}
|
|
else if(desc.IsSampler())
|
|
{
|
|
m_Reflection->Samplers.push_back(desc);
|
|
}
|
|
else if(desc.IsSRV())
|
|
{
|
|
m_Reflection->SRVs.push_back(desc);
|
|
}
|
|
else if(desc.IsUAV())
|
|
{
|
|
m_Reflection->UAVs.push_back(desc);
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected type of resource: %u", desc.type);
|
|
}
|
|
}
|
|
|
|
// Expand out any array resources. We deliberately place these at the end of the resources
|
|
// array, so that any non-array resources can be picked up first before any arrays.
|
|
//
|
|
// The reason for this is that an array element could refer to an un-used alias in a bind
|
|
// point, and an individual non-array resoruce will always refer to the used alias (an
|
|
// un-used individual resource will be omitted entirely from the reflection
|
|
//
|
|
// Note we preserve the arrays in SM5.1
|
|
if(h->targetVersion < 0x501)
|
|
{
|
|
for(rdcarray<ShaderInputBind> *arr :
|
|
{&m_Reflection->SRVs, &m_Reflection->UAVs, &m_Reflection->Samplers})
|
|
{
|
|
rdcarray<ShaderInputBind> &resArray = *arr;
|
|
for(size_t i = 0; i < resArray.size();)
|
|
{
|
|
if(resArray[i].bindCount > 1)
|
|
{
|
|
// remove the item from the array at this location
|
|
ShaderInputBind desc = resArray.takeAt(i);
|
|
|
|
rdcstr rname = desc.name;
|
|
uint32_t arraySize = desc.bindCount;
|
|
|
|
desc.bindCount = 1;
|
|
|
|
for(uint32_t a = 0; a < arraySize; a++)
|
|
{
|
|
desc.name = StringFormat::Fmt("%s[%u]", rname.c_str(), a);
|
|
resArray.push_back(desc);
|
|
desc.reg++;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// just move on if this item wasn't arrayed
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::set<rdcstr> cbuffernames;
|
|
|
|
for(int32_t i = 0; i < h->cbuffers.count; i++)
|
|
{
|
|
RDEFCBuffer *cbuf =
|
|
(RDEFCBuffer *)(chunkContents + h->cbuffers.offset + i * sizeof(RDEFCBuffer));
|
|
|
|
CBuffer cb;
|
|
|
|
// I have no real justification for this, it seems some cbuffers are included that are
|
|
// empty and have nameOffset = 0, fxc seems to skip them so I'll do the same.
|
|
// See github issue #122
|
|
if(cbuf->nameOffset == 0)
|
|
continue;
|
|
|
|
cb.name = (const char *)(chunkContents + cbuf->nameOffset);
|
|
|
|
cb.descriptor.byteSize = cbuf->size;
|
|
cb.descriptor.type = (CBuffer::Descriptor::Type)cbuf->type;
|
|
|
|
cb.variables.reserve(cbuf->variables.count);
|
|
|
|
size_t varStride = sizeof(RDEFCBufferVariable);
|
|
|
|
if(h->targetVersion < 0x500)
|
|
{
|
|
size_t extraData = sizeof(RDEFCBufferVariable) - offsetof(RDEFCBufferVariable, unknown);
|
|
|
|
varStride -= extraData;
|
|
|
|
// it seems in rare circumstances, this data is present even for targetVersion < 0x500.
|
|
// use a heuristic to check if the lower stride would cause invalid-looking data
|
|
// for variables. See github issue #122
|
|
if(cbuf->variables.count > 1)
|
|
{
|
|
RDEFCBufferVariable *var =
|
|
(RDEFCBufferVariable *)(chunkContents + cbuf->variables.offset + varStride);
|
|
|
|
if(var->nameOffset > m_ShaderBlob.size())
|
|
{
|
|
varStride += extraData;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int32_t vi = 0; vi < cbuf->variables.count; vi++)
|
|
{
|
|
RDEFCBufferVariable *var =
|
|
(RDEFCBufferVariable *)(chunkContents + cbuf->variables.offset + vi * varStride);
|
|
|
|
RDCASSERT(var->nameOffset < m_ShaderBlob.size());
|
|
|
|
CBufferVariable v;
|
|
|
|
v.name = (const char *)(chunkContents + var->nameOffset);
|
|
|
|
// var->size; // size with cbuffer padding
|
|
v.offset = var->startOffset;
|
|
|
|
v.type = ParseRDEFType(h, chunkContents, var->typeOffset);
|
|
|
|
cb.variables.push_back(v);
|
|
}
|
|
|
|
rdcstr cname = cb.name;
|
|
|
|
while(cbuffernames.find(cname) != cbuffernames.end())
|
|
cname += "_";
|
|
|
|
cbuffernames.insert(cname);
|
|
|
|
cb.identifier = cbufferbinds[cname].identifier;
|
|
cb.space = cbufferbinds[cname].space;
|
|
cb.reg = cbufferbinds[cname].reg;
|
|
cb.bindCount = cbufferbinds[cname].bindCount;
|
|
|
|
if(cb.descriptor.type == CBuffer::Descriptor::TYPE_CBUFFER)
|
|
{
|
|
m_Reflection->CBuffers.push_back(cb);
|
|
}
|
|
else if(cb.descriptor.type == CBuffer::Descriptor::TYPE_RESOURCE_BIND_INFO)
|
|
{
|
|
RDCASSERT(cb.variables.size() == 1 && cb.variables[0].name == "$Element");
|
|
m_Reflection->ResourceBinds[cb.name] = cb.variables[0].type;
|
|
}
|
|
else if(cb.descriptor.type == CBuffer::Descriptor::TYPE_INTERFACE_POINTERS)
|
|
{
|
|
m_Reflection->Interfaces = cb;
|
|
}
|
|
else
|
|
{
|
|
RDCDEBUG("Unused information, buffer %d: %s", cb.descriptor.type,
|
|
(const char *)(chunkContents + cbuf->nameOffset));
|
|
}
|
|
}
|
|
}
|
|
else if(*fourcc == FOURCC_STAT)
|
|
{
|
|
if(DXIL::Program::Valid(chunkContents, *chunkSize))
|
|
{
|
|
RDCEraseEl(m_ShaderStats);
|
|
m_ShaderStats.version = ShaderStatistics::STATS_DX12;
|
|
|
|
// this stats chunk is a whole program, just with the actual function definition removed
|
|
// (and any related debug metadata). We have to handle this later with the bytecode.
|
|
/* DXIL::Program prog(chunkContents, *chunkSize); */
|
|
}
|
|
else if(*chunkSize == STATSizeDX10)
|
|
{
|
|
memcpy(&m_ShaderStats, chunkContents, STATSizeDX10);
|
|
m_ShaderStats.version = ShaderStatistics::STATS_DX10;
|
|
}
|
|
else if(*chunkSize == STATSizeDX11)
|
|
{
|
|
memcpy(&m_ShaderStats, chunkContents, STATSizeDX11);
|
|
m_ShaderStats.version = ShaderStatistics::STATS_DX11;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected Unexpected STAT chunk version");
|
|
}
|
|
}
|
|
else if(*fourcc == FOURCC_SHEX || *fourcc == FOURCC_SHDR)
|
|
{
|
|
m_DXBCByteCode = new DXBCBytecode::Program(chunkContents, *chunkSize);
|
|
}
|
|
else if(*fourcc == FOURCC_SPDB || *fourcc == FOURCC_SDBG)
|
|
{
|
|
// debug info is processed afterwards
|
|
}
|
|
else if(*fourcc == FOURCC_ILDB || *fourcc == FOURCC_DXIL)
|
|
{
|
|
// we avoiding parsing these immediately because you can get both in a dxbc, so we prefer the
|
|
// debug version.
|
|
// we do remember where the non-debug DXIL is though so we can return it for editing (we only
|
|
// edit non-debug DXIL)
|
|
|
|
if(*fourcc == FOURCC_DXIL)
|
|
{
|
|
m_NonDebugDXILByteCodeOffset = chunkContents - data;
|
|
m_NonDebugDXILByteCodeSize = *chunkSize;
|
|
}
|
|
}
|
|
else if(*fourcc == FOURCC_ILDN)
|
|
{
|
|
if(*chunkSize < sizeof(ILDNHeader))
|
|
{
|
|
RDCERR("Invalid ILDN chunk encountered: size %u", *chunkSize);
|
|
continue;
|
|
}
|
|
|
|
const ILDNHeader *h = (const ILDNHeader *)chunkContents;
|
|
|
|
m_DebugFileName = rdcstr(h->Name, h->NameLength);
|
|
}
|
|
else if(*fourcc == FOURCC_HASH)
|
|
{
|
|
if(*chunkSize < sizeof(HASHHeader))
|
|
{
|
|
RDCERR("Invalid HASH chunk encountered: size %u", *chunkSize);
|
|
continue;
|
|
}
|
|
|
|
const HASHHeader *h = (const HASHHeader *)chunkContents;
|
|
|
|
memcpy(m_Hash, h->hashValue, sizeof(h->hashValue));
|
|
}
|
|
else if(*fourcc == FOURCC_SFI0)
|
|
{
|
|
if(*chunkSize < sizeof(GlobalShaderFlags))
|
|
{
|
|
RDCERR("Invalid SFI0 chunk encountered: size %u", *chunkSize);
|
|
continue;
|
|
}
|
|
|
|
m_GlobalFlags = *(const GlobalShaderFlags *)chunkContents;
|
|
}
|
|
else if(*fourcc == FOURCC_RTS0)
|
|
{
|
|
// root signature
|
|
}
|
|
else if(*fourcc == FOURCC_RDAT)
|
|
{
|
|
// runtime data
|
|
}
|
|
else if(*fourcc == FOURCC_PSV0)
|
|
{
|
|
// this chunk contains some information we could use for reflection but it doesn't contain
|
|
// enough, and doesn't have anything else interesting so we skip it
|
|
}
|
|
else if(*fourcc == FOURCC_ISGN || *fourcc == FOURCC_OSGN || *fourcc == FOURCC_ISG1 ||
|
|
*fourcc == FOURCC_OSG1 || *fourcc == FOURCC_OSG5 || *fourcc == FOURCC_PCSG ||
|
|
*fourcc == FOURCC_PSG1)
|
|
{
|
|
// processed later
|
|
}
|
|
else
|
|
{
|
|
RDCWARN("Unknown chunk %c%c%c%c", ((const char *)fourcc)[0], ((const char *)fourcc)[1],
|
|
((const char *)fourcc)[2], ((const char *)fourcc)[3]);
|
|
}
|
|
}
|
|
|
|
if(m_DXBCByteCode == NULL)
|
|
{
|
|
// prefer ILDB if present
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
uint32_t *chunkSize = (uint32_t *)(data + chunkOffsets[chunkIdx] + sizeof(uint32_t));
|
|
|
|
char *chunkContents = (char *)(data + chunkOffsets[chunkIdx] + sizeof(uint32_t) * 2);
|
|
|
|
if(*fourcc == FOURCC_ILDB)
|
|
{
|
|
m_DXILByteCode = new DXIL::Program((const byte *)chunkContents, *chunkSize);
|
|
}
|
|
}
|
|
|
|
// next search the debug file if it exists
|
|
for(uint32_t chunkIdx = 0;
|
|
debugHeader && m_DXILByteCode == NULL && chunkIdx < debugHeader->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx]);
|
|
uint32_t *chunkSize = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t));
|
|
|
|
char *chunkContents = (char *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t) * 2);
|
|
|
|
if(*fourcc == FOURCC_ILDB)
|
|
m_DXILByteCode = new DXIL::Program((const byte *)chunkContents, *chunkSize);
|
|
}
|
|
|
|
// if we didn't find ILDB then we have to get the bytecode from DXIL. However we look for the
|
|
// STAT chunk and if we find it get reflection from there, since it will have better
|
|
// information. What a mess.
|
|
if(m_DXILByteCode == NULL)
|
|
{
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
uint32_t *chunkSize = (uint32_t *)(data + chunkOffsets[chunkIdx] + sizeof(uint32_t));
|
|
|
|
const byte *chunkContents =
|
|
(const byte *)(data + chunkOffsets[chunkIdx] + sizeof(uint32_t) * 2);
|
|
|
|
if(*fourcc == FOURCC_DXIL)
|
|
{
|
|
m_DXILByteCode = new DXIL::Program(chunkContents, *chunkSize);
|
|
}
|
|
else if(*fourcc == FOURCC_STAT)
|
|
{
|
|
if(DXIL::Program::Valid(chunkContents, *chunkSize))
|
|
{
|
|
// unfortunate that we have to parse the whole blob just to get reflection as well as
|
|
// parsing the DXIL bytecode.
|
|
m_Reflection = DXIL::Program(chunkContents, *chunkSize).GetReflection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get type/version that's used regularly and cheap to fetch
|
|
if(m_DXBCByteCode)
|
|
{
|
|
m_Type = m_DXBCByteCode->GetShaderType();
|
|
m_Version.Major = m_DXBCByteCode->GetMajorVersion();
|
|
m_Version.Minor = m_DXBCByteCode->GetMinorVersion();
|
|
|
|
m_DXBCByteCode->SetReflection(m_Reflection);
|
|
}
|
|
else if(m_DXILByteCode)
|
|
{
|
|
m_Type = m_DXILByteCode->GetShaderType();
|
|
m_Version.Major = m_DXILByteCode->GetMajorVersion();
|
|
m_Version.Minor = m_DXILByteCode->GetMinorVersion();
|
|
}
|
|
|
|
// if reflection information was stripped, attempt to reverse engineer basic info from
|
|
// declarations
|
|
if(m_Reflection == NULL)
|
|
{
|
|
// need to disassemble now to guess resources
|
|
if(m_DXBCByteCode)
|
|
m_Reflection = m_DXBCByteCode->GuessReflection();
|
|
else if(m_DXILByteCode)
|
|
m_Reflection = m_DXILByteCode->GetReflection();
|
|
else
|
|
m_Reflection = new Reflection;
|
|
}
|
|
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
uint32_t *chunkSize = (uint32_t *)(fourcc + 1);
|
|
|
|
char *chunkContents = (char *)(fourcc + 2);
|
|
|
|
if(*fourcc == FOURCC_ISGN || *fourcc == FOURCC_OSGN || *fourcc == FOURCC_ISG1 ||
|
|
*fourcc == FOURCC_OSG1 || *fourcc == FOURCC_OSG5 || *fourcc == FOURCC_PCSG ||
|
|
*fourcc == FOURCC_PSG1)
|
|
{
|
|
if(*chunkSize < sizeof(SIGNHeader))
|
|
{
|
|
RDCERR("Invalid SIGN chunk encountered: size %u", *chunkSize);
|
|
continue;
|
|
}
|
|
|
|
SIGNHeader *sign = (SIGNHeader *)chunkContents;
|
|
|
|
rdcarray<SigParameter> *sig = NULL;
|
|
|
|
bool input = false;
|
|
bool output = false;
|
|
bool patchOrPerPrim = false;
|
|
|
|
if(*fourcc == FOURCC_ISGN || *fourcc == FOURCC_ISG1)
|
|
{
|
|
sig = &m_Reflection->InputSig;
|
|
input = true;
|
|
}
|
|
if(*fourcc == FOURCC_OSGN || *fourcc == FOURCC_OSG1 || *fourcc == FOURCC_OSG5)
|
|
{
|
|
sig = &m_Reflection->OutputSig;
|
|
output = true;
|
|
}
|
|
if(*fourcc == FOURCC_PCSG || *fourcc == FOURCC_PSG1)
|
|
{
|
|
sig = &m_Reflection->PatchConstantSig;
|
|
|
|
// for mesh shaders put everything in the output signature
|
|
if(m_Type == DXBC::ShaderType::Mesh)
|
|
sig = &m_Reflection->OutputSig;
|
|
|
|
patchOrPerPrim = true;
|
|
}
|
|
|
|
RDCASSERT(sig && (sig->empty() || m_Type == DXBC::ShaderType::Mesh));
|
|
|
|
SIGNElement *el0 = (SIGNElement *)(sign + 1);
|
|
SIGNElement7 *el7 = (SIGNElement7 *)el0;
|
|
SIGNElement1 *el1 = (SIGNElement1 *)el0;
|
|
|
|
for(uint32_t signIdx = 0; signIdx < sign->numElems; signIdx++)
|
|
{
|
|
SigParameter desc;
|
|
|
|
const SIGNElement *el = el0;
|
|
|
|
if(*fourcc == FOURCC_ISG1 || *fourcc == FOURCC_OSG1 || *fourcc == FOURCC_PSG1)
|
|
{
|
|
desc.stream = el1->stream;
|
|
|
|
// discard el1->precision as we don't use it and don't want to pollute the common API
|
|
// structures
|
|
|
|
el = &el1->elem;
|
|
}
|
|
|
|
if(*fourcc == FOURCC_OSG5)
|
|
{
|
|
desc.stream = el7->stream;
|
|
|
|
el = &el7->elem;
|
|
}
|
|
|
|
SigCompType compType = (SigCompType)el->componentType;
|
|
desc.varType = VarType::Float;
|
|
if(compType == COMPONENT_TYPE_UINT32)
|
|
desc.varType = VarType::UInt;
|
|
else if(compType == COMPONENT_TYPE_SINT32)
|
|
desc.varType = VarType::SInt;
|
|
else if(compType == COMPONENT_TYPE_FLOAT32)
|
|
desc.varType = VarType::Float;
|
|
else if(compType == COMPONENT_TYPE_UINT16)
|
|
desc.varType = VarType::UShort;
|
|
else if(compType == COMPONENT_TYPE_SINT16)
|
|
desc.varType = VarType::SShort;
|
|
else if(compType == COMPONENT_TYPE_FLOAT16)
|
|
desc.varType = VarType::Half;
|
|
else if(compType == COMPONENT_TYPE_UINT64)
|
|
desc.varType = VarType::ULong;
|
|
else if(compType == COMPONENT_TYPE_SINT64)
|
|
desc.varType = VarType::SLong;
|
|
else if(compType == COMPONENT_TYPE_FLOAT64)
|
|
desc.varType = VarType::Double;
|
|
|
|
desc.regChannelMask = (uint8_t)(el->mask & 0xff);
|
|
desc.channelUsedMask = (uint8_t)(el->rwMask & 0xff);
|
|
desc.regIndex = el->registerNum;
|
|
desc.semanticIndex = (uint16_t)el->semanticIdx;
|
|
desc.semanticName = chunkContents + el->nameOffset;
|
|
desc.systemValue = GetSystemValue(el->systemType);
|
|
desc.compCount = (desc.regChannelMask & 0x1 ? 1 : 0) + (desc.regChannelMask & 0x2 ? 1 : 0) +
|
|
(desc.regChannelMask & 0x4 ? 1 : 0) + (desc.regChannelMask & 0x8 ? 1 : 0);
|
|
|
|
// this is the per-primitive signature for mesh shaders
|
|
if(m_Type == DXBC::ShaderType::Mesh && patchOrPerPrim)
|
|
desc.perPrimitiveRate = true;
|
|
|
|
RDCASSERT(m_Type != DXBC::ShaderType::Max);
|
|
|
|
// pixel shader outputs with registers are always targets
|
|
if(m_Type == DXBC::ShaderType::Pixel && output &&
|
|
desc.systemValue == ShaderBuiltin::Undefined && desc.regIndex <= 16)
|
|
desc.systemValue = ShaderBuiltin::ColorOutput;
|
|
|
|
// check system value semantics
|
|
if(desc.systemValue == ShaderBuiltin::Undefined)
|
|
{
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_Position"))
|
|
desc.systemValue = ShaderBuiltin::Position;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_ClipDistance"))
|
|
desc.systemValue = ShaderBuiltin::ClipDistance;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_CullDistance"))
|
|
desc.systemValue = ShaderBuiltin::CullDistance;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_RenderTargetArrayIndex"))
|
|
desc.systemValue = ShaderBuiltin::RTIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_ViewportArrayIndex"))
|
|
desc.systemValue = ShaderBuiltin::ViewportIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_VertexID"))
|
|
desc.systemValue = ShaderBuiltin::VertexIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_PrimitiveID"))
|
|
desc.systemValue = ShaderBuiltin::PrimitiveIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_InstanceID"))
|
|
desc.systemValue = ShaderBuiltin::InstanceIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_DispatchThreadID"))
|
|
desc.systemValue = ShaderBuiltin::DispatchThreadIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_GroupID"))
|
|
desc.systemValue = ShaderBuiltin::GroupIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_GroupIndex"))
|
|
desc.systemValue = ShaderBuiltin::GroupFlatIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_GroupThreadID"))
|
|
desc.systemValue = ShaderBuiltin::GroupThreadIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_GSInstanceID"))
|
|
desc.systemValue = ShaderBuiltin::GSInstanceIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_OutputControlPointID"))
|
|
desc.systemValue = ShaderBuiltin::OutputControlPointIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_DomainLocation"))
|
|
desc.systemValue = ShaderBuiltin::DomainLocation;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_IsFrontFace"))
|
|
desc.systemValue = ShaderBuiltin::IsFrontFace;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_SampleIndex"))
|
|
desc.systemValue = ShaderBuiltin::MSAASampleIndex;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_TessFactor"))
|
|
desc.systemValue = ShaderBuiltin::OuterTessFactor;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_InsideTessFactor"))
|
|
desc.systemValue = ShaderBuiltin::InsideTessFactor;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_Target"))
|
|
desc.systemValue = ShaderBuiltin::ColorOutput;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_Depth"))
|
|
desc.systemValue = ShaderBuiltin::DepthOutput;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_Coverage"))
|
|
desc.systemValue = ShaderBuiltin::MSAACoverage;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_DepthGreaterEqual"))
|
|
desc.systemValue = ShaderBuiltin::DepthOutputGreaterEqual;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_DepthLessEqual"))
|
|
desc.systemValue = ShaderBuiltin::DepthOutputLessEqual;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_Barycentrics"))
|
|
desc.systemValue = ShaderBuiltin::Barycentrics;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_ShadingRate"))
|
|
desc.systemValue = ShaderBuiltin::PackedFragRate;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_CullPrimitive"))
|
|
desc.systemValue = ShaderBuiltin::CullPrimitive;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_StencilRef"))
|
|
desc.systemValue = ShaderBuiltin::StencilReference;
|
|
if(!_stricmp(desc.semanticName.c_str(), "SV_InnerCoverage"))
|
|
desc.systemValue = ShaderBuiltin::IsFullyCovered;
|
|
}
|
|
|
|
RDCASSERT(desc.systemValue != ShaderBuiltin::Undefined || desc.regIndex >= 0);
|
|
|
|
sig->push_back(desc);
|
|
|
|
el0++;
|
|
el1++;
|
|
el7++;
|
|
}
|
|
|
|
for(uint32_t i = 0; i < sign->numElems; i++)
|
|
{
|
|
SigParameter &a = (*sig)[i];
|
|
|
|
for(uint32_t j = 0; j < sign->numElems; j++)
|
|
{
|
|
SigParameter &b = (*sig)[j];
|
|
if(i != j && a.semanticName == b.semanticName)
|
|
{
|
|
a.needSemanticIndex = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rdcstr semanticIdxName = a.semanticName;
|
|
if(a.needSemanticIndex)
|
|
semanticIdxName += ToStr(a.semanticIndex);
|
|
|
|
a.semanticIdxName = semanticIdxName;
|
|
}
|
|
}
|
|
else if(*fourcc == FOURCC_Aon9) // 10Level9 most likely
|
|
{
|
|
char *c = (char *)fourcc;
|
|
RDCWARN("Unknown chunk: %c%c%c%c", c[0], c[1], c[2], c[3]);
|
|
}
|
|
}
|
|
|
|
// sort per-primitive outputs to the end
|
|
if(m_Type == DXBC::ShaderType::Mesh)
|
|
{
|
|
std::stable_sort(m_Reflection->OutputSig.begin(), m_Reflection->OutputSig.end(),
|
|
[](const SigParameter &a, const SigParameter &b) {
|
|
return a.perPrimitiveRate < b.perPrimitiveRate;
|
|
});
|
|
}
|
|
|
|
// make sure to fetch the dispatch threads dimension from disassembly
|
|
if(m_Type == DXBC::ShaderType::Compute && m_DXBCByteCode)
|
|
m_DXBCByteCode->FetchComputeProperties(m_Reflection);
|
|
if((m_Type == DXBC::ShaderType::Compute || m_Type == DXBC::ShaderType::Amplification ||
|
|
m_Type == DXBC::ShaderType::Mesh) &&
|
|
m_DXILByteCode)
|
|
m_DXILByteCode->FetchComputeProperties(m_Reflection);
|
|
|
|
// initialise debug chunks last
|
|
for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);
|
|
|
|
if(*fourcc == FOURCC_SDBG)
|
|
{
|
|
m_DebugInfo = ProcessSDBGChunk(fourcc);
|
|
}
|
|
else if(*fourcc == FOURCC_SPDB)
|
|
{
|
|
m_DebugInfo = ProcessSPDBChunk(fourcc);
|
|
}
|
|
}
|
|
|
|
// try to find SPDB in the separate debug info pdb now
|
|
for(uint32_t chunkIdx = 0;
|
|
debugHeader && m_DebugInfo == NULL && chunkIdx < debugHeader->numChunks; chunkIdx++)
|
|
{
|
|
uint32_t *fourcc = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx]);
|
|
|
|
if(*fourcc == FOURCC_SPDB)
|
|
{
|
|
m_DebugInfo = ProcessSPDBChunk(fourcc);
|
|
}
|
|
}
|
|
|
|
if(m_DXBCByteCode && m_DebugInfo == NULL && !m_DebugShaderBlob.empty())
|
|
m_DebugInfo = ProcessPDB(m_DebugShaderBlob.data(), (uint32_t)m_DebugShaderBlob.size());
|
|
|
|
if(m_DXILByteCode)
|
|
m_DebugInfo = m_DXILByteCode;
|
|
|
|
// we do a mini-preprocess of the files from the debug info to handle #line directives.
|
|
// This means that any lines that our source file declares to be in another filename via a #line
|
|
// get put in the right place for what the debug information hopefully matches.
|
|
// We also concatenate duplicate lines and display them all, to handle edge cases where #lines
|
|
// declare duplicates.
|
|
|
|
if(m_DebugInfo)
|
|
{
|
|
if(m_DXBCByteCode)
|
|
m_DXBCByteCode->SetDebugInfo(m_DebugInfo);
|
|
|
|
PreprocessLineDirectives(m_DebugInfo->Files);
|
|
}
|
|
|
|
// if we had bytecode in this container, ensure we had reflection. If it's a blob with only an
|
|
// input signature then we can do without reflection.
|
|
if(m_DXBCByteCode || m_DXILByteCode)
|
|
{
|
|
RDCASSERT(m_Reflection);
|
|
|
|
if(shaderExtReg != ~0U)
|
|
{
|
|
bool found = false;
|
|
const bool pre_sm51 = (m_Version.Major * 10 + m_Version.Minor) < 51;
|
|
|
|
// see if we can find the magic UAV. If so remove it from the reflection
|
|
for(size_t i = 0; i < m_Reflection->UAVs.size(); i++)
|
|
{
|
|
const ShaderInputBind &uav = m_Reflection->UAVs[i];
|
|
if(uav.reg == shaderExtReg && (pre_sm51 || shaderExtSpace == uav.space))
|
|
{
|
|
found = true;
|
|
m_Reflection->UAVs.erase(i);
|
|
if(m_DXBCByteCode)
|
|
m_DXBCByteCode->SetShaderEXTUAV(api, shaderExtSpace, shaderExtReg);
|
|
m_ShaderExt = {shaderExtSpace, shaderExtReg};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DXBCContainer::~DXBCContainer()
|
|
{
|
|
// DXIL bytecode doubles as debug info, don't delete it twice
|
|
if(m_DXILByteCode)
|
|
m_DebugInfo = NULL;
|
|
|
|
SAFE_DELETE(m_DebugInfo);
|
|
|
|
SAFE_DELETE(m_DXBCByteCode);
|
|
SAFE_DELETE(m_DXILByteCode);
|
|
|
|
SAFE_DELETE(m_Reflection);
|
|
}
|
|
|
|
struct FxcArg
|
|
{
|
|
uint32_t bit;
|
|
const char *arg;
|
|
} fxc_flags[] = {
|
|
{D3DCOMPILE_DEBUG, " /Zi "},
|
|
{D3DCOMPILE_SKIP_VALIDATION, " /Vd "},
|
|
{D3DCOMPILE_SKIP_OPTIMIZATION, " /Od "},
|
|
{D3DCOMPILE_PACK_MATRIX_ROW_MAJOR, " /Zpr "},
|
|
{D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR, " /Zpc "},
|
|
{D3DCOMPILE_PARTIAL_PRECISION, " /Gpp "},
|
|
//{D3DCOMPILE_FORCE_VS_SOFTWARE_NO_OPT, " /XX "},
|
|
//{D3DCOMPILE_FORCE_PS_SOFTWARE_NO_OPT, " /XX "},
|
|
{D3DCOMPILE_NO_PRESHADER, " /Op "},
|
|
{D3DCOMPILE_AVOID_FLOW_CONTROL, " /Gfa "},
|
|
{D3DCOMPILE_PREFER_FLOW_CONTROL, " /Gfp "},
|
|
{D3DCOMPILE_ENABLE_STRICTNESS, " /Ges "},
|
|
{D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, " /Gec "},
|
|
{D3DCOMPILE_IEEE_STRICTNESS, " /Gis "},
|
|
{D3DCOMPILE_WARNINGS_ARE_ERRORS, " /WX "},
|
|
{D3DCOMPILE_RESOURCES_MAY_ALIAS, " /res_may_alias "},
|
|
{D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES, " /enable_unbounded_descriptor_tables "},
|
|
{D3DCOMPILE_ALL_RESOURCES_BOUND, " /all_resources_bound "},
|
|
{D3DCOMPILE_DEBUG_NAME_FOR_SOURCE, " /Zss "},
|
|
{D3DCOMPILE_DEBUG_NAME_FOR_BINARY, " /Zsb "},
|
|
};
|
|
|
|
uint32_t DecodeFlags(const ShaderCompileFlags &compileFlags)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
for(const ShaderCompileFlag flag : compileFlags.flags)
|
|
{
|
|
if(flag.name == "@cmdline")
|
|
{
|
|
rdcstr cmdline = flag.value;
|
|
|
|
// ensure cmdline is surrounded by spaces and all whitespace is spaces. This means we can
|
|
// search for our flags surrounded by space and ensure we get exact matches.
|
|
for(char &c : cmdline)
|
|
if(isspace(c))
|
|
c = ' ';
|
|
|
|
cmdline = " " + cmdline + " ";
|
|
|
|
for(const FxcArg &arg : fxc_flags)
|
|
{
|
|
if(strstr(cmdline.c_str(), arg.arg))
|
|
ret |= arg.bit;
|
|
}
|
|
|
|
// check optimisation special case
|
|
if(strstr(cmdline.c_str(), " /O0 "))
|
|
ret |= D3DCOMPILE_OPTIMIZATION_LEVEL0;
|
|
else if(strstr(cmdline.c_str(), " /O1 "))
|
|
ret |= D3DCOMPILE_OPTIMIZATION_LEVEL1;
|
|
else if(strstr(cmdline.c_str(), " /O2 "))
|
|
ret |= D3DCOMPILE_OPTIMIZATION_LEVEL2;
|
|
else if(strstr(cmdline.c_str(), " /O3 "))
|
|
ret |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
|
|
|
// ignore any other flags we might not understand
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
rdcstr GetProfile(const ShaderCompileFlags &compileFlags)
|
|
{
|
|
for(const ShaderCompileFlag flag : compileFlags.flags)
|
|
{
|
|
if(flag.name == "@cmdline")
|
|
{
|
|
rdcstr cmdline = flag.value;
|
|
|
|
// ensure cmdline is surrounded by spaces and all whitespace is spaces. This means we can
|
|
// search for our flags surrounded by space and ensure we get exact matches.
|
|
for(char &c : cmdline)
|
|
if(isspace(c))
|
|
c = ' ';
|
|
|
|
cmdline = " " + cmdline + " ";
|
|
|
|
const char *prof = strstr(cmdline.c_str(), " /T ");
|
|
if(!prof)
|
|
prof = strstr(cmdline.c_str(), " -T ");
|
|
|
|
if(!prof)
|
|
return "";
|
|
|
|
prof += 4;
|
|
|
|
return rdcstr(prof, strchr(prof, ' ') - prof);
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
ShaderCompileFlags EncodeFlags(const uint32_t flags, const rdcstr &profile)
|
|
{
|
|
ShaderCompileFlags ret;
|
|
|
|
rdcstr cmdline;
|
|
|
|
for(const FxcArg &arg : fxc_flags)
|
|
{
|
|
if(flags & arg.bit)
|
|
cmdline += arg.arg;
|
|
}
|
|
|
|
// optimization flags are a special case.
|
|
//
|
|
// D3DCOMPILE_OPTIMIZATION_LEVEL0 = (1 << 14)
|
|
// D3DCOMPILE_OPTIMIZATION_LEVEL1 = 0
|
|
// D3DCOMPILE_OPTIMIZATION_LEVEL2 = ((1 << 14) | (1 << 15))
|
|
// D3DCOMPILE_OPTIMIZATION_LEVEL3 = (1 << 15)
|
|
|
|
uint32_t opt = (flags & D3DCOMPILE_OPTIMIZATION_LEVEL2);
|
|
if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL0)
|
|
cmdline += " /O0";
|
|
else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL1)
|
|
cmdline += " /O1";
|
|
else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL2)
|
|
cmdline += " /O2";
|
|
else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL3)
|
|
cmdline += " /O3";
|
|
|
|
if(!profile.empty())
|
|
cmdline += " /T " + profile;
|
|
|
|
ret.flags = {{"@cmdline", cmdline.trimmed()}};
|
|
|
|
// If D3DCOMPILE_SKIP_OPTIMIZATION is set, then prefer source-level debugging as it should be
|
|
// accurate enough to work with.
|
|
if(flags & D3DCOMPILE_SKIP_OPTIMIZATION)
|
|
ret.flags.push_back({"preferSourceDebug", "1"});
|
|
|
|
return ret;
|
|
}
|
|
|
|
}; // namespace DXBC
|
|
|
|
#if ENABLED(ENABLE_UNIT_TESTS)
|
|
|
|
#include "catch/catch.hpp"
|
|
|
|
#if 0
|
|
|
|
TEST_CASE("DO NOT COMMIT - convenience test", "[dxbc]")
|
|
{
|
|
// this test loads a file from disk and passes it through DXBC::DXBCContainer. Useful for when you
|
|
// are iterating on a shader and don't want to have to load a whole capture.
|
|
bytebuf buf;
|
|
FileIO::ReadAll("/path/to/container_file.dxbc", buf);
|
|
|
|
DXBC::DXBCContainer container(buf, rdcstr(), GraphicsAPI::D3D11, ~0U, ~0U);
|
|
|
|
// the only thing fetched lazily is the disassembly, so grab that here
|
|
|
|
rdcstr disasm = container.GetDisassembly();
|
|
|
|
RDCLOG("%s", disasm.c_str());
|
|
}
|
|
|
|
#endif
|
|
|
|
#include "dxbc_compile.h"
|
|
|
|
TEST_CASE("Check DXBC hash algorithm", "[dxbc]")
|
|
{
|
|
SECTION("Test live compiles against fxc")
|
|
{
|
|
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;
|
|
|
|
// don't include debug info
|
|
uint32_t flags = D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_SKIP_OPTIMIZATION;
|
|
|
|
// create snippets that affect the compilation since we don't have embedded source
|
|
rdcarray<rdcstr> 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;
|
|
)",
|
|
};
|
|
|
|
// add 128 snippets, each with a one character long input to slowly inflate the reflection
|
|
// data.
|
|
// The intent is to produce bytecodes of length 0 through 60 mod 64 (since bytecode is always
|
|
// dword aligned)
|
|
{
|
|
const char *snippet = R"(
|
|
#define TEX_NAME tex%s
|
|
Texture2D<float> TEX_NAME : register(t0);
|
|
float4 main(float3 input : INPUT) : SV_Target0
|
|
{
|
|
return TEX_NAME.Load(input);
|
|
}
|
|
)";
|
|
rdcstr extra;
|
|
for(int i = 0; i < 128; i++)
|
|
{
|
|
snippets.push_back(StringFormat::Fmt(snippet, extra.c_str()));
|
|
extra += 'A';
|
|
}
|
|
}
|
|
|
|
bool dwordLength[15] = {};
|
|
|
|
for(rdcstr snippet : snippets)
|
|
{
|
|
rdcstr source;
|
|
|
|
if(snippet.contains("main("))
|
|
source = snippet;
|
|
else
|
|
source = R"(
|
|
Texture2D<float4> tex : register(t0);
|
|
|
|
float4 main(float input : INPUT) : SV_Target0
|
|
{
|
|
float4 ret = input.xxxx;
|
|
)" + snippet +
|
|
R"(
|
|
return ret;
|
|
}
|
|
|
|
)";
|
|
|
|
ID3DBlob *errBlob;
|
|
hr = compileFunc(source.c_str(), source.size(), "main", NULL, NULL, "main", "ps_5_0", flags,
|
|
0, &byteBlob, &errBlob);
|
|
|
|
if(errBlob)
|
|
RDCLOG("%s", (char *)errBlob->GetBufferPointer());
|
|
|
|
REQUIRE(SUCCEEDED(hr));
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
bytebuf bytecode;
|
|
bytecode.assign((const byte *)byteBlob->GetBufferPointer(), byteBlob->GetBufferSize());
|
|
|
|
REQUIRE(bytecode.size() % 4 == 0);
|
|
dwordLength[(bytecode.size() % 64) / 4] = true;
|
|
|
|
bytebuf hashed = bytecode;
|
|
DXBC::FileHeader *header = (DXBC::FileHeader *)hashed.data();
|
|
RDCEraseEl(header->hashValue);
|
|
|
|
DXBC::DXBCContainer::HashContainer(hashed.data(), hashed.size());
|
|
|
|
bool same = (bytecode == hashed);
|
|
CHECK(same);
|
|
}
|
|
|
|
SAFE_RELEASE(byteBlob);
|
|
}
|
|
|
|
// check that we've tested every length, mod 64.
|
|
for(int i = 0; i < ARRAY_COUNT(dwordLength); i++)
|
|
CHECK(dwordLength[i]);
|
|
}
|
|
|
|
SECTION("Test odd-sized buffer")
|
|
{
|
|
// dxc produces non-dword sized containers, but we don't want to pull dxc into our tests so we
|
|
// instead test a fixed known shader
|
|
|
|
bytebuf dxil = {
|
|
0x44, 0x58, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xef, 0x05, 0x00, 0x00, 0x06, 0x00,
|
|
0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xbb,
|
|
0x00, 0x00, 0x00, 0x37, 0x01, 0x00, 0x00, 0x53, 0x01, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30,
|
|
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47,
|
|
0x31, 0x2f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x49, 0x4e, 0x50, 0x55, 0x54, 0x41, 0x00, 0x4f, 0x53, 0x47, 0x31, 0x34, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x50, 0x6f,
|
|
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x50, 0x53, 0x56, 0x30, 0x74, 0x00, 0x00, 0x00,
|
|
0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00,
|
|
0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
|
|
0x00, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x00, 0x41, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x01, 0x00, 0x44, 0x03, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48,
|
|
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x28, 0x08, 0x8c, 0xa0, 0xf5, 0x45,
|
|
0x32, 0x63, 0x6a, 0x19, 0x1b, 0xa0, 0xf6, 0xc4, 0x76, 0x44, 0x58, 0x49, 0x4c, 0x94, 0x04,
|
|
0x00, 0x00, 0x60, 0x00, 0x01, 0x00, 0x25, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00,
|
|
0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x7c, 0x04, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde,
|
|
0x21, 0x0c, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00,
|
|
0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10,
|
|
0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80,
|
|
0x10, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0x84, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b,
|
|
0x0a, 0x32, 0x42, 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32,
|
|
0x42, 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x22, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83,
|
|
0xe5, 0x8a, 0x04, 0x21, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1b,
|
|
0x88, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0x00, 0x00, 0x49, 0x18, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x13, 0x82, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x0e, 0x00, 0x00,
|
|
0x00, 0x32, 0x22, 0x08, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x22, 0xa4, 0x84, 0x04, 0x13,
|
|
0x22, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x88, 0x8c, 0x0b, 0x84, 0x84, 0x4c, 0x10,
|
|
0x28, 0x23, 0x00, 0x25, 0x00, 0x8a, 0x39, 0x02, 0x30, 0x98, 0x23, 0x40, 0x66, 0x00, 0x8a,
|
|
0x01, 0x33, 0x43, 0x45, 0x36, 0x10, 0x90, 0x03, 0x03, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72,
|
|
0xc0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d,
|
|
0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, 0x07,
|
|
0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, 0x07,
|
|
0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07,
|
|
0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07,
|
|
0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07,
|
|
0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, 0x07,
|
|
0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e,
|
|
0x78, 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07,
|
|
0x43, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3c,
|
|
0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x81, 0x00, 0x00,
|
|
0x0b, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x10, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26,
|
|
0x47, 0xc6, 0x04, 0x43, 0x9a, 0x12, 0x18, 0x01, 0x28, 0x85, 0x62, 0x28, 0x83, 0xf2, 0x20,
|
|
0x2a, 0x89, 0x11, 0x80, 0x12, 0x28, 0x83, 0x42, 0xa0, 0x1c, 0x6b, 0x08, 0x08, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90,
|
|
0x46, 0x02, 0x13, 0x44, 0x35, 0x18, 0x63, 0x0b, 0x73, 0x3b, 0x03, 0xb1, 0x2b, 0x93, 0x9b,
|
|
0x4b, 0x7b, 0x73, 0x03, 0x99, 0x71, 0xb9, 0x01, 0x41, 0xa1, 0x0b, 0x3b, 0x9b, 0x7b, 0x91,
|
|
0x2a, 0x62, 0x2a, 0x0a, 0x9a, 0x2a, 0xfa, 0x9a, 0xb9, 0x81, 0x79, 0x31, 0x4b, 0x73, 0x0b,
|
|
0x63, 0x4b, 0xd9, 0x10, 0x04, 0x13, 0x84, 0x41, 0x98, 0x20, 0x0c, 0xc3, 0x06, 0x61, 0x20,
|
|
0x26, 0x08, 0x03, 0xb1, 0x41, 0x18, 0x0c, 0x0a, 0x76, 0x73, 0x13, 0x84, 0xa1, 0xd8, 0x30,
|
|
0x20, 0x09, 0x31, 0x41, 0x48, 0x9a, 0x0d, 0xc1, 0x32, 0x41, 0x10, 0x00, 0x12, 0x6d, 0x61,
|
|
0x69, 0x6e, 0x34, 0x92, 0x9c, 0xa0, 0xaa, 0xa8, 0x82, 0x26, 0x08, 0x04, 0x32, 0x41, 0x20,
|
|
0x92, 0x0d, 0x01, 0x31, 0x41, 0x20, 0x94, 0x0d, 0x0b, 0xf1, 0x40, 0x91, 0x14, 0x0d, 0x13,
|
|
0x11, 0x01, 0x1b, 0x02, 0x8a, 0xcb, 0x94, 0xd5, 0x17, 0xd4, 0xdb, 0x5c, 0x1a, 0x5d, 0xda,
|
|
0x9b, 0xdb, 0x04, 0x81, 0x58, 0x26, 0x08, 0x04, 0x33, 0x41, 0x18, 0x8c, 0x09, 0xc2, 0x70,
|
|
0x6c, 0x10, 0x32, 0x6d, 0xc3, 0x42, 0x58, 0xd0, 0x25, 0x61, 0x03, 0x46, 0x44, 0xdb, 0x86,
|
|
0x80, 0xdb, 0x30, 0x54, 0x1d, 0xb0, 0xa1, 0x68, 0x1c, 0x0f, 0x00, 0xaa, 0xb0, 0xb1, 0xd9,
|
|
0xb5, 0xb9, 0xa4, 0x91, 0x95, 0xb9, 0xd1, 0x4d, 0x09, 0x82, 0x2a, 0x64, 0x78, 0x2e, 0x76,
|
|
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x02, 0xa2, 0x09, 0x19, 0x9e, 0x8b, 0x5d, 0x18,
|
|
0x9b, 0x5d, 0x99, 0xdc, 0x94, 0xc0, 0xa8, 0x43, 0x86, 0xe7, 0x32, 0x87, 0x16, 0x46, 0x56,
|
|
0x26, 0xd7, 0xf4, 0x46, 0x56, 0xc6, 0x36, 0x25, 0x48, 0xea, 0x90, 0xe1, 0xb9, 0xd8, 0xa5,
|
|
0x95, 0xdd, 0x25, 0x91, 0x4d, 0xd1, 0x85, 0xd1, 0x95, 0x4d, 0x09, 0x96, 0x3a, 0x64, 0x78,
|
|
0x2e, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x53, 0x02, 0x0f,
|
|
0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4,
|
|
0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07,
|
|
0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80,
|
|
0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d,
|
|
0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d,
|
|
0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07,
|
|
0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, 0x11, 0x0e, 0xec, 0x90,
|
|
0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, 0x0e, 0x33, 0x10,
|
|
0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, 0x89, 0x3b,
|
|
0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, 0x3b,
|
|
0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07,
|
|
0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05,
|
|
0x76, 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87,
|
|
0x79, 0x98, 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30,
|
|
0x03, 0x62, 0xc8, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc,
|
|
0x61, 0x1c, 0xca, 0x21, 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39,
|
|
0xc8, 0x43, 0x39, 0x98, 0x43, 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38,
|
|
0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b,
|
|
0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87,
|
|
0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10,
|
|
0x0e, 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, 0x0f, 0xf4, 0x00, 0x00, 0x00, 0x71,
|
|
0x20, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x50, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x34,
|
|
0x39, 0x11, 0x81, 0x52, 0xd3, 0x43, 0x4d, 0x7e, 0x71, 0xdb, 0x06, 0x40, 0x30, 0x00, 0xd2,
|
|
0x00, 0x61, 0x20, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x13, 0x04, 0x41, 0x2c, 0x10, 0x00,
|
|
0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x44, 0x45, 0x40, 0x35, 0x46, 0x00, 0x82, 0x20, 0x88,
|
|
0x7f, 0x63, 0x04, 0x20, 0x08, 0x82, 0x20, 0x18, 0x8c, 0x11, 0x80, 0x20, 0x08, 0x92, 0x60,
|
|
0x30, 0x46, 0x00, 0x82, 0x20, 0x88, 0x82, 0x01, 0x00, 0x00, 0x00, 0x00, 0x23, 0x06, 0x09,
|
|
0x00, 0x82, 0x60, 0x60, 0x48, 0x0f, 0x04, 0x29, 0xc4, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18,
|
|
0x18, 0xd2, 0x03, 0x41, 0xc9, 0x30, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x86, 0xf4, 0x40,
|
|
0x50, 0x21, 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0x21, 0x3d, 0x10, 0x84, 0x04, 0x08,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
DXBC::DXBCContainer::HashContainer(dxil.data(), dxil.size());
|
|
|
|
DXBC::FileHeader *header = (DXBC::FileHeader *)dxil.data();
|
|
|
|
CHECK(header->hashValue[0] == 3739765114);
|
|
CHECK(header->hashValue[1] == 3689508432);
|
|
CHECK(header->hashValue[2] == 2832704775);
|
|
CHECK(header->hashValue[3] == 3632933760);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Check DXBC flags are non-overlapping", "[dxbc]")
|
|
{
|
|
for(const DXBC::FxcArg &a : DXBC::fxc_flags)
|
|
{
|
|
for(const DXBC::FxcArg &b : DXBC::fxc_flags)
|
|
{
|
|
if(a.arg == b.arg)
|
|
continue;
|
|
|
|
// no argument should be a subset of another argument
|
|
rdcstr arga = a.arg;
|
|
rdcstr argb = b.arg;
|
|
arga.trim();
|
|
argb.trim();
|
|
INFO("a: '" << arga << "' b: '" << argb << "'");
|
|
CHECK(strstr(arga.c_str(), argb.c_str()) == NULL);
|
|
CHECK(strstr(argb.c_str(), arga.c_str()) == NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Check DXBC flag encoding/decoding", "[dxbc]")
|
|
{
|
|
SECTION("encode/decode identity")
|
|
{
|
|
uint32_t flags = D3DCOMPILE_PARTIAL_PRECISION | D3DCOMPILE_SKIP_OPTIMIZATION |
|
|
D3DCOMPILE_ALL_RESOURCES_BOUND | D3DCOMPILE_OPTIMIZATION_LEVEL2;
|
|
uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = 0;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_DEBUG;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
};
|
|
|
|
SECTION("encode/decode discards unrecognised parameters")
|
|
{
|
|
uint32_t flags = D3DCOMPILE_PARTIAL_PRECISION | (1 << 30);
|
|
uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags2 == D3DCOMPILE_PARTIAL_PRECISION);
|
|
|
|
ShaderCompileFlags compileflags;
|
|
|
|
compileflags.flags = {
|
|
{"@cmdline", "/Zi /Z8 /JJ /WX /K other words embed/Odparam /DFoo=\"bar\""}};
|
|
|
|
flags2 = DXBC::DecodeFlags(compileflags);
|
|
|
|
CHECK(flags2 == (D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS));
|
|
|
|
flags = ~0U;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
uint32_t allflags = 0;
|
|
for(const DXBC::FxcArg &a : DXBC::fxc_flags)
|
|
allflags |= a.bit;
|
|
|
|
allflags |= D3DCOMPILE_OPTIMIZATION_LEVEL2;
|
|
|
|
CHECK(flags2 == allflags);
|
|
};
|
|
|
|
SECTION("optimisation flags are properly decoded and encoded")
|
|
{
|
|
uint32_t flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL0;
|
|
uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL1;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL2;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
};
|
|
|
|
SECTION("Profile is correctly encoded and decoded")
|
|
{
|
|
const uint32_t flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
|
|
rdcstr profile = "ps_5_0";
|
|
rdcstr profile2 = DXBC::GetProfile(DXBC::EncodeFlags(flags, profile));
|
|
|
|
CHECK(profile == profile2);
|
|
|
|
profile = "ps_4_0";
|
|
profile2 = DXBC::GetProfile(DXBC::EncodeFlags(flags, profile));
|
|
|
|
CHECK(profile == profile2);
|
|
|
|
profile = "";
|
|
profile2 = DXBC::GetProfile(DXBC::EncodeFlags(flags, profile));
|
|
|
|
CHECK(profile == profile2);
|
|
|
|
profile = "cs_5_0";
|
|
profile2 = DXBC::GetProfile(DXBC::EncodeFlags(flags, profile));
|
|
|
|
CHECK(profile == profile2);
|
|
|
|
profile = "??_9_9";
|
|
profile2 = DXBC::GetProfile(DXBC::EncodeFlags(flags, profile));
|
|
|
|
CHECK(profile == profile2);
|
|
};
|
|
|
|
SECTION("Profile does not affect flag encoding")
|
|
{
|
|
uint32_t flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, ""));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, "ps_5_0"));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, "ps_4_0"));
|
|
|
|
CHECK(flags == flags2);
|
|
|
|
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags, "??_9_9"));
|
|
|
|
CHECK(flags == flags2);
|
|
};
|
|
}
|
|
#endif
|