diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index 90ca01b9f..027175b84 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -61,6 +61,14 @@ public: // every ID's variable, if a pointer it may be pointing at a ShaderVariable stored elsewhere DenseIdMap 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> pointersForId; + // the list of IDs that are currently valid and live rdcarray 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 &indices); + DebugAPIWrapper *apiWrapper = NULL; GlobalState global; @@ -114,11 +129,16 @@ private: rdcarray memberNames; - rdcstr GetRawName(Id id); + rdcstr GetRawName(Id id) const; rdcstr GetHumanName(Id id); std::set usedNames; std::map 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 diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 610050d02..97d0457aa 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -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 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 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 &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()); }