Support SPIR-V pointers with our own pointers

* When we need a SPIR-V pointer we box up a real pointer to a ShaderVariable,
  and optionally column/scalar selectors if it's a pointer to a scalar within a
  vector or matrix.
* For globals in opaque storage classes we'll allocate our own ShaderVariables
  so that we can safely keep pointers to them. Otherwise IDs of temporary
  pointers (from OpAccessChain) should always be a strict subset of the lifetime
  of the ID it's pointing to, so we can keep the pointer around. The variables
  don't move and are persistently allocated so they won't be relocated from
  under us (we pre-allocate space for the IDs).
* Before passing back a ShaderVariable to the outside world, we evaluate its
  current value. Thus pointers are presented as if they are just variables that
  happen to update when their parent does.
This commit is contained in:
baldurk
2020-02-20 14:12:57 +00:00
parent c4037e9246
commit b4b100004f
2 changed files with 217 additions and 3 deletions
+21 -1
View File
@@ -61,6 +61,14 @@ public:
// every ID's variable, if a pointer it may be pointing at a ShaderVariable stored elsewhere
DenseIdMap<ShaderVariable> ids;
// for any allocated variables, a list of 'extra' pointers pointing to it. By default the actual
// storage of allocated variables is not directly accessible (it's stored in e.g. inputs, outputs,
// global constants, stack frame variables, etc). The ID for the allocating OpVariable is replaced
// with a pointer pointing to that storage. However more pointers can be generated with
// OpAccessChain etc, and these pointers must be listed as changed whenever the underlying Id
// changes (and vice-versa - a change via any of those pointers must update all other pointers).
SparseIdMap<rdcarray<Id>> pointersForId;
// the list of IDs that are currently valid and live
rdcarray<Id> live;
@@ -90,6 +98,13 @@ private:
virtual void PostParse();
virtual void RegisterOp(Iter it);
ShaderVariable EvaluatePointerVariable(const ShaderVariable &v) const;
ShaderVariable MakePointerVariable(Id id, const ShaderVariable *v, uint32_t scalar0 = ~0U,
uint32_t scalar1 = ~0U) const;
Id GetPointerBaseId(const ShaderVariable &v) const;
void WriteThroughPointer(const ShaderVariable &ptr, const ShaderVariable &val);
ShaderVariable MakeCompositePointer(const ShaderVariable &base, Id id, rdcarray<uint32_t> &indices);
DebugAPIWrapper *apiWrapper = NULL;
GlobalState global;
@@ -114,11 +129,16 @@ private:
rdcarray<MemberName> memberNames;
rdcstr GetRawName(Id id);
rdcstr GetRawName(Id id) const;
rdcstr GetHumanName(Id id);
std::set<rdcstr> usedNames;
std::map<Id, rdcstr> dynamicNames;
};
// this does a 'safe' value assignment, by doing parallel depth-first iteration of both variables
// and only copying the value itself. This ensures we don't change any locations that might be
// pointed to. Assignments should only ever be between compatible types so this should be safe.
void AssignValue(ShaderVariable &dst, const ShaderVariable &src);
}; // namespace rdcspv
@@ -28,6 +28,16 @@
namespace rdcspv
{
void AssignValue(ShaderVariable &dst, const ShaderVariable &src)
{
dst.value = src.value;
RDCASSERTEQUAL(dst.members.size(), src.members.size());
for(size_t i = 0; i < src.members.size(); i++)
AssignValue(dst.members[i], src.members[i]);
}
ThreadState::ThreadState(int workgroupIdx, GlobalState &globalState) : global(globalState)
{
workgroupIndex = workgroupIdx;
@@ -105,7 +115,7 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
ShaderDebugState initial;
for(const Id &v : active.live)
initial.changes.push_back({ShaderVariable(), active.ids[v]});
initial.changes.push_back({ShaderVariable(), EvaluatePointerVariable(active.ids[v])});
initial.sourceVars = sourceVars;
@@ -117,7 +127,191 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
return ret;
}
rdcstr Debugger::GetRawName(Id id)
ShaderVariable Debugger::MakePointerVariable(Id id, const ShaderVariable *v, uint32_t scalar0,
uint32_t scalar1) const
{
ShaderVariable var;
var.rows = var.columns = 1;
var.type = VarType::ULong;
var.name = GetRawName(id);
var.isPointer = true;
// encode the pointer into the first u64v
var.value.u64v[0] = (uint64_t)(uintptr_t)v;
// uv[1] overlaps with u64v[0], so start from [2] storing scalar indices
var.value.uv[2] = scalar0;
var.value.uv[3] = scalar1;
// store the base ID of the allocated storage in [4]
var.value.uv[4] = id.value();
return var;
}
ShaderVariable Debugger::MakeCompositePointer(const ShaderVariable &base, Id id,
rdcarray<uint32_t> &indices)
{
const ShaderVariable *leaf = &base;
// if the base is a plain value, we just start walking down the chain. If the base is a pointer
// though, we want to step down the chain in the underlying storage, so dereference first.
if(base.isPointer)
leaf = (const ShaderVariable *)(uintptr_t)base.value.u64v[0];
// first walk any struct member/array indices
size_t i = 0;
while(!leaf->members.empty())
{
RDCASSERT(i < indices.size(), i, indices.size());
leaf = &leaf->members[indices[i++]];
}
// apply any remaining scalar selectors
uint32_t scalar0 = ~0U, scalar1 = ~0U;
size_t remaining = indices.size() - i;
if(remaining == 2)
{
scalar0 = indices[i];
scalar1 = indices[i + 1];
}
else if(remaining == 1)
{
scalar0 = indices[i];
}
return MakePointerVariable(id, leaf, scalar0, scalar1);
}
ShaderVariable Debugger::EvaluatePointerVariable(const ShaderVariable &ptr) const
{
if(!ptr.isPointer)
return ptr;
ShaderVariable ret;
ret = *(const ShaderVariable *)(uintptr_t)ptr.value.u64v[0];
ret.name = ptr.name;
// we don't support pointers to scalars since our 'unit' of pointer is a ShaderVariable, so check
// if we have scalar indices to apply:
uint32_t scalar0 = ptr.value.uv[2];
uint32_t scalar1 = ptr.value.uv[3];
ShaderValue val;
if(ret.rows > 1)
{
// matrix case
if(scalar0 != ~0U && scalar1 != ~0U)
{
// two indices - selecting a scalar. scalar0 is the first index in the chain so it chooses
// column
if(VarTypeByteSize(ret.type) == 8)
val.u64v[0] = ret.value.u64v[scalar1 * ret.columns + scalar0];
else
val.uv[0] = ret.value.uv[scalar1 * ret.columns + scalar0];
// it's a scalar now, even if it was a matrix before
ret.rows = ret.columns = 1;
ret.value = val;
}
else if(scalar0 != ~0U)
{
// one index, selecting a column
for(uint32_t row = 0; row < ret.rows; row++)
{
if(VarTypeByteSize(ret.type) == 8)
val.u64v[0] = ret.value.u64v[row * ret.columns + scalar0];
else
val.uv[0] = ret.value.uv[row * ret.columns + scalar0];
}
// it's a vector now, even if it was a matrix before
ret.rows = 1;
ret.value = val;
}
}
else
{
// vector case, selecting a scalar (if anything)
if(scalar0 != ~0U)
{
if(VarTypeByteSize(ret.type) == 8)
val.u64v[0] = ret.value.u64v[scalar0];
else
val.uv[0] = ret.value.uv[scalar0];
// it's a scalar now, even if it was a matrix before
ret.columns = 1;
ret.value = val;
}
}
return ret;
}
Id Debugger::GetPointerBaseId(const ShaderVariable &ptr) const
{
RDCASSERT(ptr.isPointer);
// we stored the base ID in [4] so that it's always available regardless of access chains
return Id::fromWord(ptr.value.uv[4]);
}
void Debugger::WriteThroughPointer(const ShaderVariable &ptr, const ShaderVariable &val)
{
ShaderVariable *storage = (ShaderVariable *)(uintptr_t)ptr.value.u64v[0];
// we don't support pointers to scalars since our 'unit' of pointer is a ShaderVariable, so check
// if we have scalar indices to apply:
uint32_t scalar0 = ptr.value.uv[2];
uint32_t scalar1 = ptr.value.uv[3];
// in the common case we don't have scalar selectors. In this case just assign the value
if(scalar0 == ~0U && scalar1 == ~0U)
{
AssignValue(*storage, val);
}
else
{
// otherwise we need to store only the selected part of this pointer. We assume by SPIR-V
// validity rules that the incoming value matches the pointed value
if(storage->rows > 1)
{
// matrix case
if(scalar0 != ~0U && scalar1 != ~0U)
{
// two indices - selecting a scalar. scalar0 is the first index in the chain so it chooses
// column
if(VarTypeByteSize(storage->type) == 8)
storage->value.u64v[scalar1 * storage->columns + scalar0] = val.value.u64v[0];
else
storage->value.uv[scalar1 * storage->columns + scalar0] = val.value.uv[0];
}
else if(scalar0 != ~0U)
{
// one index, selecting a column
for(uint32_t row = 0; row < storage->rows; row++)
{
if(VarTypeByteSize(storage->type) == 8)
storage->value.u64v[row * storage->columns + scalar0] = val.value.u64v[0];
else
storage->value.uv[row * storage->columns + scalar0] = val.value.uv[0];
}
}
}
else
{
// vector case, selecting a scalar
if(VarTypeByteSize(storage->type) == 8)
storage->value.u64v[scalar0] = val.value.u64v[0];
else
storage->value.uv[scalar0] = val.value.uv[0];
}
}
}
rdcstr Debugger::GetRawName(Id id) const
{
return StringFormat::Fmt("_%u", id.value());
}