Files
renderdoc/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp
T

1832 lines
68 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021-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 "core/settings.h"
#include "driver/shaders/dxbc/dxbc_bytecode_editor.h"
#include "driver/shaders/dxil/dxil_bytecode_editor.h"
#include "driver/shaders/dxil/dxil_common.h"
#include "d3d12_command_queue.h"
#include "d3d12_debug.h"
#include "d3d12_device.h"
#include "d3d12_replay.h"
#include "d3d12_resources.h"
#include "d3d12_shader_cache.h"
RDOC_CONFIG(rdcstr, D3D12_Debug_FeedbackDumpDirPath, "",
"Path to dump bindless feedback annotation generated DXBC/DXIL files.");
RDOC_CONFIG(
bool, D3D12_BindlessFeedback, true,
"Enable fetching from GPU which descriptors were dynamically used in descriptor arrays.");
struct D3D12FeedbackKey
{
ShaderStage stage;
DXBCBytecode::OperandType type;
uint32_t space;
uint32_t bind;
bool operator<(const D3D12FeedbackKey &key) const
{
if(stage != key.stage)
return stage < key.stage;
if(type != key.type)
return type < key.type;
if(space != key.space)
return space < key.space;
return bind < key.bind;
}
bool operator==(const D3D12FeedbackKey &key) const
{
return stage == key.stage && type == key.type && space == key.space && bind == key.bind;
}
};
struct D3D12FeedbackSlot
{
public:
D3D12FeedbackSlot()
{
slot = 0;
used = 0;
}
void SetSlot(uint32_t s) { slot = s; }
void SetStaticUsed() { used = 0x1; }
bool StaticUsed() const { return used != 0x0; }
uint32_t Slot() const { return slot; }
uint32_t numDescriptors = 1;
DescriptorAccess access;
private:
uint32_t slot : 31;
uint32_t used : 1;
};
static const uint32_t numReservedSlots = 4;
static const uint32_t magicFeedbackValue = 0xfeed0000;
// the 8 lower bits to store the descriptor type of resource for direct heap access bindless
static const uint32_t magicFeedbackTypeMask = 0x000000ff;
// the next 8 bits indicate the shader stage mask of usage
static const uint32_t magicFeedbackStageMask = 0x0000ff00;
static const uint32_t magicFeedbackStageShift = 8;
static D3D12FeedbackKey GetDirectHeapAccessKey(bool samplers)
{
D3D12FeedbackKey key = {};
key.type = DXBCBytecode::OperandType::TYPE_THIS_POINTER;
key.space = samplers ? 1 : 0;
return key;
}
static bool AnnotateDXBCShader(const DXBC::DXBCContainer *dxbc, uint32_t space,
const std::map<D3D12FeedbackKey, D3D12FeedbackSlot> &slots,
uint32_t maxSlot, bytebuf &editedBlob)
{
using namespace DXBCBytecode;
using namespace DXBCBytecode::Edit;
ProgramEditor editor(dxbc, editedBlob);
// get ourselves a temp
uint32_t t = editor.AddTemp();
// declare the output UAV
ResourceDecl desc;
desc.compType = CompType::UInt;
desc.type = TextureType::Buffer;
desc.raw = true;
ResourceIdentifier u = {~0U, ~0U};
for(size_t i = 0; i < editor.GetNumInstructions(); i++)
{
const Operation &op = editor.GetInstruction(i);
for(const Operand &operand : op.operands)
{
if(operand.type != TYPE_RESOURCE && operand.type != TYPE_UNORDERED_ACCESS_VIEW)
continue;
const Declaration *decl =
editor.FindDeclaration(operand.type, (uint32_t)operand.indices[0].index);
if(!decl)
{
RDCERR("Couldn't find declaration for %d operand identifier %u", operand.type,
(uint32_t)operand.indices[0].index);
continue;
}
// ignore non-arrayed declarations
if(decl->operand.indices[1].index == decl->operand.indices[2].index)
continue;
bool dynamic = operand.indices[1].relative;
Operand idx;
if(dynamic)
{
// the operand should be relative addressing like r0.x + 6 for a t6 resource being indexed
// with [r0.x]
RDCASSERT(operand.indices[1].index == decl->operand.indices[1].index);
idx = operand.indices[1].operand;
// should be getting a scalar index
if(idx.comps[1] != 0xff || idx.comps[2] != 0xff || idx.comps[3] != 0xff)
{
RDCERR("Unexpected vector index for resource: %s",
operand.toString(dxbc->GetReflection(), ToString::None).c_str());
continue;
}
}
else
{
// shader could be indexing into an array with a fixed index. Handle that by subtracting the
// base manually
RDCASSERT(operand.indices[1].index >= decl->operand.indices[1].index);
idx = imm(uint32_t(operand.indices[1].index - decl->operand.indices[1].index));
}
D3D12FeedbackKey key = {};
key.stage = GetShaderStage(editor.GetShaderType());
key.type = operand.type;
key.space = decl->space;
key.bind = (int32_t)decl->operand.indices[1].index;
auto it = slots.find(key);
if(it == slots.end())
{
RDCERR("Couldn't find reserved base slot for %d at space %u and bind %u", key.type,
key.space, key.bind);
continue;
}
if(u.first == ~0U && u.second == ~0U)
u = editor.DeclareUAV(desc, space, 0, 0);
// clamp to number of slots
editor.InsertOperation(i++, oper(OPCODE_UMIN, {temp(t).swizzle(0), imm(maxSlot), idx}));
// resource base plus index
editor.InsertOperation(
i++, oper(OPCODE_IADD, {temp(t).swizzle(0), temp(t).swizzle(0), imm(it->second.Slot())}));
// multiply by 4 for byte index
editor.InsertOperation(i++,
oper(OPCODE_ISHL, {temp(t).swizzle(0), temp(t).swizzle(0), imm(2)}));
// atomic or the slot
editor.InsertOperation(
i++, oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(magicFeedbackValue)}));
// only one resource operand per instruction
break;
}
}
if(u.first != ~0U || u.second != ~0U)
{
editor.InsertOperation(0, oper(OPCODE_MOV, {temp(t).swizzle(0), imm(0)}));
editor.InsertOperation(
1, oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(magicFeedbackValue)}));
return true;
}
return false;
}
static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space,
const std::map<D3D12FeedbackKey, D3D12FeedbackSlot> &slots,
uint32_t maxSlot, bytebuf &editedBlob)
{
using namespace DXIL;
ProgramEditor editor(dxbc, editedBlob);
const Type *i32 = editor.GetInt32Type();
const Type *i8 = editor.GetInt8Type();
const Type *i1 = editor.GetBoolType();
const Type *handleType = editor.CreateNamedStructType(
"dx.types.Handle", {editor.CreatePointerType(i8, Type::PointerAddrSpace::Default)});
const Function *createHandle = editor.GetFunctionByName("dx.op.createHandle");
const Function *createHandleFromBinding =
editor.GetFunctionByName("dx.op.createHandleFromBinding");
const Function *createHandleFromHeap = editor.GetFunctionByName("dx.op.createHandleFromHeap");
const Function *annotateHandle = editor.GetFunctionByName("dx.op.annotateHandle");
bool isShaderModel6_6OrAbove =
dxbc->m_Version.Major > 6 || (dxbc->m_Version.Major == 6 && dxbc->m_Version.Minor >= 6);
// if we don't have the handle type then this shader can't use any dynamically! we have no
// feedback to get
if(!handleType || (!createHandle && !isShaderModel6_6OrAbove) ||
(isShaderModel6_6OrAbove && !createHandleFromHeap && !createHandleFromBinding))
return false;
// Create createHandleFromBinding we'll need to create the feedback UAV
if(!createHandleFromBinding && isShaderModel6_6OrAbove)
{
const Type *resBindType = editor.CreateNamedStructType("dx.types.ResBind", {i32, i32, i32, i8});
createHandleFromBinding = editor.DeclareFunction("dx.op.createHandleFromBinding", handleType,
{i32, resBindType, i32, i1},
Attribute::NoUnwind | Attribute::ReadNone);
}
// get the functions we'll need
const Function *atomicBinOp = editor.DeclareFunction("dx.op.atomicBinOp.i32", i32,
{i32, handleType, i32, i32, i32, i32, i32},
Attribute::NoUnwind | Attribute::ReadNone);
const Function *binOp = editor.DeclareFunction("dx.op.binary.i32", i32, {i32, i32, i32},
Attribute::NoUnwind | Attribute::ReadNone);
// while we're iterating through the metadata to add our UAV, we'll also note the shader-local
// register IDs of each SRV/UAV with slots, and record the base slot in this array for easy access
// later when annotating dx.op.createHandle calls. We also need to know the base register because
// the index dxc provides is register-relative
rdcarray<rdcpair<uint32_t, uint32_t>> srvBaseSlots, uavBaseSlots;
// declare the resource, this happens purely in metadata but we need to store the slot
uint32_t regSlot = 0;
Metadata *reslist = NULL;
{
const Type *rw = editor.CreateNamedStructType("struct.RWByteAddressBuffer", {i32});
const Type *rwptr = editor.CreatePointerType(rw, Type::PointerAddrSpace::Default);
Metadata *resources = editor.CreateNamedMetadata("dx.resources");
if(resources->children.empty())
resources->children.push_back(editor.CreateMetadata());
reslist = resources->children[0];
if(reslist->children.empty())
reslist->children.resize(4);
Metadata *srvs = reslist->children[0];
Metadata *uavs = reslist->children[1];
// if there isn't a UAV list, create an empty one so we can add our own
if(!uavs)
uavs = reslist->children[1] = editor.CreateMetadata();
D3D12FeedbackKey key;
key.stage = GetShaderStage(editor.GetShaderType());
key.type = DXBCBytecode::TYPE_RESOURCE;
for(size_t i = 0; srvs && i < srvs->children.size(); i++)
{
// each SRV child should have a fixed format
const Metadata *srv = srvs->children[i];
const Constant *slot = cast<Constant>(srv->children[(size_t)ResField::ID]->value);
const Constant *srvSpace = cast<Constant>(srv->children[(size_t)ResField::Space]->value);
const Constant *reg = cast<Constant>(srv->children[(size_t)ResField::RegBase]->value);
if(!slot)
{
RDCWARN("Unexpected non-constant slot ID in SRV");
continue;
}
if(!srvSpace)
{
RDCWARN("Unexpected non-constant register space in SRV");
continue;
}
if(!reg)
{
RDCWARN("Unexpected non-constant register base in SRV");
continue;
}
uint32_t id = slot->getU32();
key.space = srvSpace->getU32();
key.bind = reg->getU32();
// ensure every valid ID has an index, even if it's 0
srvBaseSlots.resize_for_index(id);
auto it = slots.find(key);
// not annotated
if(it == slots.end())
continue;
// static used i.e. not arrayed? ignore
if(it->second.StaticUsed())
continue;
uint32_t feedbackSlot = it->second.Slot();
// we assume all feedback slots are non-zero, so that a 0 base slot can be used as an
// identifier for 'this resource isn't annotated'
RDCASSERT(feedbackSlot > 0);
srvBaseSlots[id] = {feedbackSlot, key.bind};
}
key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW;
for(size_t i = 0; i < uavs->children.size(); i++)
{
// each UAV child should have a fixed format, [0] is the reg ID and I think this should always
// be == the index
const Metadata *uav = uavs->children[i];
const Constant *slot = cast<Constant>(uav->children[(size_t)ResField::ID]->value);
const Constant *uavSpace = cast<Constant>(uav->children[(size_t)ResField::Space]->value);
const Constant *reg = cast<Constant>(uav->children[(size_t)ResField::RegBase]->value);
if(!slot)
{
RDCWARN("Unexpected non-constant slot ID in UAV");
continue;
}
if(!uavSpace)
{
RDCWARN("Unexpected non-constant register space in UAV");
continue;
}
if(!reg)
{
RDCWARN("Unexpected non-constant register base in UAV");
continue;
}
RDCASSERT(slot->getU32() == i);
uint32_t id = slot->getU32();
regSlot = RDCMAX(id + 1, regSlot);
// ensure every valid ID has an index, even if it's 0
uavBaseSlots.resize_for_index(id);
key.space = uavSpace->getU32();
key.bind = reg->getU32();
auto it = slots.find(key);
// not annotated
if(it == slots.end())
continue;
// static used i.e. not arrayed? ignore
if(it->second.StaticUsed())
continue;
uint32_t feedbackSlot = it->second.Slot();
// we assume all feedback slots are non-zero, so that a 0 base slot can be used as an
// identifier for 'this resource isn't annotated'
RDCASSERT(feedbackSlot > 0);
uavBaseSlots[id] = {feedbackSlot, key.bind};
}
Constant rwundef;
rwundef.type = rwptr;
rwundef.setUndef(true);
// create the new UAV record
Metadata *feedbackuav = editor.CreateMetadata();
feedbackuav->children = {
editor.CreateConstantMetadata(regSlot),
editor.CreateConstantMetadata(editor.CreateConstant(rwundef)),
editor.CreateConstantMetadata(""),
editor.CreateConstantMetadata(space),
editor.CreateConstantMetadata(0U), // reg base
editor.CreateConstantMetadata(1U), // reg count
editor.CreateConstantMetadata(uint32_t(ResourceKind::RawBuffer)), // shape
editor.CreateConstantMetadata(false), // globally coherent
editor.CreateConstantMetadata(false), // hidden counter
editor.CreateConstantMetadata(false), // raster order
NULL, // UAV tags
};
uavs->children.push_back(feedbackuav);
}
rdcstr entryName;
// add the entry point tags
{
Metadata *entryPoints = editor.GetMetadataByName("dx.entryPoints");
if(!entryPoints)
{
RDCERR("Couldn't find entry point list");
return false;
}
// TODO select the entry point for multiple entry points? RT only for now
Metadata *entry = entryPoints->children[0];
entryName = entry->children[1]->str;
Metadata *taglist = entry->children[4];
if(!taglist)
taglist = entry->children[4] = editor.CreateMetadata();
// find existing shader flags tag, if there is one
Metadata *shaderFlagsTag = NULL;
Metadata *shaderFlagsData = NULL;
size_t existingTag = 0;
for(size_t t = 0; taglist && t < taglist->children.size(); t += 2)
{
RDCASSERT(taglist->children[t]->isConstant);
if(cast<Constant>(taglist->children[t]->value)->getU32() ==
(uint32_t)ShaderEntryTag::ShaderFlags)
{
shaderFlagsTag = taglist->children[t];
shaderFlagsData = taglist->children[t + 1];
existingTag = t + 1;
break;
}
}
uint32_t shaderFlagsValue =
shaderFlagsData ? cast<Constant>(shaderFlagsData->value)->getU32() : 0U;
// raw and structured buffers
shaderFlagsValue |= 0x10;
if(editor.GetShaderType() != DXBC::ShaderType::Compute &&
editor.GetShaderType() != DXBC::ShaderType::Pixel)
{
// UAVs on non-PS/CS stages
shaderFlagsValue |= 0x10000;
}
// (re-)create shader flags tag
shaderFlagsData = editor.CreateConstantMetadata(shaderFlagsValue);
// if we didn't have a shader tags entry at all, create the metadata node for the shader flags
// tag
if(!shaderFlagsTag)
shaderFlagsTag = editor.CreateConstantMetadata((uint32_t)ShaderEntryTag::ShaderFlags);
// if we had a tag already, we can just re-use that tag node and replace the data node.
// Otherwise we need to add both, and we insert them first
if(existingTag)
{
taglist->children[existingTag] = shaderFlagsData;
}
else
{
taglist->children.insert(0, shaderFlagsTag);
taglist->children.insert(1, shaderFlagsData);
}
// set reslist and taglist in case they were null before
entry->children[3] = reslist;
entry->children[4] = taglist;
}
// get the editor to patch PSV0 with our extra UAV
editor.RegisterUAV(DXILResourceType::ByteAddressUAV, space, 0, 0, ResourceKind::RawBuffer);
Function *f = editor.GetFunctionByName(entryName);
if(!f)
{
RDCERR("Couldn't find entry point function '%s'", entryName.c_str());
return false;
}
// One ( and only one ) of createHandle or createHandleFromBinding should be defined
RDCASSERTNOTEQUAL(createHandle == NULL, createHandleFromBinding == NULL);
int startInst = 0;
// create our handle first thing
Instruction *handle = NULL;
if(createHandle)
{
RDCASSERT(!isShaderModel6_6OrAbove);
handle = editor.InsertInstruction(
f, startInst++,
editor.CreateInstruction(createHandle, DXOp::createHandle,
{
// kind = UAV
editor.CreateConstant((uint8_t)HandleKind::UAV),
// ID/slot
editor.CreateConstant(regSlot),
// array index
editor.CreateConstant(0U),
// non-uniform
editor.CreateConstant(false),
}));
}
else if(createHandleFromBinding)
{
RDCASSERT(isShaderModel6_6OrAbove);
const Type *resBindType = editor.CreateNamedStructType("dx.types.ResBind", {});
Constant *resBindConstant =
editor.CreateConstant(resBindType, {
// Lower id bound
editor.CreateConstant(0U),
// Upper id bound
editor.CreateConstant(0U),
// Space ID
editor.CreateConstant(space),
// kind = UAV
editor.CreateConstant((uint8_t)HandleKind::UAV),
});
Instruction *handleCreate = editor.InsertInstruction(
f, startInst++,
editor.CreateInstruction(createHandleFromBinding, DXOp::createHandleFromBinding,
{
// resBind
resBindConstant,
// ID/slot
editor.CreateConstant(0U),
// non-uniform
editor.CreateConstant(false),
}));
handle = editor.InsertInstruction(
f, startInst++,
editor.CreateInstruction(
annotateHandle, DXOp::annotateHandle,
{
// Resource handle
handleCreate,
// Resource properties
editor.CreateConstant(
editor.CreateNamedStructType("dx.types.ResourceProperties", {}),
{
// IsUav : (1 << 12)
editor.CreateConstant(uint32_t((1 << 12) | (uint32_t)ResourceKind::RawBuffer)),
//
editor.CreateConstant(0U),
}),
}));
}
Constant *undefi32 = editor.CreateUndef(i32);
// insert an OR to offset 0, just to indicate validity
editor.InsertInstruction(f, startInst++,
editor.CreateInstruction(atomicBinOp, DXOp::atomicBinOp,
{
// feedback UAV handle
handle,
// operation OR
editor.CreateConstant(2U),
// offset
editor.CreateConstant(0U),
// offset 2
undefi32,
// offset 3
undefi32,
// value
editor.CreateConstant(magicFeedbackValue),
}));
for(size_t i = startInst; i < f->instructions.size(); i++)
{
const Instruction &inst = *f->instructions[i];
// we want to annotate any calls to createHandle
if(inst.op == Operation::Call &&
((createHandle && inst.getFuncCall()->name == createHandle->name) ||
(createHandleFromBinding && inst.getFuncCall()->name == createHandleFromBinding->name)))
{
Value *idxArg;
rdcpair<uint32_t, uint32_t> slotInfo = {0, 0};
if((createHandle && inst.getFuncCall()->name == createHandle->name))
{
RDCASSERT(!isShaderModel6_6OrAbove);
if(inst.args.size() != 5)
{
RDCERR("Unexpected number of arguments to createHandle");
continue;
}
Constant *kindArg = cast<Constant>(inst.args[1]);
Constant *idArg = cast<Constant>(inst.args[2]);
idxArg = inst.args[3];
if(!kindArg || !idArg)
{
RDCERR("Unexpected non-constant argument to createHandle");
continue;
}
HandleKind kind = (HandleKind)kindArg->getU32();
uint32_t id = idArg->getU32();
if(kind == HandleKind::SRV && id < srvBaseSlots.size())
slotInfo = srvBaseSlots[id];
else if(kind == HandleKind::UAV && id < uavBaseSlots.size())
slotInfo = uavBaseSlots[id];
}
else
{
RDCASSERT(isShaderModel6_6OrAbove);
if(inst.args.size() != 4)
{
RDCERR("Unexpected number of arguments to createHandleFromBinding");
continue;
}
Constant *resBindArg = cast<Constant>(inst.args[1]);
idxArg = inst.args[2];
if(!resBindArg)
{
RDCERR("Unexpected non-constant argument to createHandleFromBinding");
continue;
}
if(!resBindArg->isNULL() && resBindArg->getMembers().size() != 4)
{
RDCERR("Unexpected number of members to resBind");
continue;
}
D3D12FeedbackKey key;
key.stage = GetShaderStage(editor.GetShaderType());
if(resBindArg->isNULL())
{
key.type = DXBCBytecode::TYPE_RESOURCE;
key.space = 0;
key.bind = 0;
}
else
{
Constant *regArg = cast<Constant>(resBindArg->getMembers()[0]);
Constant *spaceArg = cast<Constant>(resBindArg->getMembers()[2]);
Constant *kindArg = cast<Constant>(resBindArg->getMembers()[3]);
if(!regArg || !spaceArg || !kindArg)
{
RDCERR("Unexpected non-constant argument to createHandleFromBinding");
continue;
}
HandleKind kind = (HandleKind)kindArg->getU32();
if(kind != HandleKind::SRV && kind != HandleKind::UAV)
continue;
key.type = kind == HandleKind::UAV ? DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW
: DXBCBytecode::TYPE_RESOURCE;
key.space = spaceArg->getU32();
key.bind = regArg->getU32();
}
auto it = slots.find(key);
// not annotated
if(it == slots.end())
continue;
// static used i.e. not arrayed? ignore
if(it->second.StaticUsed())
continue;
slotInfo = {it->second.Slot(), key.bind};
}
if(slotInfo.first == 0)
continue;
// idx0Based = idx - baseReg
Instruction *idx0Based = editor.InsertInstruction(
f, i++,
editor.CreateInstruction(Operation::Sub, i32,
{
// idx to the createHandle op
idxArg,
// register that this is relative to
editor.CreateConstant((uint32_t)slotInfo.second),
}));
// slotPlusBase = idx0Based + slot
Instruction *slotPlusBase = editor.InsertInstruction(
f, i++,
editor.CreateInstruction(Operation::Add, i32,
{
// idx to the createHandle op
idx0Based,
// base slot
editor.CreateConstant(slotInfo.first),
}));
// slotPlusBaseClamped = min(slotPlusBase, maxSlot)
Instruction *slotPlusBaseClamped =
editor.InsertInstruction(f, i++,
editor.CreateInstruction(binOp, DXOp::UMin,
{
// slotPlusBase
slotPlusBase,
// max slot
editor.CreateConstant(maxSlot),
}));
// byteOffset = slotPlusBaseClamped << 2
Instruction *byteOffset =
editor.InsertInstruction(f, i++,
editor.CreateInstruction(Operation::ShiftLeft, i32,
{
slotPlusBaseClamped,
editor.CreateConstant(2U),
}));
editor.InsertInstruction(f, i++,
editor.CreateInstruction(atomicBinOp, DXOp::atomicBinOp,
{
// feedback UAV handle
handle,
// operation OR
editor.CreateConstant(2U),
// offset
byteOffset,
// offset 2
undefi32,
// offset 3
undefi32,
// value
editor.CreateConstant(magicFeedbackValue),
}));
}
else if(inst.op == Operation::Call && createHandleFromHeap &&
inst.getFuncCall()->name == createHandleFromHeap->name)
{
RDCASSERT(isShaderModel6_6OrAbove);
if(inst.args.size() != 4)
{
RDCERR("Unexpected number of arguments to createHandleFromHeap");
continue;
}
Constant *isSamplerArg = cast<Constant>(inst.args[2]);
if(!isSamplerArg)
{
RDCERR("Unexpected non-constant argument to createHandleFromHeap");
continue;
}
bool isSampler = isSamplerArg->getU32() != 0;
D3D12FeedbackKey key = GetDirectHeapAccessKey(isSampler);
auto it = slots.find(key);
if(it == slots.end())
continue;
// Look for annotation for the type of this view ( SRV/UAV/CBV/Sampler )
const Instruction *annotateInst = NULL;
for(size_t nextInstIndex = i + 1; nextInstIndex < f->instructions.size(); nextInstIndex++)
{
const Instruction &nextInst = *f->instructions[nextInstIndex];
if(nextInst.op == Operation::Call && annotateHandle &&
nextInst.getFuncCall()->name == annotateHandle->name)
{
if(nextInst.args.size() != 3)
{
RDCERR("Unexpected number of arguments to annotateHandle");
continue;
}
Value *idxArg = nextInst.args[1];
if(idxArg->id == inst.id)
{
annotateInst = &nextInst;
break;
}
}
}
if(annotateInst == NULL)
{
RDCERR("Unexpected, could not find annotateHandle for createHandleFromHeap");
continue;
}
Constant *resPropArg = cast<Constant>(annotateInst->args[2]);
if(!resPropArg)
{
RDCERR("Unexpected non-constant argument for dx.types.ResourceProperties");
continue;
}
if(resPropArg->getMembers().size() != 2)
{
RDCERR("Unexpected number of arguments to dx.types.ResourceProperties");
continue;
}
Constant *resKindArg = cast<Constant>(resPropArg->getMembers()[0]);
if(!resKindArg)
{
RDCERR("Unexpected non-constant argument for dx.types.ResourceProperties's resource kind");
continue;
}
DescriptorType descriptorType = DescriptorType::Unknown;
ResourceKind resKind = (ResourceKind)(resKindArg->getU32() & 0xFF);
bool isUav = (resKindArg->getU32() & (1 << 12)) != 0;
if(resKind == ResourceKind::Sampler || resKind == ResourceKind::SamplerComparison)
{
descriptorType = DescriptorType::Sampler;
RDCASSERT(isSampler);
RDCASSERT(!isUav);
}
else if(resKind == ResourceKind::CBuffer)
{
descriptorType = DescriptorType::ConstantBuffer;
RDCASSERT(!isSampler);
RDCASSERT(!isUav);
}
else if(isUav)
{
descriptorType = DescriptorType::ReadWriteImage;
if(resKind == ResourceKind::TBuffer)
descriptorType = DescriptorType::ReadWriteTypedBuffer;
else if(resKind == ResourceKind::RawBuffer || resKind == ResourceKind::StructuredBuffer)
descriptorType = DescriptorType::ReadWriteBuffer;
RDCASSERT(!isSampler);
}
else
{
descriptorType = DescriptorType::Image;
if(resKind == ResourceKind::TBuffer || resKind == ResourceKind::TypedBuffer)
descriptorType = DescriptorType::TypedBuffer;
else if(resKind == ResourceKind::RawBuffer || resKind == ResourceKind::StructuredBuffer)
descriptorType = DescriptorType::Buffer;
RDCASSERT(!isSampler);
}
Value *idxArg = inst.args[1];
Instruction op;
// slotPlusBase = idx0Based + slot
Instruction *slotPlusBase = editor.InsertInstruction(
f, i++,
editor.CreateInstruction(Operation::Add, i32,
{
// idx to the createHandleFromHeap op
idxArg,
// base slot
editor.CreateConstant(it->second.Slot()),
}));
// slotPlusBaseClamped = min(slotPlusBase, maxSlot)
Instruction *slotPlusBaseClamped =
editor.InsertInstruction(f, i++,
editor.CreateInstruction(binOp, DXOp::UMin,
{
// slotPlusBase
slotPlusBase,
// max slot
editor.CreateConstant(maxSlot),
}));
// byteOffset = slotPlusBase << 2
Instruction *byteOffset =
editor.InsertInstruction(f, i++,
editor.CreateInstruction(Operation::ShiftLeft, i32,
{
slotPlusBaseClamped,
editor.CreateConstant(2U),
}));
// this would in theory alias if there were different stages that used the same descriptor
// with different types, but we assume that won't happen (since that would mean texture
// reinterpreted as buffer or something invalid, not just 2D <-> 2DArray or some valid alias)
uint32_t feedbackValue = magicFeedbackValue;
feedbackValue |= ((uint32_t)descriptorType) & magicFeedbackTypeMask;
uint32_t stageMask = (uint32_t)MaskForStage(GetShaderStage(editor.GetShaderType()));
feedbackValue |= (stageMask << magicFeedbackStageShift) & magicFeedbackStageMask;
editor.InsertInstruction(f, i++,
editor.CreateInstruction(atomicBinOp, DXOp::atomicBinOp,
{
// feedback UAV handle
handle,
// operation OR
editor.CreateConstant(2U),
// offset
byteOffset,
// offset 2
undefi32,
// offset 3
undefi32,
// value
editor.CreateConstant(feedbackValue),
}));
}
}
return true;
}
static bool AddArraySlots(WrappedID3D12PipelineState::ShaderEntry *shad, uint32_t space,
uint32_t maxDescriptors,
std::map<D3D12FeedbackKey, D3D12FeedbackSlot> &slots, uint32_t &numSlots,
bytebuf &editedBlob, D3D12_SHADER_BYTECODE &desc, bool directHeapAccess)
{
if(!shad)
return false;
bool dynamicUsed = false;
ShaderReflection &refl = shad->GetDetails();
for(size_t i = 0; i < refl.readOnlyResources.size(); i++)
{
const ShaderResource &ro = refl.readOnlyResources[i];
if(ro.bindArraySize > 1)
{
D3D12FeedbackKey key;
key.stage = refl.stage;
key.type = DXBCBytecode::TYPE_RESOURCE;
key.space = ro.fixedBindSetOrSpace;
key.bind = ro.fixedBindNumber;
DescriptorAccess access;
access.stage = refl.stage;
access.type = ro.descriptorType;
access.index = i & 0xffff;
// descriptor storage side will be calculated later when finalising this with the root
// signature information.
slots[key].SetSlot(numSlots);
slots[key].numDescriptors = RDCMIN(maxDescriptors, ro.bindArraySize);
slots[key].access = access;
numSlots += slots[key].numDescriptors;
dynamicUsed = true;
}
else if(ro.bindArraySize <= 1)
{
// since the eventual descriptor range iteration won't know which descriptors map to arrays
// and which to fixed slots, it can't mark fixed descriptors as dynamically used itself. So
// instead we don't reserve a slot and set the top bit for these binds to indicate that
// they're fixed used. This allows for overlap between an array and a fixed resource which is
// allowed
D3D12FeedbackKey key;
key.stage = refl.stage;
key.type = DXBCBytecode::TYPE_RESOURCE;
key.space = ro.fixedBindSetOrSpace;
key.bind = ro.fixedBindNumber;
slots[key].SetStaticUsed();
}
}
for(size_t i = 0; i < refl.readWriteResources.size(); i++)
{
const ShaderResource &rw = refl.readWriteResources[i];
if(rw.bindArraySize > 1)
{
D3D12FeedbackKey key;
key.stage = refl.stage;
key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW;
key.space = rw.fixedBindSetOrSpace;
key.bind = rw.fixedBindNumber;
DescriptorAccess access;
access.stage = refl.stage;
access.type = rw.descriptorType;
access.index = i & 0xffff;
// descriptor storage side will be calculated later when finalising this with the root
// signature information.
slots[key].SetSlot(numSlots);
slots[key].numDescriptors = RDCMIN(maxDescriptors, rw.bindArraySize);
slots[key].access = access;
numSlots += slots[key].numDescriptors;
dynamicUsed = true;
}
else if(rw.bindArraySize <= 1)
{
// since the eventual descriptor range iteration won't know which descriptors map to arrays
// and which to fixed slots, it can't mark fixed descriptors as dynamically used itself. So
// instead we don't reserve a slot and set the top bit for these binds to indicate that
// they're fixed used. This allows for overlap between an array and a fixed resource which is
// allowed
D3D12FeedbackKey key;
key.stage = refl.stage;
key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW;
key.space = rw.fixedBindSetOrSpace;
key.bind = rw.fixedBindNumber;
slots[key].SetStaticUsed();
}
}
if(shad->GetDXBC()->m_Version.Major < 6 ||
(shad->GetDXBC()->m_Version.Major == 6 && shad->GetDXBC()->m_Version.Minor < 6))
{
directHeapAccess = false;
}
if(directHeapAccess)
directHeapAccess = shad->GetDXBC()->GetDXILByteCode()->GetDirectHeapAcessCount() > 0;
if(directHeapAccess)
{
// only one stage should allocate space for direct heap access as it's shared
D3D12FeedbackKey key = GetDirectHeapAccessKey(false);
if(slots.find(key) == slots.end())
{
slots[key].SetSlot(numSlots);
slots[key].numDescriptors = maxDescriptors;
numSlots += maxDescriptors;
key = GetDirectHeapAccessKey(true);
slots[key].SetSlot(numSlots);
slots[key].numDescriptors = D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE;
numSlots += D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE;
}
dynamicUsed = true;
}
// if we haven't encountered any array slots, no need to do any patching
if(!dynamicUsed)
return false;
// only SM5.1 can have dynamic array indexing
if(shad->GetDXBC()->m_Version.Major == 5 && shad->GetDXBC()->m_Version.Minor == 1)
{
if(AnnotateDXBCShader(shad->GetDXBC(), space, slots, numSlots, editedBlob))
{
if(!D3D12_Debug_FeedbackDumpDirPath().empty())
FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/before_dxbc_" +
ToStr(shad->GetDetails().stage).c_str() + ".dxbc",
shad->GetDXBC()->GetShaderBlob());
if(!D3D12_Debug_FeedbackDumpDirPath().empty())
FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/after_dxbc_" +
ToStr(shad->GetDetails().stage).c_str() + ".dxbc",
editedBlob);
desc.pShaderBytecode = editedBlob.data();
desc.BytecodeLength = editedBlob.size();
}
}
else if(shad->GetDXBC()->m_Version.Major >= 6)
{
if(AnnotateDXILShader(shad->GetDXBC(), space, slots, numSlots, editedBlob))
{
if(!D3D12_Debug_FeedbackDumpDirPath().empty())
{
bytebuf orig = shad->GetDXBC()->GetShaderBlob();
DXBC::DXBCContainer::StripChunk(orig, DXBC::FOURCC_ILDB);
DXBC::DXBCContainer::StripChunk(orig, DXBC::FOURCC_STAT);
FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/before_dxil_" +
ToStr(shad->GetDetails().stage).c_str() + ".dxbc",
orig);
}
if(!D3D12_Debug_FeedbackDumpDirPath().empty())
{
FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/after_dxil_" +
ToStr(shad->GetDetails().stage).c_str() + ".dxbc",
editedBlob);
}
desc.pShaderBytecode = editedBlob.data();
desc.BytecodeLength = editedBlob.size();
}
}
return true;
}
struct D3D12StatCallback : public D3D12ActionCallback
{
D3D12StatCallback(WrappedID3D12Device *dev, ID3D12QueryHeap *heap)
: m_pDevice(dev), m_PipeStatsQueryHeap(heap)
{
m_pDevice->GetQueue()->GetCommandData()->m_ActionCallback = this;
}
~D3D12StatCallback() { m_pDevice->GetQueue()->GetCommandData()->m_ActionCallback = NULL; }
void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override
{
if(cmd->GetType() == D3D12_COMMAND_LIST_TYPE_DIRECT)
cmd->BeginQuery(m_PipeStatsQueryHeap, D3D12_QUERY_TYPE_PIPELINE_STATISTICS, 0);
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override
{
if(cmd->GetType() == D3D12_COMMAND_LIST_TYPE_DIRECT)
cmd->EndQuery(m_PipeStatsQueryHeap, D3D12_QUERY_TYPE_PIPELINE_STATISTICS, 0);
return false;
}
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override {}
// we don't need to distinguish, call the Draw functions
void PreDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override { PreDraw(eid, cmd); }
bool PostDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override
{
return PostDraw(eid, cmd);
}
void PostRedispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) override
{
PostRedraw(eid, cmd);
}
void PreMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) override
{
if(flags & ActionFlags::PassBoundary)
return;
PreDraw(eid, cmd);
}
bool PostMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) override
{
if(flags & ActionFlags::PassBoundary)
return false;
return PostDraw(eid, cmd);
}
void PostRemisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) override
{
if(flags & ActionFlags::PassBoundary)
return;
PostRedraw(eid, cmd);
}
void PreCloseCommandList(ID3D12GraphicsCommandListX *cmd) override{};
void AliasEvent(uint32_t primary, uint32_t alias) override {}
WrappedID3D12Device *m_pDevice;
ID3D12QueryHeap *m_PipeStatsQueryHeap;
};
bool D3D12Replay::FetchShaderFeedback(uint32_t eventId)
{
if(m_BindlessFeedback.Usage.find(eventId) != m_BindlessFeedback.Usage.end())
return false;
if(!D3D12_BindlessFeedback())
return false;
if(m_pDevice->HasFatalError())
return false;
// create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid
// if it actually has any data in it later.
D3D12DynamicShaderFeedback &result = m_BindlessFeedback.Usage[eventId];
const ActionDescription *action = m_pDevice->GetAction(eventId);
if(action == NULL ||
!(action->flags & (ActionFlags::Dispatch | ActionFlags::MeshDispatch | ActionFlags::Drawcall)))
{
// deliberately show no bindings as used for non-draws
result.valid = true;
return false;
}
result.compute = bool(action->flags & ActionFlags::Dispatch);
D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState;
D3D12ResourceManager *rm = m_pDevice->GetResourceManager();
WrappedID3D12PipelineState *pipe =
(WrappedID3D12PipelineState *)rm->GetCurrentAs<ID3D12PipelineState>(rs.pipe);
D3D12RootSignature modsig;
if(!pipe)
{
RDCERR("Can't fetch shader feedback, no pipeline state bound");
result.valid = true;
return false;
}
bytebuf editedBlob[(uint32_t)ShaderStage::Count];
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
pipe->Fill(pipeDesc);
uint32_t feedbackBufSpace = 1;
uint32_t maxDescriptors = 0;
for(ResourceId id : rs.heaps)
{
WrappedID3D12DescriptorHeap *heap =
(WrappedID3D12DescriptorHeap *)rm->GetCurrentAs<ID3D12DescriptorHeap>(id);
D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc();
maxDescriptors = RDCMAX(maxDescriptors, desc.NumDescriptors);
}
RDCDEBUG("Clamping any unbounded ranges to %u descriptors", maxDescriptors);
std::map<D3D12FeedbackKey, D3D12FeedbackSlot> slots;
// reserve the first 4 dwords for debug info and a validity flag
uint32_t numSlots = numReservedSlots;
#if ENABLED(RDOC_DEVEL)
m_pDevice->GetShaderCache()->LoadDXC();
#endif
bool dynamicAccessPerStage[NumShaderStages] = {};
if(result.compute)
{
ID3D12RootSignature *sig = rm->GetCurrentAs<ID3D12RootSignature>(rs.compute.rootsig);
if(!sig)
{
result.valid = true;
return false;
}
modsig = ((WrappedID3D12RootSignature *)sig)->sig;
bool directHeapAccess =
(modsig.Flags & (D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED |
D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED)) != 0;
feedbackBufSpace = modsig.maxSpaceIndex;
dynamicAccessPerStage[5] =
AddArraySlots(pipe->CS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[(uint32_t)ShaderStage::Compute], pipeDesc.CS, directHeapAccess);
}
else
{
ID3D12RootSignature *sig = rm->GetCurrentAs<ID3D12RootSignature>(rs.graphics.rootsig);
if(!sig)
{
result.valid = true;
return false;
}
modsig = ((WrappedID3D12RootSignature *)sig)->sig;
bool directHeapAccess =
(modsig.Flags & (D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED |
D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED)) != 0;
feedbackBufSpace = modsig.maxSpaceIndex;
dynamicAccessPerStage[0] =
AddArraySlots(pipe->VS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Vertex)], pipeDesc.VS, directHeapAccess);
dynamicAccessPerStage[1] =
AddArraySlots(pipe->HS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Hull)], pipeDesc.HS, directHeapAccess);
dynamicAccessPerStage[2] =
AddArraySlots(pipe->DS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Domain)], pipeDesc.DS, directHeapAccess);
dynamicAccessPerStage[3] =
AddArraySlots(pipe->GS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Geometry)], pipeDesc.GS, directHeapAccess);
dynamicAccessPerStage[4] =
AddArraySlots(pipe->PS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Pixel)], pipeDesc.PS, directHeapAccess);
dynamicAccessPerStage[6] = AddArraySlots(
pipe->AS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Amplification)], pipeDesc.AS, directHeapAccess);
dynamicAccessPerStage[7] =
AddArraySlots(pipe->MS(), feedbackBufSpace, maxDescriptors, slots, numSlots,
editedBlob[uint32_t(ShaderStage::Mesh)], pipeDesc.MS, directHeapAccess);
}
// if numSlots wasn't increased, none of the resources were arrayed so we have nothing to do.
// Silently return
if(numSlots == numReservedSlots)
{
return false;
}
numSlots = AlignUp16(numSlots);
// need to be able to add a descriptor of our UAV without hitting the 64 DWORD limit
if(modsig.dwordLength > 62)
{
RDCWARN("Root signature is 64 DWORDS, adding feedback buffer might fail");
}
// add root UAV element
modsig.Parameters.push_back(D3D12RootSignatureParameter());
{
D3D12RootSignatureParameter &param = modsig.Parameters.back();
param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV;
param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
param.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_VOLATILE;
param.Descriptor.RegisterSpace = feedbackBufSpace;
param.Descriptor.ShaderRegister = 0;
}
if(m_BindlessFeedback.PipeStatsHeap == NULL)
{
D3D12_QUERY_HEAP_DESC pipestatsQueryDesc;
pipestatsQueryDesc.Count = 1;
pipestatsQueryDesc.NodeMask = 1;
pipestatsQueryDesc.Type = D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS;
HRESULT hr = m_pDevice->CreateQueryHeap(&pipestatsQueryDesc, __uuidof(ID3D12QueryHeap),
(void **)&m_BindlessFeedback.PipeStatsHeap);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create shader feedback pipeline query heap HRESULT: %s", ToStr(hr).c_str());
return false;
}
}
if(m_BindlessFeedback.FeedbackBuffer == NULL ||
m_BindlessFeedback.FeedbackBuffer->GetDesc().Width < numSlots * sizeof(uint32_t))
{
SAFE_RELEASE(m_BindlessFeedback.FeedbackBuffer);
D3D12_RESOURCE_DESC desc = {};
desc.Alignment = 0;
desc.DepthOrArraySize = 1;
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
desc.Format = DXGI_FORMAT_UNKNOWN;
desc.Height = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Width = AlignUp<uint32_t>(numSlots * sizeof(uint32_t), 1024) +
sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS);
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;
HRESULT hr = m_pDevice->CreateCommittedResource(
&heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL,
__uuidof(ID3D12Resource), (void **)&m_BindlessFeedback.FeedbackBuffer);
if(m_BindlessFeedback.FeedbackBuffer == NULL || FAILED(hr))
{
RDCERR("Couldn't create feedback buffer with %u slots: %s", numSlots, ToStr(hr).c_str());
return false;
}
m_BindlessFeedback.FeedbackBuffer->SetName(L"m_BindlessFeedback.FeedbackBuffer");
}
{
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uavDesc.Format = DXGI_FORMAT_R32_UINT;
uavDesc.Buffer.CounterOffsetInBytes = 0;
// start with elements after the counter
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.NumElements = numSlots;
uavDesc.Buffer.StructureByteStride = 0;
m_pDevice->CreateUnorderedAccessView(m_BindlessFeedback.FeedbackBuffer, NULL, &uavDesc,
GetDebugManager()->GetCPUHandle(FEEDBACK_CLEAR_UAV));
m_pDevice->CreateUnorderedAccessView(m_BindlessFeedback.FeedbackBuffer, NULL, &uavDesc,
GetDebugManager()->GetUAVClearHandle(FEEDBACK_CLEAR_UAV));
ID3D12GraphicsCommandList *list = m_pDevice->GetNewList();
if(!list)
return false;
GetDebugManager()->SetDescriptorHeaps(list, true, false);
UINT zeroes[4] = {0, 0, 0, 0};
list->ClearUnorderedAccessViewUint(GetDebugManager()->GetGPUHandle(FEEDBACK_CLEAR_UAV),
GetDebugManager()->GetUAVClearHandle(FEEDBACK_CLEAR_UAV),
m_BindlessFeedback.FeedbackBuffer, zeroes, 0, NULL);
list->Close();
}
ID3D12RootSignature *annotatedSig = NULL;
{
ID3DBlob *root = m_pDevice->GetShaderCache()->MakeRootSig(modsig);
HRESULT hr =
m_pDevice->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(),
__uuidof(ID3D12RootSignature), (void **)&annotatedSig);
if(annotatedSig == NULL || FAILED(hr))
{
SAFE_RELEASE(root);
RDCERR("Couldn't create feedback modified root signature: %s", ToStr(hr).c_str());
return false;
}
SAFE_RELEASE(root);
}
ID3D12PipelineState *annotatedPipe = NULL;
{
pipeDesc.pRootSignature = annotatedSig;
HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &annotatedPipe);
if(annotatedPipe == NULL || FAILED(hr))
{
SAFE_RELEASE(annotatedSig);
RDCERR("Couldn't create feedback modified pipeline: %s", ToStr(hr).c_str());
return false;
}
}
D3D12RenderState prev = rs;
rs.pipe = GetResID(annotatedPipe);
if(result.compute)
{
rs.compute.rootsig = GetResID(annotatedSig);
size_t idx = modsig.Parameters.size() - 1;
rs.compute.sigelems.resize_for_index(idx);
rs.compute.sigelems[idx] =
D3D12RenderState::SignatureElement(eRootUAV, GetResID(m_BindlessFeedback.FeedbackBuffer), 0);
}
else
{
rs.graphics.rootsig = GetResID(annotatedSig);
size_t idx = modsig.Parameters.size() - 1;
rs.graphics.sigelems.resize_for_index(idx);
rs.graphics.sigelems[idx] =
D3D12RenderState::SignatureElement(eRootUAV, GetResID(m_BindlessFeedback.FeedbackBuffer), 0);
}
{
D3D12StatCallback cb(m_pDevice, m_BindlessFeedback.PipeStatsHeap);
m_pDevice->ReplayLog(0, eventId, eReplay_OnlyDraw);
ID3D12GraphicsCommandList *list = m_pDevice->GetNewList();
if(!list)
return true;
list->ResolveQueryData(m_BindlessFeedback.PipeStatsHeap, D3D12_QUERY_TYPE_PIPELINE_STATISTICS,
0, 1, m_BindlessFeedback.FeedbackBuffer, numSlots * sizeof(uint32_t));
list->Close();
}
m_pDevice->ExecuteLists();
m_pDevice->FlushLists();
SAFE_RELEASE(annotatedPipe);
SAFE_RELEASE(annotatedSig);
rs = prev;
bytebuf results;
GetDebugManager()->GetBufferData(m_BindlessFeedback.FeedbackBuffer, 0, 0, results);
if(results.size() < numSlots * sizeof(uint32_t))
{
RDCERR("Results buffer not the right size!");
}
else
{
uint32_t *slotsData = (uint32_t *)results.data();
D3D12_QUERY_DATA_PIPELINE_STATISTICS *pipelinestats =
(D3D12_QUERY_DATA_PIPELINE_STATISTICS *)(slotsData + numSlots);
if(slotsData[0] == magicFeedbackValue)
{
result.valid = true;
// now we iterate over descriptor ranges and find which (of potentially multiple) registers
// each descriptor maps to and store the index if it's dynamically or statically used. We do
// this here so it only happens once instead of doing it when looking up the data.
D3D12FeedbackKey curKey;
D3D12FeedbackBindIdentifier curIdentifier = {};
// don't iterate the last signature element because that's ours!
for(size_t rootEl = 0; rootEl < modsig.Parameters.size() - 1; rootEl++)
{
curIdentifier.rootEl = rootEl;
const D3D12RootSignatureParameter &p = modsig.Parameters[rootEl];
// only tables need feedback data, others all are treated as dynamically used
if(p.ParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE)
{
for(size_t r = 0; r < p.ranges.size(); r++)
{
const D3D12_DESCRIPTOR_RANGE1 &range = p.ranges[r];
curIdentifier.rangeIndex = r;
curKey.space = range.RegisterSpace;
UINT num = range.NumDescriptors;
ShaderStageMask visMask = ShaderStageMask::Unknown;
// see which shader's binds we should look up for this range
switch(p.ShaderVisibility)
{
case D3D12_SHADER_VISIBILITY_ALL:
visMask = result.compute ? ShaderStageMask::Compute : ShaderStageMask::All;
break;
case D3D12_SHADER_VISIBILITY_VERTEX: visMask = ShaderStageMask::Vertex; break;
case D3D12_SHADER_VISIBILITY_HULL: visMask = ShaderStageMask::Hull; break;
case D3D12_SHADER_VISIBILITY_DOMAIN: visMask = ShaderStageMask::Domain; break;
case D3D12_SHADER_VISIBILITY_GEOMETRY: visMask = ShaderStageMask::Geometry; break;
case D3D12_SHADER_VISIBILITY_PIXEL: visMask = ShaderStageMask::Pixel; break;
case D3D12_SHADER_VISIBILITY_AMPLIFICATION:
visMask = ShaderStageMask::Amplification;
break;
case D3D12_SHADER_VISIBILITY_MESH: visMask = ShaderStageMask::Mesh; break;
default: RDCERR("Unexpected shader visibility %d", p.ShaderVisibility); return true;
}
// set the key type
if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SRV)
curKey.type = DXBCBytecode::TYPE_RESOURCE;
else if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_UAV)
curKey.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW;
for(ShaderStage st : values<ShaderStage>())
{
curKey.stage = st;
if(visMask & MaskForStage(st))
{
curIdentifier.descIndex = 0;
curKey.bind = range.BaseShaderRegister;
// the feedback entries start here
auto slotIt = slots.lower_bound(curKey);
// iterate over the declared range. This could be unbounded, so we might exit
// another way
for(uint32_t i = 0; i < num; i++)
{
// stop when we've run out of recorded used slots
if(slotIt == slots.end())
break;
uint32_t space = slotIt->first.space;
uint32_t bind = slotIt->first.bind;
uint32_t numDescriptors = slotIt->second.numDescriptors;
// stop if the next used slot is in another space or is another type
if(space > curKey.space || slotIt->first.type != curKey.type)
break;
// if the next bind is definitely outside this range, early out now instead of
// iterating fruitlessly
if((bind - range.BaseShaderRegister) > num)
break;
uint32_t lastBind = bind + RDCCLAMP(numDescriptors, 1U, maxDescriptors);
// if this slot's array covers the current bind, check the result
if(bind <= curKey.bind && curKey.bind < lastBind)
{
// if it's static used by having a fixed result declared, it's used
const bool staticUsed = slotIt->second.StaticUsed();
// otherwise check the feedback we got
const uint32_t baseSlot = slotIt->second.Slot();
const uint32_t arrayIndex = curKey.bind - bind;
if(staticUsed || slotsData[baseSlot + arrayIndex])
result.used.push_back(curIdentifier);
}
curKey.bind++;
curIdentifier.descIndex++;
// if we've passed this slot, move to the next one. Because we're iterating a
// contiguous range of binds the next slot will be enough for the next iteration
if(curKey.bind >= lastBind)
slotIt++;
}
}
}
}
}
}
for(D3D12FeedbackKey directAccessKey :
{GetDirectHeapAccessKey(false), GetDirectHeapAccessKey(true)})
{
auto slotIt = slots.find(directAccessKey);
if(slotIt != slots.end())
{
const D3D12FeedbackSlot &feedbackSlots = slots.at(directAccessKey);
for(uint32_t i = feedbackSlots.Slot();
i < feedbackSlots.Slot() + feedbackSlots.numDescriptors; ++i)
{
if((slotsData[i] & magicFeedbackValue) == magicFeedbackValue)
{
uint32_t usedSlot = i - feedbackSlots.Slot();
D3D12FeedbackBindIdentifier directAccessIdentifier = {};
directAccessIdentifier.descIndex = usedSlot;
directAccessIdentifier.rootEl = ~0U;
directAccessIdentifier.rangeIndex = ~0U;
directAccessIdentifier.directAccess = true;
ShaderStageMask feedbackStages =
ShaderStageMask((slotsData[i] & magicFeedbackStageMask) >> magicFeedbackStageShift);
DescriptorType descriptorType = DescriptorType(slotsData[i] & magicFeedbackTypeMask);
if(descriptorType == DescriptorType::Sampler)
{
directAccessIdentifier.bindType = BindType::Sampler;
}
else if(IsConstantBlockDescriptor(descriptorType))
{
directAccessIdentifier.bindType = BindType::ConstantBuffer;
}
else if(IsReadOnlyDescriptor(descriptorType))
{
directAccessIdentifier.bindType = BindType::ReadOnlyResource;
}
else if(IsReadWriteDescriptor(descriptorType))
{
directAccessIdentifier.bindType = BindType::ReadWriteResource;
}
else
{
continue;
}
// add a usage for each stage that reported it used
for(ShaderStage stage : values<ShaderStage>())
{
if(MaskForStage(stage) & feedbackStages)
{
directAccessIdentifier.shaderStage = stage;
result.used.push_back(directAccessIdentifier);
}
}
}
}
}
}
D3D12FeedbackKey resourceDirectAccessKey = GetDirectHeapAccessKey(false),
samplerDirectAccessKey = GetDirectHeapAccessKey(true);
for(auto it = slots.begin(); it != slots.end(); ++it)
{
if(it->first == resourceDirectAccessKey || it->first == samplerDirectAccessKey)
{
for(uint32_t i = it->second.Slot(); i < it->second.Slot() + it->second.numDescriptors; ++i)
{
if((slotsData[i] & magicFeedbackValue) == magicFeedbackValue)
{
DescriptorAccess access;
access.index = DescriptorAccess::NoShaderBinding;
access.byteOffset = access.arrayElement = i - it->second.Slot();
access.byteSize = 1;
// descriptorStore will be set before this is returned by the replay, it's implicit
// from the type
ShaderStageMask feedbackStages =
ShaderStageMask((slotsData[i] & magicFeedbackStageMask) >> magicFeedbackStageShift);
access.type = DescriptorType(slotsData[i] & magicFeedbackTypeMask);
// add a usage for each stage that reported it used
for(ShaderStage stage : values<ShaderStage>())
{
if(MaskForStage(stage) & feedbackStages)
{
access.stage = stage;
result.access.push_back(access);
}
}
}
}
continue;
}
// ignore static used slots, we don't need these for the descriptor access representation
if(it->second.numDescriptors == 1)
continue;
DescriptorAccess access = it->second.access;
D3D12_SHADER_VISIBILITY visibility;
switch(access.stage)
{
case ShaderStage::Vertex: visibility = D3D12_SHADER_VISIBILITY_VERTEX; break;
case ShaderStage::Hull: visibility = D3D12_SHADER_VISIBILITY_HULL; break;
case ShaderStage::Domain: visibility = D3D12_SHADER_VISIBILITY_DOMAIN; break;
case ShaderStage::Geometry: visibility = D3D12_SHADER_VISIBILITY_GEOMETRY; break;
case ShaderStage::Pixel: visibility = D3D12_SHADER_VISIBILITY_PIXEL; break;
case ShaderStage::Amplification:
visibility = D3D12_SHADER_VISIBILITY_AMPLIFICATION;
break;
case ShaderStage::Mesh: visibility = D3D12_SHADER_VISIBILITY_MESH; break;
case ShaderStage::Compute: visibility = D3D12_SHADER_VISIBILITY_ALL; break;
case ShaderStage::Count:
default: RDCERR("Invalid shader stage"); continue;
}
D3D12_DESCRIPTOR_RANGE_TYPE rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
if(IsConstantBlockDescriptor(access.type))
rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
else if(IsConstantBlockDescriptor(access.type))
rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
else if(IsReadOnlyDescriptor(access.type))
rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
else if(IsReadWriteDescriptor(access.type))
rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
for(uint32_t i = 0; i < it->second.numDescriptors; i++)
{
const uint32_t baseSlot = it->second.Slot();
const uint32_t arrayIndex = i;
if(slotsData[baseSlot + arrayIndex])
{
access.arrayElement = i;
rdctie(access.byteSize, access.byteOffset) = FindMatchingRootParameter(
&modsig, visibility, rangeType, it->first.space, it->first.bind);
access.byteOffset += access.arrayElement;
if(access.byteSize != ~0U)
result.access.push_back(access);
}
}
}
std::sort(result.used.begin(), result.used.end());
}
else if((pipelinestats->VSInvocations == 0 || !dynamicAccessPerStage[0]) &&
(pipelinestats->HSInvocations == 0 || !dynamicAccessPerStage[1]) &&
(pipelinestats->DSInvocations == 0 || !dynamicAccessPerStage[2]) &&
(pipelinestats->GSInvocations == 0 || !dynamicAccessPerStage[3]) &&
(pipelinestats->PSInvocations == 0 || !dynamicAccessPerStage[4]) &&
(pipelinestats->CSInvocations == 0 || !dynamicAccessPerStage[5]))
{
RDCDEBUG(
"No results from shader feedback but no dynamically-accessing shaders were actually "
"invoked");
result.valid = true;
}
else
{
RDCERR("Didn't get valid feedback identifier. Expected %08x got %08x", magicFeedbackValue,
slotsData[0]);
}
}
return true;
}
void D3D12Replay::ClearFeedbackCache()
{
m_BindlessFeedback.Usage.clear();
}
#if ENABLED(ENABLE_UNIT_TESTS) && 0
#include "catch/catch.hpp"
TEST_CASE("DO NOT COMMIT - convenience test", "[dxbc]")
{
// this test loads a file from disk and does a no-op edit pass on it then an annotation pass on
// it. 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);
bytebuf editedBlob;
{
DXBC::DXBCContainer dxbc(buf, rdcstr(), GraphicsAPI::D3D12, ~0U, ~0U);
DXIL::ProgramEditor editor(&dxbc, 1234, editedBlob);
}
{
DXBC::DXBCContainer container(editedBlob, rdcstr(), GraphicsAPI::D3D12, ~0U, ~0U);
rdcstr disasm = container.GetDisassembly();
RDCLOG("no edits - %s", disasm.c_str());
}
{
WrappedID3D12Device device(NULL, D3D12InitParams(), false);
D3D12_SHADER_BYTECODE desc;
desc.BytecodeLength = buf.size();
desc.pShaderBytecode = buf.data();
WrappedID3D12PipelineState::ShaderEntry shad(desc, &device);
std::map<D3D12FeedbackKey, D3D12FeedbackSlot> slots;
uint32_t numSlots = 4;
AddArraySlots(&shad, 123456, 1000000, slots, numSlots, editedBlob, desc);
}
{
DXBC::DXBCContainer container(editedBlob, rdcstr(), GraphicsAPI::D3D12, ~0U, ~0U);
rdcstr disasm = container.GetDisassembly();
RDCLOG("annotated - %s", disasm.c_str());
}
}
#endif