Files
renderdoc/renderdoc/driver/shaders/dxil/dxil_reflect.cpp
T
Jake Turner fa33bcfbfb Add debug config variable D3D12_Experimental_EnableDXILShaderDebugging
When DXIL debugging is enabled, GetDebugStatus() matches currently supported LLVM instructions and DX.OP calls.
2024-10-16 10:10:40 +01:00

2169 lines
72 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2024 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 "common/formatting.h"
#include "core/settings.h"
#include "strings/string_utils.h"
#include "dxil_bytecode.h"
#include "dxil_common.h"
RDOC_DEBUG_CONFIG(bool, D3D12_Experimental_EnableDXILShaderDebugging, false,
"Enable support for experimental DXIL shader debugger");
namespace DXIL
{
enum class ResourcesTag
{
// SRV & UAV
ElementType = 0,
StructStride = 1,
// UAV
SamplerFeedbackKind = 2,
Atomic64Use = 3,
// CBuffer
IsTBufferTag = 0,
// Sampler
};
namespace SignatureElement
{
const uint32_t ID = 0;
const uint32_t Name = 1;
const uint32_t Type = 2;
const uint32_t SystemValue = 3;
const uint32_t IndexVector = 4;
const uint32_t InterpMode = 5;
const uint32_t Rows = 6;
const uint32_t Cols = 7;
const uint32_t StartRow = 8;
const uint32_t StartCol = 9;
const uint32_t NameValueList = 10;
};
enum class StructMemberAnnotation
{
SNorm = 0,
UNorm = 1,
Matrix = 2,
CBufferOffset = 3,
SemanticString = 4,
InterpolationMode = 5,
FieldName = 6,
CompType = 7,
Precise = 8,
CBUsed = 9,
// ResourceProperties = 10,
// BitFields = 11,
FieldWidth = 12,
VectorSize = 13,
};
template <typename T>
T getival(const Metadata *m)
{
Constant *c = cast<Constant>(m->value);
if(c && c->isLiteral())
return T(c->getU32());
return T();
}
struct DXMeta
{
struct
{
const Metadata *contents = NULL;
const Metadata *defines = NULL;
const Metadata *mainFileName = NULL;
const Metadata *args = NULL;
} source;
const Metadata *version = NULL;
const Metadata *valver = NULL;
const Metadata *shaderModel = NULL;
const Metadata *resources = NULL;
const Metadata *typeAnnotations = NULL;
const Metadata *viewIdState = NULL;
const Metadata *entryPoints = NULL;
// technically llvm.ident
const Metadata *ident = NULL;
DXMeta(const rdcarray<NamedMetadata *> &namedMeta)
{
DXMeta &dx = *this;
DXMeta &llvm = *this;
for(size_t i = 0; i < namedMeta.size(); i++)
{
#define GRAB_META(metaname) \
if(namedMeta[i]->name == #metaname) \
metaname = namedMeta[i];
GRAB_META(llvm.ident);
GRAB_META(dx.source.contents);
GRAB_META(dx.source.defines);
GRAB_META(dx.source.mainFileName);
GRAB_META(dx.source.args);
GRAB_META(dx.version);
GRAB_META(dx.valver);
GRAB_META(dx.shaderModel);
GRAB_META(dx.resources);
GRAB_META(dx.typeAnnotations);
GRAB_META(dx.viewIdState);
GRAB_META(dx.entryPoints);
#undef GRAB_META
}
}
};
struct TypeInfo
{
struct MemberData
{
enum Flags : uint8_t
{
None = 0,
UNorm = 0x1,
SNorm = 0x2,
RowMajor = 0x4,
Matrix = 0x8,
Precise = 0x10,
CBUsed = 0x20,
} flags = None;
uint8_t rows = 0, cols = 0;
uint32_t offset;
rdcstr name;
rdcstr semantic;
ComponentType type;
uint32_t fieldWidth;
uint32_t vectorSize;
};
struct StructData
{
rdcarray<MemberData> members;
};
std::map<const Type *, StructData> structData;
TypeInfo(const Metadata *typeAnnotations)
{
if(!typeAnnotations)
return;
const Metadata *structAnnotations = NULL;
for(size_t i = 0; i < typeAnnotations->children.size(); i++)
{
if(getival<uint32_t>(typeAnnotations->children[i]->children[0]) == 0)
{
structAnnotations = typeAnnotations->children[i];
break;
}
}
if(!structAnnotations)
return;
for(size_t c = 1; c < structAnnotations->children.size(); c += 2)
{
const Type *type = structAnnotations->children[c]->type;
const Metadata *structMembers = structAnnotations->children[c + 1];
RDCASSERT(structMembers->children.size() - 1 >= type->members.size(),
structMembers->children.size(), type->members.size());
StructData &data = structData[type];
data.members.resize(type->members.size());
for(size_t m = 0; m < type->members.size(); m++)
{
const Metadata *memberIn = structMembers->children[m + 1];
MemberData &memberOut = data.members[m];
for(size_t tag = 0; tag < memberIn->children.size(); tag += 2)
{
StructMemberAnnotation fieldTag = getival<StructMemberAnnotation>(memberIn->children[tag]);
switch(fieldTag)
{
case StructMemberAnnotation::SNorm:
{
if(getival<uint32_t>(memberIn->children[tag + 1]) != 0)
memberOut.flags = MemberData::Flags(memberOut.flags | MemberData::SNorm);
break;
}
case StructMemberAnnotation::UNorm:
{
if(getival<uint32_t>(memberIn->children[tag + 1]) != 0)
memberOut.flags = MemberData::Flags(memberOut.flags | MemberData::UNorm);
break;
}
case StructMemberAnnotation::Matrix:
{
const Metadata *matrixData = memberIn->children[tag + 1];
memberOut.rows = getival<uint8_t>(matrixData->children[0]);
memberOut.cols = getival<uint8_t>(matrixData->children[1]);
bool rowmajor = (getival<uint32_t>(matrixData->children[2]) == 1);
if(rowmajor)
memberOut.flags =
MemberData::Flags(memberOut.flags | MemberData::RowMajor | MemberData::Matrix);
else
memberOut.flags = MemberData::Flags(memberOut.flags | MemberData::Matrix);
break;
}
case StructMemberAnnotation::CBufferOffset:
memberOut.offset = getival<uint32_t>(memberIn->children[tag + 1]);
break;
case StructMemberAnnotation::SemanticString:
memberOut.semantic = memberIn->children[tag + 1]->str;
break;
case StructMemberAnnotation::InterpolationMode: break;
case StructMemberAnnotation::FieldName:
memberOut.name = memberIn->children[tag + 1]->str;
break;
case StructMemberAnnotation::CompType:
memberOut.type = getival<ComponentType>(memberIn->children[tag + 1]);
break;
case StructMemberAnnotation::Precise:
{
if(getival<uint32_t>(memberIn->children[tag + 1]) != 0)
memberOut.flags = MemberData::Flags(memberOut.flags | MemberData::Precise);
break;
}
case StructMemberAnnotation::CBUsed:
{
if(getival<uint32_t>(memberIn->children[tag + 1]) != 0)
memberOut.flags = MemberData::Flags(memberOut.flags | MemberData::CBUsed);
break;
}
case StructMemberAnnotation::FieldWidth:
memberOut.fieldWidth = getival<uint32_t>(memberIn->children[tag + 1]);
break;
case StructMemberAnnotation::VectorSize:
memberOut.vectorSize = getival<uint32_t>(memberIn->children[tag + 1]);
break;
default: RDCWARN("Unexpected field tag %u", fieldTag); break;
}
}
}
}
}
};
EntryPointInterface::Signature::Signature(const Metadata *signature)
{
/*
// Extended properties
static const unsigned kDxilSignatureElementOutputStreamTag = 0;
static const unsigned kHLSignatureElementGlobalSymbolTag = 1;
static const unsigned kDxilSignatureElementDynIdxCompMaskTag = 2;
static const unsigned kDxilSignatureElementUsageCompMaskTag = 3;
*/
name = signature->children[SignatureElement::Name]->str;
type = getival<ComponentType>(signature->children[SignatureElement::Type]);
interpolation = getival<D3D_INTERPOLATION_MODE>(signature->children[SignatureElement::InterpMode]);
rows = getival<uint32_t>(signature->children[SignatureElement::Rows]);
cols = getival<uint8_t>(signature->children[SignatureElement::Cols]);
startRow = getival<int32_t>(signature->children[SignatureElement::StartRow]);
startCol = getival<int8_t>(signature->children[SignatureElement::StartCol]);
}
EntryPointInterface::ResourceBase::ResourceBase(ResourceClass resourceClass, const Metadata *md)
: resClass(resourceClass)
{
id = getival<uint32_t>(md->children[(size_t)ResField::ID]);
type = md->children[(size_t)ResField::VarDecl]->type;
name = md->children[(size_t)ResField::Name]->str;
space = getival<uint32_t>(md->children[(size_t)ResField::Space]);
regBase = getival<uint32_t>(md->children[(size_t)ResField::RegBase]);
regCount = getival<uint32_t>(md->children[(size_t)ResField::RegCount]);
if(resourceClass == ResourceClass::SRV)
{
SRV &srv = srvData;
srv.shape = getival<ResourceKind>(md->children[(size_t)ResField::SRVShape]);
srv.sampleCount = getival<uint32_t>(md->children[(size_t)ResField::SRVSampleCount]);
const Metadata *tags = md->children[(size_t)ResField::SRVTags];
for(size_t t = 0; tags && t < tags->children.size(); t += 2)
{
RDCASSERT(tags->children[t]->isConstant);
ResourcesTag tag = getival<ResourcesTag>(tags->children[t]);
switch(tag)
{
case ResourcesTag::ElementType:
srv.compType = getival<ComponentType>(tags->children[t + 1]);
break;
case ResourcesTag::StructStride:
srv.elementStride = getival<uint32_t>(tags->children[t + 1]);
break;
default: break;
}
}
}
else if(resourceClass == ResourceClass::UAV)
{
UAV &uav = uavData;
uav.shape = getival<ResourceKind>(md->children[(size_t)ResField::UAVShape]);
uav.globallCoherent =
(getival<uint32_t>(md->children[(size_t)ResField::UAVGloballyCoherent]) == 1);
uav.hasCounter = (getival<uint32_t>(md->children[(size_t)ResField::UAVHiddenCounter]) == 1);
uav.rasterizerOrderedView =
(getival<uint32_t>(md->children[(size_t)ResField::UAVRasterOrder]) == 1);
const Metadata *tags = md->children[(size_t)ResField::UAVTags];
for(size_t t = 0; tags && t < tags->children.size(); t += 2)
{
RDCASSERT(tags->children[t]->isConstant);
ResourcesTag tag = getival<ResourcesTag>(tags->children[t]);
switch(tag)
{
case ResourcesTag::ElementType:
uav.compType = getival<ComponentType>(tags->children[t + 1]);
break;
case ResourcesTag::StructStride:
uav.elementStride = getival<uint32_t>(tags->children[t + 1]);
break;
case ResourcesTag::SamplerFeedbackKind:
uav.samplerFeedback = getival<SamplerFeedbackType>(tags->children[t + 1]);
break;
case ResourcesTag::Atomic64Use:
uav.atomic64Use = (getival<uint32_t>(tags->children[t + 1]) == 1);
break;
default: break;
}
}
}
else if(resourceClass == ResourceClass::CBuffer)
{
CBuffer &cbuffer = cbufferData;
cbuffer.sizeInBytes = getival<uint32_t>(md->children[(size_t)ResField::CBufferByteSize]);
const Metadata *tags = md->children[(size_t)ResField::CBufferTags];
for(size_t t = 0; tags && t < tags->children.size(); t += 2)
{
RDCASSERT(tags->children[t]->isConstant);
ResourcesTag tag = getival<ResourcesTag>(tags->children[t]);
if(tag == ResourcesTag::IsTBufferTag)
cbuffer.isTBuffer = (getival<uint32_t>(tags->children[t + 1]) == 1);
}
cbuffer.cbufferRefl = NULL;
}
else if(resourceClass == ResourceClass::Sampler)
{
Sampler &sampler = samplerData;
sampler.samplerType = getival<SamplerKind>(md->children[(size_t)ResField::SamplerType]);
}
}
EntryPointInterface::EntryPointInterface(const Metadata *entryPoint)
{
if(entryPoint->children[0] == NULL)
return;
function = entryPoint->children[0]->type;
name = entryPoint->children[1]->str;
const Metadata *signatures = entryPoint->children[2];
if(signatures)
{
const Metadata *ins = signatures->children[0];
if(ins)
{
for(size_t i = 0; i < ins->children.size(); ++i)
inputs.push_back(ins->children[i]);
}
const Metadata *outs = signatures->children[1];
if(outs)
{
for(size_t i = 0; i < outs->children.size(); ++i)
outputs.push_back(outs->children[i]);
}
const Metadata *patchCons = signatures->children[2];
if(patchCons)
{
for(size_t i = 0; i < patchCons->children.size(); ++i)
patchConstants.push_back(patchCons->children[i]);
}
}
// SRVs, UAVs, CBs, Samplers
const Metadata *resources = entryPoint->children[3];
if(resources)
{
const Metadata *srvsMeta = resources->children[0];
if(srvsMeta)
{
for(size_t i = 0; i < srvsMeta->children.size(); ++i)
srvs.push_back(ResourceBase(ResourceClass::SRV, srvsMeta->children[i]));
}
const Metadata *uavsMeta = resources->children[1];
if(uavsMeta)
{
for(size_t i = 0; i < uavsMeta->children.size(); ++i)
uavs.push_back(ResourceBase(ResourceClass::UAV, uavsMeta->children[i]));
}
const Metadata *cbuffersMeta = resources->children[2];
if(cbuffersMeta)
{
for(size_t i = 0; i < cbuffersMeta->children.size(); ++i)
cbuffers.push_back(ResourceBase(ResourceClass::CBuffer, cbuffersMeta->children[i]));
}
const Metadata *samplersMeta = resources->children[3];
if(samplersMeta)
{
for(size_t i = 0; i < samplersMeta->children.size(); ++i)
samplers.push_back(ResourceBase(ResourceClass::Sampler, samplersMeta->children[i]));
}
}
/*
static const unsigned kDxilShaderFlagsTag = 0;
static const unsigned kDxilGSStateTag = 1;
static const unsigned kDxilDSStateTag = 2;
static const unsigned kDxilHSStateTag = 3;
static const unsigned kDxilNumThreadsTag = 4;
static const unsigned kDxilAutoBindingSpaceTag = 5;
static const unsigned kDxilRayPayloadSizeTag = 6;
static const unsigned kDxilRayAttribSizeTag = 7;
static const unsigned kDxilShaderKindTag = 8;
static const unsigned kDxilMSStateTag = 9;
static const unsigned kDxilASStateTag = 10;
static const unsigned kDxilWaveSizeTag = 11;
static const unsigned kDxilEntryRootSigTag = 12;
static const unsigned kDxilNodeLaunchTypeTag = 13;
static const unsigned kDxilNodeIsProgramEntryTag = 14;
static const unsigned kDxilNodeIdTag = 15;
static const unsigned kDxilNodeLocalRootArgumentsTableIndexTag = 16;
static const unsigned kDxilShareInputOfTag = 17;
static const unsigned kDxilNodeDispatchGridTag = 18;
static const unsigned kDxilNodeMaxRecursionDepthTag = 19;
static const unsigned kDxilNodeInputsTag = 20;
static const unsigned kDxilNodeOutputsTag = 21;
static const unsigned kDxilNodeMaxDispatchGridTag = 22;
static const unsigned kDxilRangedWaveSizeTag = 23;
*/
const Metadata *properties = entryPoint->children[4];
if(properties)
{
}
}
static DXBC::CBufferVariableType MakePayloadType(const TypeInfo &typeInfo, const Type *t)
{
using namespace DXBC;
CBufferVariableType ret = {};
ret.elements = 1;
if(t->type == Type::Scalar || t->type == Type::Vector)
{
ret.rows = ret.cols = 1;
if(t->type == Type::Vector)
ret.cols = t->elemCount;
ret.bytesize = (t->bitWidth / 8) * ret.cols;
ret.varClass = CLASS_SCALAR;
if(t->scalarType == Type::Float)
{
if(t->bitWidth > 32)
ret.varType = VarType::Double;
else if(t->bitWidth == 16)
ret.varType = VarType::Half;
else
ret.varType = VarType::Float;
}
else
{
// can't distinguish int/uint here, default to signed
if(t->bitWidth > 32)
ret.varType = VarType::SLong;
else if(t->bitWidth == 32)
ret.varType = VarType::SInt;
else if(t->bitWidth == 16)
ret.varType = VarType::SShort;
else if(t->bitWidth == 8)
ret.varType = VarType::SByte;
else if(t->bitWidth == 1)
ret.varType = VarType::Bool;
}
ret.name = ToStr(ret.varType);
if(t->type == Type::Vector)
ret.name += ToStr(ret.cols);
}
else if(t->type == Type::Array)
{
ret = MakePayloadType(typeInfo, t->inner);
ret.elements *= RDCMAX(1U, t->elemCount);
ret.bytesize += (ret.elements - 1) * ret.bytesize;
}
else if(t->type == Type::Struct)
{
ret.name = t->name;
ret.varType = VarType::Unknown;
ret.varClass = CLASS_STRUCT;
auto it = typeInfo.structData.find(t);
char structPrefix[] = "struct.";
if(ret.name.beginsWith(structPrefix))
ret.name.erase(0, sizeof(structPrefix) - 1);
char classPrefix[] = "class.";
if(ret.name.beginsWith(classPrefix))
ret.name.erase(0, sizeof(classPrefix) - 1);
for(size_t i = 0; i < t->members.size(); i++)
{
ret.members.push_back({});
ret.members.back().type = MakePayloadType(typeInfo, t->members[i]);
if(it != typeInfo.structData.end())
ret.members.back().name = it->second.members[i].name;
else
ret.members.back().name = StringFormat::Fmt("member%zu", i);
ret.bytesize += ret.members.back().type.bytesize;
}
}
else
{
RDCERR("Unexpected type %u iterating cbuffer variable type %s", t->type, t->name.c_str());
}
return ret;
}
void Program::FillEntryPointInterfaces()
{
if(!m_EntryPointInterfaces.isEmpty())
return;
DXMeta dx(m_NamedMeta);
m_EntryPointInterfaces.clear();
if(!dx.entryPoints)
return;
for(size_t c = 0; c < dx.entryPoints->children.size(); ++c)
m_EntryPointInterfaces.emplace_back(dx.entryPoints->children[c]);
}
void Program::FetchComputeProperties(DXBC::Reflection *reflection)
{
DXMeta dx(m_NamedMeta);
TypeInfo typeInfo(dx.typeAnnotations);
for(size_t i = 0; i < m_Functions.size(); i++)
{
const Function &f = *m_Functions[i];
// Match "dx.op.threadIdGroup" before "dx.op.threadId"
if(f.name.beginsWith("dx.op.threadIdInGroup"))
{
SigParameter param;
param.systemValue = ShaderBuiltin::GroupThreadIndex;
param.compCount = 3;
param.regChannelMask = param.channelUsedMask = 0x7;
param.semanticIdxName = param.semanticName = "threadIdInGroup";
param.varType = VarType::UInt;
reflection->InputSig.push_back(param);
}
else if(f.name.beginsWith("dx.op.threadId"))
{
SigParameter param;
param.systemValue = ShaderBuiltin::DispatchThreadIndex;
param.compCount = 3;
param.regChannelMask = param.channelUsedMask = 0x7;
param.semanticIdxName = param.semanticName = "threadId";
param.varType = VarType::UInt;
reflection->InputSig.push_back(param);
}
else if(f.name.beginsWith("dx.op.groupId"))
{
SigParameter param;
param.systemValue = ShaderBuiltin::GroupIndex;
param.compCount = 3;
param.regChannelMask = param.channelUsedMask = 0x7;
param.semanticIdxName = param.semanticName = "groupID";
param.varType = VarType::UInt;
reflection->InputSig.push_back(param);
}
else if(f.name.beginsWith("dx.op.flattenedThreadIdInGroup"))
{
SigParameter param;
param.systemValue = ShaderBuiltin::GroupFlatIndex;
param.compCount = 1;
param.regChannelMask = param.channelUsedMask = 0x1;
param.semanticIdxName = param.semanticName = "flattenedThreadIdInGroup";
param.varType = VarType::UInt;
reflection->InputSig.push_back(param);
}
if(m_Type == DXBC::ShaderType::Amplification)
{
for(const Instruction *in : f.instructions)
{
const Instruction &inst = *in;
if(inst.op == Operation::Call && inst.getFuncCall()->name.beginsWith("dx.op.dispatchMesh"))
{
if(inst.args.size() != 5)
{
RDCERR("Unexpected number of arguments to dispatchMesh");
continue;
}
Type *payloadType = NULL;
GlobalVar *payloadVariable = cast<GlobalVar>(inst.args[4]);
if(payloadVariable)
{
payloadType = (Type *)payloadVariable->type;
}
else
{
Instruction *payloadAlloc = cast<Instruction>(inst.args[4]);
if(payloadAlloc->op == Operation::Alloca || payloadAlloc->op == Operation::GetElementPtr)
{
payloadType = (Type *)payloadAlloc->type;
}
else
{
RDCERR("Unexpected non-variable payload argument to dispatchMesh");
continue;
}
}
RDCASSERT(payloadType->type == Type::Pointer);
payloadType = (Type *)payloadType->inner;
reflection->TaskPayload = MakePayloadType(typeInfo, payloadType);
break;
}
}
}
}
for(size_t i = 0; i < m_NamedMeta.size(); i++)
{
const NamedMetadata &m = *m_NamedMeta[i];
if(m.name == "dx.entryPoints")
{
// expect only one child for this, DX doesn't support multiple entry points for compute
// shaders
RDCASSERTEQUAL(m.children.size(), 1);
Metadata &entry = *m.children[0];
RDCASSERTEQUAL(entry.children.size(), 5);
Metadata &tags = *entry.children[4];
for(size_t t = 0; t < tags.children.size(); t += 2)
{
RDCASSERT(tags.children[t]->isConstant);
ShaderEntryTag shaderTypeTag = getival<ShaderEntryTag>(tags.children[t]);
if(shaderTypeTag == ShaderEntryTag::Compute)
{
Metadata &threadDim = *tags.children[t + 1];
RDCASSERTEQUAL(threadDim.children.size(), 3);
reflection->DispatchThreadsDimension[0] = getival<uint32_t>(threadDim.children[0]);
reflection->DispatchThreadsDimension[1] = getival<uint32_t>(threadDim.children[1]);
reflection->DispatchThreadsDimension[2] = getival<uint32_t>(threadDim.children[2]);
return;
}
else if(shaderTypeTag == ShaderEntryTag::Amplification)
{
Metadata &ampData = *tags.children[t + 1];
Metadata &threadDim = *ampData.children[0];
RDCASSERTEQUAL(threadDim.children.size(), 3);
reflection->DispatchThreadsDimension[0] = getival<uint32_t>(threadDim.children[0]);
reflection->DispatchThreadsDimension[1] = getival<uint32_t>(threadDim.children[1]);
reflection->DispatchThreadsDimension[2] = getival<uint32_t>(threadDim.children[2]);
return;
}
else if(shaderTypeTag == ShaderEntryTag::Mesh)
{
Metadata &meshData = *tags.children[t + 1];
Metadata &threadDim = *meshData.children[0];
RDCASSERTEQUAL(threadDim.children.size(), 3);
reflection->DispatchThreadsDimension[0] = getival<uint32_t>(threadDim.children[0]);
reflection->DispatchThreadsDimension[1] = getival<uint32_t>(threadDim.children[1]);
reflection->DispatchThreadsDimension[2] = getival<uint32_t>(threadDim.children[2]);
return;
}
}
break;
}
}
RDCERR("Couldn't find thread dimension tag in shader");
reflection->DispatchThreadsDimension[0] = 1;
reflection->DispatchThreadsDimension[1] = 1;
reflection->DispatchThreadsDimension[2] = 1;
}
void Program::FillRayPayloads(
Program *executable,
rdcflatmap<ShaderEntryPoint, rdcpair<DXBC::CBufferVariableType, DXBC::CBufferVariableType>> &rayPayloads)
{
if(m_Type != DXBC::ShaderType::Library)
return;
DXMeta dx(m_NamedMeta);
TypeInfo typeInfo(dx.typeAnnotations);
if(dx.entryPoints)
{
for(Metadata *entry : dx.entryPoints->children)
{
if(entry->children.size() > 2 && entry->children[0] != NULL)
{
ShaderEntryPoint entryPoint;
entryPoint.name = entry->children[1]->str;
Metadata *tags = entry->children[4];
for(size_t i = 0; i < tags->children.size(); i += 2)
{
// 8 is the type tag
if(getival<uint32_t>(tags->children[i]) == 8U)
{
entryPoint.stage =
GetShaderStage((DXBC::ShaderType)getival<uint32_t>(tags->children[i + 1]));
break;
}
}
Function *ownFunc = cast<Function>(entry->children[0]->value);
Function *executableFunc = NULL;
// locate the function in the executable program so we can iterate instructions.
for(Function *f : executable->m_Functions)
{
// assume names will match
if(f->name == ownFunc->name)
{
executableFunc = f;
break;
}
}
// intersection shaders only report attributes, they do not access the ray payload
if(entryPoint.stage == ShaderStage::Intersection)
{
// find the reportHit and grab the type from that
for(const Instruction *in : executableFunc->instructions)
{
const Instruction &inst = *in;
if(inst.op == Operation::Call && inst.getFuncCall()->name.beginsWith("dx.op.reportHit"))
{
if(inst.args.size() != 4)
{
RDCERR("Unexpected number of arguments to reportHit");
continue;
}
const Type *executableAttrType = inst.args[3]->type;
if(!executableAttrType)
{
RDCERR("Unexpected untyped payload argument to reportHit");
continue;
}
RDCASSERT(executableAttrType->type == Type::Pointer);
executableAttrType = (Type *)executableAttrType->inner;
Type *ownAttrType = NULL;
// we have the executable type but we can't use that to look up our type info. Try to
// go back by name
for(Type *t : m_Types)
{
if(t->type == executableAttrType->type && t->name == executableAttrType->name)
{
ownAttrType = t;
break;
}
}
if(ownAttrType)
rayPayloads[entryPoint].second = MakePayloadType(typeInfo, ownAttrType);
else
RDCERR("Couldn't find matching attribute type for '%s' by name",
executableAttrType->name.c_str());
break;
}
}
}
// raygen shaders only use the ray payload, not attributes
else if(entryPoint.stage == ShaderStage::RayGen)
{
// find the reportHit and grab the type from that
for(const Instruction *in : executableFunc->instructions)
{
const Instruction &inst = *in;
if(inst.op == Operation::Call && inst.getFuncCall()->name.beginsWith("dx.op.traceRay"))
{
if(inst.args.size() != 16)
{
RDCERR("Unexpected number of arguments to traceRay");
continue;
}
const Type *executablePayloadType = inst.args[15]->type;
if(!executablePayloadType)
{
RDCERR("Unexpected untyped payload argument to traceRay");
continue;
}
RDCASSERT(executablePayloadType->type == Type::Pointer);
executablePayloadType = (Type *)executablePayloadType->inner;
Type *ownPayloadType = NULL;
// we have the executable type but we can't use that to look up our type info. Try to
// go back by name
for(Type *t : m_Types)
{
if(t->type == executablePayloadType->type && t->name == executablePayloadType->name)
{
ownPayloadType = t;
break;
}
}
if(ownPayloadType)
rayPayloads[entryPoint].first = MakePayloadType(typeInfo, ownPayloadType);
else
RDCERR("Couldn't find matching payload type for '%s' by name",
executablePayloadType->name.c_str());
break;
}
}
}
else if(entryPoint.stage == ShaderStage::Miss || entryPoint.stage == ShaderStage::AnyHit ||
entryPoint.stage == ShaderStage::ClosestHit)
{
const Type *payloadType = ownFunc->type->members[0];
RDCASSERT(payloadType->type == Type::Pointer);
payloadType = (Type *)payloadType->inner;
rdcpair<DXBC::CBufferVariableType, DXBC::CBufferVariableType> &dst =
rayPayloads[entryPoint];
// miss shaders only use the payload, any-hit and closest-hit use both. The first
// parameter is the payload, the second is the attributes
dst.first = MakePayloadType(typeInfo, payloadType);
if(entryPoint.stage != ShaderStage::Miss)
{
const Type *attrType = ownFunc->type->members[1];
RDCASSERT(attrType->type == Type::Pointer);
attrType = (Type *)attrType->inner;
dst.second = MakePayloadType(typeInfo, attrType);
}
}
}
}
}
}
D3D_PRIMITIVE_TOPOLOGY Program::GetOutputTopology()
{
if(m_Type != DXBC::ShaderType::Geometry && m_Type != DXBC::ShaderType::Domain &&
m_Type != DXBC::ShaderType::Mesh)
return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
for(size_t i = 0; i < m_NamedMeta.size(); i++)
{
const NamedMetadata &m = *m_NamedMeta[i];
if(m.name == "dx.entryPoints")
{
// expect only one child for this, DX doesn't support multiple entry points for compute
// shaders
RDCASSERTEQUAL(m.children.size(), 1);
Metadata &entry = *m.children[0];
RDCASSERTEQUAL(entry.children.size(), 5);
Metadata &tags = *entry.children[4];
for(size_t t = 0; t < tags.children.size(); t += 2)
{
RDCASSERT(tags.children[t]->isConstant);
if(getival<ShaderEntryTag>(tags.children[t]) == ShaderEntryTag::Geometry)
{
Metadata &geomData = *tags.children[t + 1];
RDCASSERTEQUAL(geomData.children.size(), 5);
return getival<D3D_PRIMITIVE_TOPOLOGY>(geomData.children[3]);
}
else if(getival<ShaderEntryTag>(tags.children[t]) == ShaderEntryTag::Domain)
{
Metadata &domainData = *tags.children[t + 1];
RDCASSERTEQUAL(domainData.children.size(), 2);
// 1 for isoline, 2 for tri and 3 for quad (which outputs tris)
return getival<uint32_t>(domainData.children[0]) == 1
? D3D_PRIMITIVE_TOPOLOGY_LINELIST
: D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
}
else if(getival<ShaderEntryTag>(tags.children[t]) == ShaderEntryTag::Mesh)
{
Metadata &meshData = *tags.children[t + 1];
RDCASSERTEQUAL(meshData.children.size(), 5);
// 1 for lines, 2 for tris
return getival<uint32_t>(meshData.children[3]) == 1 ? D3D_PRIMITIVE_TOPOLOGY_LINELIST
: D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
}
}
break;
}
}
RDCERR("Couldn't find topology tag in shader");
return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
}
// a struct is empty if it has no members, or all members are empty structs
static bool IsEmptyStruct(const Type *t)
{
// base case - a non-struct is defined as 'non-empty' to propagate up
if(t->type != Type::Struct)
return false;
// for structs, no members is a trivial empty struct
if(t->members.empty())
return true;
// now recurse.
// is any member a non-empty struct? if so this is also a non-empty struct
for(const Type *m : t->members)
if(!IsEmptyStruct(m))
return false;
// no members are non-empty => all members are empty => this is empty
return true;
}
VarType VarTypeForComponentType(ComponentType compType)
{
VarType varType;
switch(compType)
{
default:
case ComponentType::Invalid:
varType = VarType::Unknown;
RDCERR("Unexpected type in cbuffer annotations");
break;
case ComponentType::I1: varType = VarType::Bool; break;
case ComponentType::I16: varType = VarType::SShort; break;
case ComponentType::U16: varType = VarType::UShort; break;
case ComponentType::I32: varType = VarType::SInt; break;
case ComponentType::U32: varType = VarType::UInt; break;
case ComponentType::I64: varType = VarType::SLong; break;
case ComponentType::U64: varType = VarType::ULong; break;
case ComponentType::F16: varType = VarType::Half; break;
case ComponentType::F32: varType = VarType::Float; break;
case ComponentType::F64: varType = VarType::Double; break;
case ComponentType::SNormF16:
case ComponentType::UNormF16: varType = VarType::Half; break;
case ComponentType::SNormF32:
case ComponentType::UNormF32: varType = VarType::Float; break;
case ComponentType::SNormF64:
case ComponentType::UNormF64: varType = VarType::Double; break;
}
return varType;
}
static DXBC::CBufferVariableType MakeCBufferVariableType(const TypeInfo &typeInfo, const Type *t)
{
using namespace DXBC;
CBufferVariableType ret = {};
ret.elements = 1;
if(t->type == Type::Scalar || t->type == Type::Vector)
{
ret.rows = ret.cols = 1;
if(t->type == Type::Vector)
ret.cols = t->elemCount;
ret.bytesize = (t->bitWidth / 8) * ret.cols;
ret.varClass = CLASS_SCALAR;
if(t->scalarType == Type::Float)
{
if(t->bitWidth > 32)
ret.varType = VarType::Double;
else if(t->bitWidth == 16)
ret.varType = VarType::Half;
else
ret.varType = VarType::Float;
}
else
{
// can't distinguish int/uint here, default to signed
if(t->bitWidth > 32)
ret.varType = VarType::SLong;
else if(t->bitWidth == 32)
ret.varType = VarType::SInt;
else if(t->bitWidth == 16)
ret.varType = VarType::SShort;
else if(t->bitWidth == 8)
ret.varType = VarType::SByte;
else if(t->bitWidth == 1)
ret.varType = VarType::Bool;
}
ret.name = ToStr(ret.varType);
if(t->type == Type::Vector)
ret.name += ToStr(ret.cols);
return ret;
}
else if(t->type == Type::Array)
{
ret = MakeCBufferVariableType(typeInfo, t->inner);
ret.elements *= RDCMAX(1U, t->elemCount);
// assume normal D3D array packing with each element on float4 boundary
ret.bytesize += (ret.elements - 1) * AlignUp16(ret.bytesize);
return ret;
}
else if(t->type == Type::Struct)
{
// processing below
}
else
{
RDCERR("Unexpected type %u iterating cbuffer variable type %s", t->type, t->name.c_str());
return ret;
}
ret.name = t->name;
ret.varType = VarType::Unknown;
ret.varClass = CLASS_STRUCT;
char alignmentPrefix[] = "dx.alignment.legacy.";
if(ret.name.beginsWith(alignmentPrefix))
ret.name.erase(0, sizeof(alignmentPrefix) - 1);
char hostlayoutPrefix[] = "hostlayout.";
if(ret.name.beginsWith(hostlayoutPrefix))
ret.name.erase(0, sizeof(hostlayoutPrefix) - 1);
char structPrefix[] = "struct.";
if(ret.name.beginsWith(structPrefix))
ret.name.erase(0, sizeof(structPrefix) - 1);
char classPrefix[] = "class.";
if(ret.name.beginsWith(classPrefix))
ret.name.erase(0, sizeof(classPrefix) - 1);
// if this is an empty struct (including recursion), return straight away
if(IsEmptyStruct(t))
return ret;
// textures declared in a struct that becomes a global uniform could end up here, treat it as an empty struct.
if(ret.name.beginsWith("Texture2D<"))
return ret;
auto it = typeInfo.structData.find(t);
if(it == typeInfo.structData.end())
{
// shouldn't get here if we don't have type information at all
RDCERR("Couldn't find type information for struct '%s'!", t->name.c_str());
return ret;
}
bool structured = false;
if(ret.name.contains("StructuredBuffer<"))
{
structured = true;
if(t->members.size() != 1 || it->second.members.size() != 1 || it->second.members[0].name != "h")
{
RDCWARN("Structured buffer declaration found but expected single inner handle");
// otherwise use it as-is and trim off the name in an attempt to make it look normal
ret.name.trim();
// remove any outer definition of the type
if(ret.name.back() == '>')
ret.name.pop_back();
else
RDCERR("Expected closing > in StructuredBuffer type name");
int idx = ret.name.indexOf('<');
ret.name.erase(0, idx + 1);
}
}
for(size_t i = 0; i < t->members.size(); i++)
{
CBufferVariable var;
var.name = it->second.members[i].name;
var.offset = it->second.members[i].offset;
const Type *inner = t->members[i];
// unpeel any arrays
while(inner->type == Type::Array)
inner = inner->inner;
if(var.type.members.empty() &&
(inner->type != Type::Struct || (it->second.members[i].flags & TypeInfo::MemberData::Matrix)))
{
switch(it->second.members[i].type)
{
case ComponentType::Invalid:
case ComponentType::SNormF16:
case ComponentType::UNormF16:
case ComponentType::SNormF32:
case ComponentType::UNormF32:
case ComponentType::SNormF64:
case ComponentType::UNormF64: RDCERR("Unexpected type in cbuffer annotations"); break;
default: break;
}
var.type.varType = VarTypeForComponentType(it->second.members[i].type);
}
if(it->second.members[i].flags & TypeInfo::MemberData::Matrix)
{
var.type.elements = 1;
const Type *matType = t->members[i];
// unpeel any arrays that aren't the last one (that's the matrix array)
while(matType->type == Type::Array && matType->inner->type == Type::Array)
{
var.type.elements *= matType->elemCount;
matType = matType->inner;
}
RDCASSERT(var.type.varType != VarType::Unknown);
var.type.rows = it->second.members[i].rows;
var.type.cols = it->second.members[i].cols;
var.type.varClass = (it->second.members[i].flags & TypeInfo::MemberData::RowMajor)
? CLASS_MATRIX_ROWS
: CLASS_MATRIX_COLUMNS;
var.type.name = ToStr(var.type.varType);
var.type.name += ToStr(var.type.rows);
var.type.name += "x";
var.type.name += ToStr(var.type.cols);
// D3D matrices in cbuffers always take up a float4 per row/column.
uint32_t matrixByteStride = AlignUp16(VarTypeByteSize(var.type.varType));
if(var.type.varClass == CLASS_MATRIX_ROWS)
matrixByteStride *= var.type.rows;
else
matrixByteStride *= var.type.cols;
var.type.bytesize = matrixByteStride * var.type.elements;
}
else
{
var.type = MakeCBufferVariableType(typeInfo, t->members[i]);
}
ret.bytesize = var.offset + var.type.bytesize;
ret.members.push_back(var);
}
// silently go into the inner member that's declared in this type as we only care about
// reflecting that actual structure
if(structured && t->members.size() == 1 && it->second.members.size() == 1 &&
it->second.members[0].name == "h")
return ret.members[0].type;
return ret;
}
static void AddResourceBind(DXBC::Reflection *refl, const TypeInfo &typeInfo, const Metadata *r,
const bool srv)
{
using namespace DXBC;
ShaderInputBind bind;
bind.name = r->children[(size_t)ResField::Name]->str;
bind.type = ShaderInputBind::TYPE_TEXTURE;
bind.space = getival<uint32_t>(r->children[(size_t)ResField::Space]);
bind.reg = getival<uint32_t>(r->children[(size_t)ResField::RegBase]);
bind.bindCount = getival<uint32_t>(r->children[(size_t)ResField::RegCount]);
bind.retType = RETURN_TYPE_UNKNOWN;
bind.numComps = 1;
const Type *resType = r->children[(size_t)ResField::VarDecl]->type;
const Type *baseType = resType;
// variable should be a pointer to the underlying type
RDCASSERT(resType->type == Type::Pointer);
resType = resType->inner;
// textures are a struct containing the inner type and a mips type
if(resType->type == Type::Struct && !resType->members.empty())
resType = resType->members[0];
// if we found a vector go further to get the underlying type
if(resType->type == Type::Vector)
{
bind.numComps = resType->elemCount;
resType = resType->inner;
}
if(resType->type == Type::Scalar)
{
if(resType->scalarType == Type::Float)
bind.retType = resType->bitWidth > 32 ? RETURN_TYPE_DOUBLE : RETURN_TYPE_FLOAT;
// can't distinguish sign bit here, hope we have metadata to set this exactly
else if(resType->scalarType == Type::Int)
bind.retType = RETURN_TYPE_SINT;
}
const Metadata *tags =
srv ? r->children[(size_t)ResField::SRVTags] : r->children[(size_t)ResField::UAVTags];
uint32_t structStride = 0;
for(size_t t = 0; tags && t < tags->children.size(); t += 2)
{
RDCASSERT(tags->children[t]->isConstant);
if(getival<ResourcesTag>(tags->children[t]) == ResourcesTag::StructStride)
{
structStride = getival<uint32_t>(tags->children[t + 1]);
}
else if(getival<ResourcesTag>(tags->children[t]) == ResourcesTag::ElementType)
{
switch(getival<ComponentType>(tags->children[t + 1]))
{
default:
case ComponentType::Invalid:
case ComponentType::I1: bind.retType = RETURN_TYPE_UNKNOWN; break;
case ComponentType::I16:
case ComponentType::I32:
case ComponentType::I64: bind.retType = RETURN_TYPE_SINT; break;
case ComponentType::U16:
case ComponentType::U32:
case ComponentType::U64: bind.retType = RETURN_TYPE_UINT; break;
case ComponentType::F16:
case ComponentType::F32: bind.retType = RETURN_TYPE_FLOAT; break;
case ComponentType::F64: bind.retType = RETURN_TYPE_DOUBLE; break;
case ComponentType::SNormF16:
case ComponentType::SNormF32:
case ComponentType::SNormF64: bind.retType = RETURN_TYPE_SNORM; break;
case ComponentType::UNormF16:
case ComponentType::UNormF32:
case ComponentType::UNormF64: bind.retType = RETURN_TYPE_UNORM; break;
}
}
}
ResourceKind shape = srv ? getival<ResourceKind>(r->children[(size_t)ResField::SRVShape])
: getival<ResourceKind>(r->children[(size_t)ResField::UAVShape]);
rdcstr defName;
switch(shape)
{
case ResourceKind::Unknown:
case ResourceKind::SamplerComparison:
case ResourceKind::CBuffer:
case ResourceKind::Sampler:
RDCERR("Unexpected %s shape %u", srv ? "SRV" : "UAV", shape);
defName = srv ? "SRV" : "UAV";
break;
case ResourceKind::RTAccelerationStructure:
bind.type = ShaderInputBind::TYPE_RTAS;
defName = "RaytracingAccelerationStructure";
bind.dimension = ShaderInputBind::DIM_RTAS;
break;
case ResourceKind::Texture1D:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture1D" : "RWTexture1D";
bind.dimension = ShaderInputBind::DIM_TEXTURE1D;
break;
case ResourceKind::FeedbackTexture2D:
// fallthrough, resource type unhandled right now
case ResourceKind::Texture2D:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture2D" : "RWTexture2D";
bind.dimension = ShaderInputBind::DIM_TEXTURE2D;
break;
case ResourceKind::Texture2DMS:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture2DMS" : "RWTexture2DMS";
bind.dimension = ShaderInputBind::DIM_TEXTURE2DMS;
break;
case ResourceKind::Texture3D:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture3D" : "RWTexture3D";
bind.dimension = ShaderInputBind::DIM_TEXTURE3D;
break;
case ResourceKind::TextureCube:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "TextureCube" : "RWTextureCube";
bind.dimension = ShaderInputBind::DIM_TEXTURECUBE;
break;
case ResourceKind::Texture1DArray:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture1DArray" : "RWTexture1DArray";
bind.dimension = ShaderInputBind::DIM_TEXTURE1DARRAY;
break;
case ResourceKind::FeedbackTexture2DArray:
// fallthrough, resource type unhandled right now
case ResourceKind::Texture2DArray:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture2DArray" : "RWTexture2DArray";
bind.dimension = ShaderInputBind::DIM_TEXTURE2DARRAY;
break;
case ResourceKind::Texture2DMSArray:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Texture2DMSArray" : "RWTexture2DMSArray";
bind.dimension = ShaderInputBind::DIM_TEXTURE2DMSARRAY;
break;
case ResourceKind::TextureCubeArray:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "TextureCubeArray" : "RWTextureCubeArray";
bind.dimension = ShaderInputBind::DIM_TEXTURECUBEARRAY;
break;
case ResourceKind::TypedBuffer:
bind.type = srv ? ShaderInputBind::TYPE_TEXTURE : ShaderInputBind::TYPE_UAV_RWTYPED;
defName = srv ? "Buffer" : "RWBuffer";
bind.dimension = ShaderInputBind::DIM_BUFFER;
break;
case ResourceKind::TBuffer:
bind.type = ShaderInputBind::TYPE_TBUFFER;
defName = "TBuffer";
bind.dimension = ShaderInputBind::DIM_UNKNOWN;
bind.retType = RETURN_TYPE_UNKNOWN;
break;
case ResourceKind::RawBuffer:
bind.type = srv ? ShaderInputBind::TYPE_BYTEADDRESS : ShaderInputBind::TYPE_UAV_RWBYTEADDRESS;
defName = srv ? "ByteAddressBuffer" : "RWByteAddressBuffer";
bind.dimension = ShaderInputBind::DIM_BUFFER;
bind.retType = RETURN_TYPE_MIXED;
break;
case ResourceKind::StructuredBuffer:
bind.type = srv ? ShaderInputBind::TYPE_STRUCTURED : ShaderInputBind::TYPE_UAV_RWSTRUCTURED;
defName = srv ? "StructuredBuffer" : "RWStructuredBuffer";
bind.dimension = ShaderInputBind::DIM_BUFFER;
bind.retType = RETURN_TYPE_MIXED;
break;
case ResourceKind::StructuredBufferWithCounter:
bind.type = srv ? ShaderInputBind::TYPE_STRUCTURED
: ShaderInputBind::TYPE_UAV_RWSTRUCTURED_WITH_COUNTER;
defName = srv ? "StructuredBufferWithCounter" : "RWStructuredBufferWithCounter";
bind.dimension = ShaderInputBind::DIM_BUFFER;
bind.retType = RETURN_TYPE_MIXED;
break;
}
if(bind.type == ShaderInputBind::TYPE_UAV_RWSTRUCTURED)
{
if(getival<uint32_t>(r->children[(size_t)ResField::UAVHiddenCounter]) != 0)
bind.type = ShaderInputBind::TYPE_UAV_RWSTRUCTURED_WITH_COUNTER;
}
if(bind.name.empty())
bind.name = StringFormat::Fmt("%s%u", defName.c_str(),
getival<uint32_t>(r->children[(size_t)ResField::ID]));
switch(shape)
{
case ResourceKind::StructuredBuffer:
case ResourceKind::StructuredBufferWithCounter:
{
if(!typeInfo.structData.empty())
{
refl->ResourceBinds[bind.name] = MakeCBufferVariableType(typeInfo, baseType->inner);
}
else
{
// if we don't have type annotations, create a dummy byte-array struct member
refl->ResourceBinds[bind.name].bytesize = structStride;
refl->ResourceBinds[bind.name].cols = 1;
refl->ResourceBinds[bind.name].rows = 1;
refl->ResourceBinds[bind.name].elements = structStride;
refl->ResourceBinds[bind.name].varClass = DXBC::CLASS_SCALAR;
refl->ResourceBinds[bind.name].varType = VarType::UByte;
}
}
default: break;
}
if(srv)
refl->SRVs.push_back(bind);
else
refl->UAVs.push_back(bind);
}
rdcarray<ShaderEntryPoint> Program::GetEntryPoints()
{
rdcarray<ShaderEntryPoint> ret;
DXMeta dx(m_NamedMeta);
if(dx.entryPoints)
{
for(Metadata *entry : dx.entryPoints->children)
{
if(entry->children.size() > 2 && entry->children[0] != NULL)
{
ShaderEntryPoint entryPoint;
entryPoint.name = entry->children[1]->str;
Metadata *tags = entry->children[4];
if(tags)
{
for(size_t i = 0; i < tags->children.size(); i += 2)
{
// 8 is the type tag
if(getival<uint32_t>(tags->children[i]) == 8U)
{
entryPoint.stage =
GetShaderStage((DXBC::ShaderType)getival<uint32_t>(tags->children[i + 1]));
break;
}
}
}
ret.push_back(entryPoint);
}
}
}
return ret;
}
void Program::FetchEntryPoint()
{
if(m_EntryPoint.empty())
{
DXMeta dx(m_NamedMeta);
if(dx.entryPoints && dx.entryPoints->children.size() > 0 &&
dx.entryPoints->children[0]->children.size() > 2)
{
m_EntryPoint = dx.entryPoints->children[0]->children[1]->str;
}
else
{
RDCERR("Didn't find dx.entryPoints");
m_EntryPoint = "main";
}
}
}
DXBC::Reflection *Program::BuildReflection()
{
const bool dxcStyleFormatting = m_DXCStyle;
using namespace DXBC;
Reflection *refl = new Reflection;
Files.clear();
m_CompileFlags.flags.clear();
DXMeta dx(m_NamedMeta);
TypeInfo typeInfo(dx.typeAnnotations);
if(dx.ident && dx.ident->children.size() == 1 && dx.ident->children[0]->children.size() == 1)
{
m_CompilerSig = "dxc - " + dx.ident->children[0]->children[0]->str;
}
else
{
m_CompilerSig = "dxc - unknown version";
}
if(dx.valver && dx.valver->children.size() == 1 && dx.valver->children[0]->children.size() == 2)
{
m_CompilerSig += StringFormat::Fmt(
" (Validation version %s.%s)",
dx.valver->children[0]->children[0]->value->toString(dxcStyleFormatting).c_str(),
dx.valver->children[0]->children[1]->value->toString(dxcStyleFormatting).c_str());
}
if(dx.entryPoints && dx.entryPoints->children.size() > 0 &&
dx.entryPoints->children[0]->children.size() > 2)
{
m_EntryPoint = dx.entryPoints->children[0]->children[1]->str;
}
else
{
RDCERR("Didn't find dx.entryPoints");
m_EntryPoint = "main";
}
if(dx.shaderModel && dx.shaderModel->children.size() == 1 &&
dx.shaderModel->children[0]->children.size() == 3)
{
m_Profile = StringFormat::Fmt(
"%s_%s_%s", dx.shaderModel->children[0]->children[0]->str.c_str(),
dx.shaderModel->children[0]->children[1]->value->toString(dxcStyleFormatting).c_str(),
dx.shaderModel->children[0]->children[2]->value->toString(dxcStyleFormatting).c_str());
}
else
{
switch(m_Type)
{
case DXBC::ShaderType::Pixel: m_Profile = "ps"; break;
case DXBC::ShaderType::Vertex: m_Profile = "vs"; break;
case DXBC::ShaderType::Geometry: m_Profile = "gs"; break;
case DXBC::ShaderType::Hull: m_Profile = "hs"; break;
case DXBC::ShaderType::Domain: m_Profile = "ds"; break;
case DXBC::ShaderType::Compute: m_Profile = "cs"; break;
default: m_Profile = "xx"; break;
}
m_Profile += StringFormat::Fmt("_%u_%u", m_Major, m_Minor);
}
if(dx.source.contents)
{
for(const Metadata *f : dx.source.contents->children)
{
if(f->children.size() != 2)
continue;
bool found = false;
rdcstr shaderFilePath = standardise_directory_separator(f->children[0]->str);
for(const ShaderSourceFile &shaderSource : Files)
{
if(shaderSource.filename == shaderFilePath)
{
found = true;
break;
}
}
if(!found)
Files.push_back({shaderFilePath, f->children[1]->str});
}
// push the main filename to the front
if(dx.source.mainFileName && !dx.source.mainFileName->children.empty())
{
rdcstr mainFile = standardise_directory_separator(dx.source.mainFileName->children[0]->str);
if(!mainFile.empty())
{
for(size_t i = 1; i < Files.size(); i++)
{
if(Files[i].filename == mainFile)
{
std::swap(Files[0], Files[i]);
break;
}
}
}
}
if(!Files.empty())
m_CompileFlags.flags.push_back({"preferSourceDebug", "1"});
}
if(dx.source.args && dx.source.args->children.size() == 1)
{
rdcstr cmdline;
for(const Metadata *f : dx.source.args->children[0]->children)
{
rdcstr param = f->str;
param.trim();
if(param.find_first_of(" \t\r\n") >= 0)
{
cmdline += " \"";
for(char c : param)
{
if(c == '"')
cmdline.push_back('\\');
cmdline.push_back(c);
}
cmdline += "\"";
}
else
{
cmdline += " " + param;
}
}
m_CompileFlags.flags.push_back({"@cmdline", cmdline});
}
else
{
m_CompileFlags.flags.push_back({"@cmdline", "-T " + m_Profile});
}
if(dx.resources)
{
RDCASSERTEQUAL(dx.resources->children.size(), 1);
const Metadata *resList = dx.resources->children[0];
RDCASSERTEQUAL(resList->children.size(), 4);
const Metadata *SRVs = resList->children[0];
if(SRVs)
{
for(const Metadata *r : SRVs->children)
{
AddResourceBind(refl, typeInfo, r, true);
}
}
const Metadata *UAVs = resList->children[1];
if(UAVs)
{
for(const Metadata *r : UAVs->children)
{
AddResourceBind(refl, typeInfo, r, false);
}
}
const Metadata *CBVs = resList->children[2];
if(CBVs)
{
for(const Metadata *r : CBVs->children)
{
CBuffer bind;
bind.name = r->children[(size_t)ResField::Name]->str;
bind.identifier = getival<uint32_t>(r->children[(size_t)ResField::ID]);
bind.space = getival<uint32_t>(r->children[(size_t)ResField::Space]);
bind.reg = getival<uint32_t>(r->children[(size_t)ResField::RegBase]);
bind.bindCount = getival<uint32_t>(r->children[(size_t)ResField::RegCount]);
bind.descriptor.type = CBuffer::Descriptor::TYPE_CBUFFER;
bind.descriptor.byteSize = getival<uint32_t>(r->children[(size_t)ResField::CBufferByteSize]);
bind.hasReflectionData = true;
if(bind.name.empty())
{
bind.hasReflectionData = false;
bind.name = StringFormat::Fmt("cbuffer%u", bind.identifier);
}
const Type *cbufType = r->children[(size_t)ResField::VarDecl]->type;
// variable should be a pointer to the cbuffer type
RDCASSERT(cbufType->type == Type::Pointer);
cbufType = cbufType->inner;
if(!typeInfo.structData.empty())
{
CBufferVariableType rootType = MakeCBufferVariableType(typeInfo, cbufType);
bind.variables.swap(rootType.members);
}
else
{
bind.hasReflectionData = false;
CBufferVariable var;
var.name = "unknown";
var.offset = 0;
// if we don't have type annotations, create a dummy struct member
var.type.bytesize = bind.descriptor.byteSize / 16;
var.type.cols = 4;
var.type.rows = 1;
var.type.elements = bind.descriptor.byteSize / 16;
var.type.varClass = DXBC::CLASS_SCALAR;
var.type.varType = VarType::UInt;
uint32_t remainingBytes = var.type.bytesize * 16;
bind.variables.push_back(var);
// add any remaining bytes if the struct isn't a multiple of float4 size
var.type.cols = 1;
var.type.bytesize = var.type.elements = 1;
var.type.varType = VarType::UByte;
for(; remainingBytes < bind.descriptor.byteSize; remainingBytes++)
{
bind.variables.push_back(var);
}
}
refl->CBuffers.push_back(bind);
}
}
const Metadata *Samplers = resList->children[3];
if(Samplers)
{
for(const Metadata *r : Samplers->children)
{
ShaderInputBind bind;
bind.name = r->children[(size_t)ResField::Name]->str;
bind.space = getival<uint32_t>(r->children[(size_t)ResField::Space]);
bind.reg = getival<uint32_t>(r->children[(size_t)ResField::RegBase]);
bind.bindCount = getival<uint32_t>(r->children[(size_t)ResField::RegCount]);
bind.type = ShaderInputBind::TYPE_SAMPLER;
bind.dimension = ShaderInputBind::DIM_UNKNOWN;
bind.numComps = 0;
bind.retType = RETURN_TYPE_UNKNOWN;
if(bind.name.empty())
bind.name =
StringFormat::Fmt("sampler%u", getival<uint32_t>(r->children[(size_t)ResField::ID]));
refl->Samplers.push_back(bind);
}
}
}
RDCEraseEl(refl->Interfaces);
RDCEraseEl(refl->DispatchThreadsDimension);
return refl;
}
rdcstr Program::GetDebugStatus()
{
if(!D3D12_Experimental_EnableDXILShaderDebugging())
return "Debugging DXIL is not supported";
if((m_Type != DXBC::ShaderType::Vertex) && (m_Type != DXBC::ShaderType::Compute) &&
(m_Type != DXBC::ShaderType::Pixel))
return "Only DXIL Vertex, Pixel and Compute shaders are supported for debugging";
for(size_t i = 0; i < m_Functions.size(); i++)
{
const Function &f = *m_Functions[i];
// Only support "dx.op" external functions
if(f.external)
{
if(!f.name.beginsWith("dx.op.") && !f.name.beginsWith("llvm.dbg."))
return StringFormat::Fmt("Unsupported external function '%s'", f.name.c_str());
}
for(const Instruction *inst : f.instructions)
{
switch(inst->op)
{
case Operation::FPTrunc:
case Operation::FPExt:
case Operation::PtrToI:
case Operation::IToPtr:
case Operation::AddrSpaceCast:
case Operation::FRem:
case Operation::UDiv:
case Operation::SDiv:
case Operation::URem:
case Operation::SRem:
case Operation::FOrdFalse:
case Operation::FOrdGreater:
case Operation::FOrdGreaterEqual:
case Operation::FOrdLess:
case Operation::FOrdLessEqual:
case Operation::FOrd:
case Operation::FUnord:
case Operation::FUnordEqual:
case Operation::FUnordGreater:
case Operation::FUnordGreaterEqual:
case Operation::FUnordLess:
case Operation::FUnordLessEqual:
case Operation::FUnordNotEqual:
case Operation::FOrdTrue:
case Operation::UGreater:
case Operation::UGreaterEqual:
case Operation::ULess:
case Operation::ULessEqual:
case Operation::SGreater:
case Operation::SGreaterEqual:
case Operation::SLess:
case Operation::SLessEqual:
case Operation::ExtractElement:
case Operation::InsertElement:
case Operation::ShuffleVector:
case Operation::InsertValue:
case Operation::Switch:
case Operation::Fence:
case Operation::CompareExchange:
case Operation::LoadAtomic:
case Operation::StoreAtomic:
case Operation::AtomicExchange:
case Operation::AtomicAdd:
case Operation::AtomicSub:
case Operation::AtomicAnd:
case Operation::AtomicNand:
case Operation::AtomicOr:
case Operation::AtomicXor:
case Operation::AtomicMax:
case Operation::AtomicMin:
case Operation::AtomicUMax:
case Operation::AtomicUMin:
return StringFormat::Fmt("Unsupported instruction '%s'", ToStr(inst->op).c_str());
case Operation::Call:
{
const Function *callFunc = inst->getFuncCall();
rdcstr funcCallName = inst->getFuncCall()->name;
if(funcCallName.beginsWith("dx.op."))
{
DXOp dxOpCode = DXOp::NumOpCodes;
RDCASSERT(getival<DXOp>(inst->args[0], dxOpCode));
RDCASSERT(dxOpCode < DXOp::NumOpCodes, dxOpCode, DXOp::NumOpCodes);
switch(dxOpCode)
{
case DXOp::TempRegLoad:
case DXOp::TempRegStore:
case DXOp::MinPrecXRegLoad:
case DXOp::MinPrecXRegStore:
case DXOp::IsNaN:
case DXOp::IsInf:
case DXOp::IsFinite:
case DXOp::IsNormal:
case DXOp::Bfrev:
case DXOp::Countbits:
case DXOp::IMul:
case DXOp::UMul:
case DXOp::UDiv:
case DXOp::UAddc:
case DXOp::USubb:
case DXOp::Fma:
case DXOp::IMad:
case DXOp::UMad:
case DXOp::Msad:
case DXOp::Ibfe:
case DXOp::Ubfe:
case DXOp::Bfi:
case DXOp::CBufferLoad:
case DXOp::SampleBias:
case DXOp::SampleGrad:
case DXOp::SampleCmp:
case DXOp::BufferUpdateCounter:
case DXOp::CheckAccessFullyMapped:
case DXOp::TextureGather:
case DXOp::TextureGatherCmp:
case DXOp::AtomicBinOp:
case DXOp::AtomicCompareExchange:
case DXOp::Barrier:
case DXOp::CalculateLOD:
case DXOp::Discard:
case DXOp::DerivFineX:
case DXOp::DerivFineY:
case DXOp::EvalSnapped:
case DXOp::EvalSampleIndex:
case DXOp::EvalCentroid:
case DXOp::SampleIndex:
case DXOp::Coverage:
case DXOp::InnerCoverage:
case DXOp::EmitStream:
case DXOp::CutStream:
case DXOp::EmitThenCutStream:
case DXOp::GSInstanceID:
case DXOp::MakeDouble:
case DXOp::SplitDouble:
case DXOp::LoadOutputControlPoint:
case DXOp::LoadPatchConstant:
case DXOp::DomainLocation:
case DXOp::StorePatchConstant:
case DXOp::OutputControlPointID:
case DXOp::PrimitiveID:
case DXOp::CycleCounterLegacy:
case DXOp::WaveIsFirstLane:
case DXOp::WaveGetLaneIndex:
case DXOp::WaveGetLaneCount:
case DXOp::WaveAnyTrue:
case DXOp::WaveAllTrue:
case DXOp::WaveActiveAllEqual:
case DXOp::WaveActiveBallot:
case DXOp::WaveReadLaneAt:
case DXOp::WaveReadLaneFirst:
case DXOp::WaveActiveOp:
case DXOp::WaveActiveBit:
case DXOp::WavePrefixOp:
case DXOp::QuadReadLaneAt:
case DXOp::QuadOp:
case DXOp::BitcastI16toF16:
case DXOp::BitcastF16toI16:
case DXOp::BitcastI32toF32:
case DXOp::BitcastF32toI32:
case DXOp::BitcastI64toF64:
case DXOp::BitcastF64toI64:
case DXOp::LegacyF32ToF16:
case DXOp::LegacyF16ToF32:
case DXOp::LegacyDoubleToFloat:
case DXOp::LegacyDoubleToSInt32:
case DXOp::LegacyDoubleToUInt32:
case DXOp::WaveAllBitCount:
case DXOp::WavePrefixBitCount:
case DXOp::AttributeAtVertex:
case DXOp::ViewID:
case DXOp::InstanceID:
case DXOp::InstanceIndex:
case DXOp::HitKind:
case DXOp::RayFlags:
case DXOp::DispatchRaysIndex:
case DXOp::DispatchRaysDimensions:
case DXOp::WorldRayOrigin:
case DXOp::WorldRayDirection:
case DXOp::ObjectRayOrigin:
case DXOp::ObjectRayDirection:
case DXOp::ObjectToWorld:
case DXOp::WorldToObject:
case DXOp::RayTMin:
case DXOp::RayTCurrent:
case DXOp::IgnoreHit:
case DXOp::AcceptHitAndEndSearch:
case DXOp::TraceRay:
case DXOp::ReportHit:
case DXOp::CallShader:
case DXOp::CreateHandleForLib:
case DXOp::PrimitiveIndex:
case DXOp::Dot2AddHalf:
case DXOp::Dot4AddI8Packed:
case DXOp::Dot4AddU8Packed:
case DXOp::WaveMatch:
case DXOp::WaveMultiPrefixOp:
case DXOp::WaveMultiPrefixBitCount:
case DXOp::SetMeshOutputCounts:
case DXOp::EmitIndices:
case DXOp::GetMeshPayload:
case DXOp::StoreVertexOutput:
case DXOp::StorePrimitiveOutput:
case DXOp::DispatchMesh:
case DXOp::WriteSamplerFeedback:
case DXOp::WriteSamplerFeedbackBias:
case DXOp::WriteSamplerFeedbackLevel:
case DXOp::WriteSamplerFeedbackGrad:
case DXOp::AllocateRayQuery:
case DXOp::RayQuery_TraceRayInline:
case DXOp::RayQuery_Proceed:
case DXOp::RayQuery_Abort:
case DXOp::RayQuery_CommitNonOpaqueTriangleHit:
case DXOp::RayQuery_CommitProceduralPrimitiveHit:
case DXOp::RayQuery_CommittedStatus:
case DXOp::RayQuery_CandidateType:
case DXOp::RayQuery_CandidateObjectToWorld3x4:
case DXOp::RayQuery_CandidateWorldToObject3x4:
case DXOp::RayQuery_CommittedObjectToWorld3x4:
case DXOp::RayQuery_CommittedWorldToObject3x4:
case DXOp::RayQuery_CandidateProceduralPrimitiveNonOpaque:
case DXOp::RayQuery_CandidateTriangleFrontFace:
case DXOp::RayQuery_CommittedTriangleFrontFace:
case DXOp::RayQuery_CandidateTriangleBarycentrics:
case DXOp::RayQuery_CommittedTriangleBarycentrics:
case DXOp::RayQuery_RayFlags:
case DXOp::RayQuery_WorldRayOrigin:
case DXOp::RayQuery_WorldRayDirection:
case DXOp::RayQuery_RayTMin:
case DXOp::RayQuery_CandidateTriangleRayT:
case DXOp::RayQuery_CommittedRayT:
case DXOp::RayQuery_CandidateInstanceIndex:
case DXOp::RayQuery_CandidateInstanceID:
case DXOp::RayQuery_CandidateGeometryIndex:
case DXOp::RayQuery_CandidatePrimitiveIndex:
case DXOp::RayQuery_CandidateObjectRayOrigin:
case DXOp::RayQuery_CandidateObjectRayDirection:
case DXOp::RayQuery_CommittedInstanceIndex:
case DXOp::RayQuery_CommittedInstanceID:
case DXOp::RayQuery_CommittedGeometryIndex:
case DXOp::RayQuery_CommittedPrimitiveIndex:
case DXOp::RayQuery_CommittedObjectRayOrigin:
case DXOp::RayQuery_CommittedObjectRayDirection:
case DXOp::GeometryIndex:
case DXOp::RayQuery_CandidateInstanceContributionToHitGroupIndex:
case DXOp::RayQuery_CommittedInstanceContributionToHitGroupIndex:
case DXOp::CreateHandleFromHeap:
case DXOp::Unpack4x8:
case DXOp::Pack4x8:
case DXOp::IsHelperLane:
case DXOp::QuadVote:
case DXOp::TextureGatherRaw:
case DXOp::SampleCmpLevel:
case DXOp::TextureStoreSample:
case DXOp::WaveMatrix_Annotate:
case DXOp::WaveMatrix_Depth:
case DXOp::WaveMatrix_Fill:
case DXOp::WaveMatrix_LoadRawBuf:
case DXOp::WaveMatrix_LoadGroupShared:
case DXOp::WaveMatrix_StoreRawBuf:
case DXOp::WaveMatrix_StoreGroupShared:
case DXOp::WaveMatrix_Multiply:
case DXOp::WaveMatrix_MultiplyAccumulate:
case DXOp::WaveMatrix_ScalarOp:
case DXOp::WaveMatrix_SumAccumulate:
case DXOp::WaveMatrix_Add:
case DXOp::AllocateNodeOutputRecords:
case DXOp::GetNodeRecordPtr:
case DXOp::IncrementOutputCount:
case DXOp::OutputComplete:
case DXOp::GetInputRecordCount:
case DXOp::FinishedCrossGroupSharing:
case DXOp::BarrierByMemoryType:
case DXOp::BarrierByMemoryHandle:
case DXOp::BarrierByNodeRecordHandle:
case DXOp::CreateNodeOutputHandle:
case DXOp::IndexNodeHandle:
case DXOp::AnnotateNodeHandle:
case DXOp::CreateNodeInputRecordHandle:
case DXOp::AnnotateNodeRecordHandle:
case DXOp::NodeOutputIsValid:
case DXOp::GetRemainingRecursionLevels:
case DXOp::SampleCmpGrad:
case DXOp::SampleCmpBias:
case DXOp::StartVertexLocation:
case DXOp::StartInstanceLocation:
case DXOp::NumOpCodes:
return StringFormat::Fmt("Unsupported dx.op call `%s` %s", callFunc->name.c_str(),
ToStr(dxOpCode).c_str());
default: break;
}
break;
}
else if(funcCallName.beginsWith("llvm.dbg."))
{
break;
}
else
{
return StringFormat::Fmt("Unsupported function call '%s'", ToStr(callFunc->name).c_str());
}
break;
}
default: break;
}
}
}
// no unsupported instructions used
return rdcstr();
}
void Program::GetLineInfo(size_t instruction, uintptr_t offset, LineColumnInfo &lineInfo) const
{
bool getEntryPoint = (instruction == ~0U);
if(getEntryPoint)
RDCASSERT(!m_EntryPoint.empty());
lineInfo = LineColumnInfo();
for(size_t i = 0; i < m_Functions.size(); i++)
{
const Function &f = *m_Functions[i];
bool getLineInfo = (getEntryPoint) ? false : (instruction < f.instructions.size());
if(getEntryPoint)
{
if(f.name == m_EntryPoint)
{
getLineInfo = true;
instruction = 0;
}
}
if(getLineInfo)
{
const Instruction *const inst = f.instructions[instruction];
uint32_t dbgLoc = inst->debugLoc;
if(dbgLoc != ~0U)
{
const DebugLocation &debugLoc = m_DebugLocations[dbgLoc];
int32_t fileIndex = -1;
Metadata *scope = debugLoc.scope;
const DIBase *const dwarfInfo = scope->dwarf;
rdcstr shaderFilePath = standardise_directory_separator(GetDebugScopeFilePath(dwarfInfo));
RDCASSERT(!shaderFilePath.empty());
for(int32_t iFile = 0; iFile < Files.count(); iFile++)
{
rdcstr filePath = Files[iFile].filename;
if(filePath == shaderFilePath)
{
fileIndex = iFile;
break;
}
}
lineInfo.fileIndex = fileIndex;
lineInfo.lineStart = (uint32_t)debugLoc.line;
lineInfo.lineEnd = (uint32_t)debugLoc.line;
// Without column end data ignore the column start data in DebugLocation
lineInfo.colStart = 0;
lineInfo.colEnd = 0;
}
lineInfo.disassemblyLine = inst->disassemblyLine;
return;
}
instruction -= f.instructions.size();
}
}
void Program::GetCallstack(size_t instruction, uintptr_t offset, rdcarray<rdcstr> &callstack) const
{
callstack.clear();
}
bool Program::HasSourceMapping() const
{
// not yet implemented and only relevant for debugging
return false;
}
void Program::GetLocals(const DXBC::DXBCContainer *dxbc, size_t instruction, uintptr_t offset,
rdcarray<SourceVariableMapping> &locals) const
{
locals.clear();
for(const LocalSourceVariable &localVar : m_Locals)
{
if(localVar.startInst > instruction)
continue;
if(instruction > localVar.endInst)
continue;
locals.append(localVar.sourceVars);
}
}
const ResourceReference *Program::GetResourceReference(const rdcstr &handleStr) const
{
if(m_ResourceHandles.count(handleStr) > 0)
{
size_t resRefIndex = m_ResourceHandles.find(handleStr)->second;
if(resRefIndex < m_ResourceHandles.size())
{
return &m_ResourceReferences[resRefIndex];
}
}
return NULL;
}
size_t Program::GetInstructionCount() const
{
size_t ret = 0;
for(size_t i = 0; i < m_Functions.size(); i++)
ret += m_Functions[i]->instructions.size();
return ret;
}
}; // namespace DXIL