mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
Make pointer read/write methods return DeviceOpResult
ThreadState DeviceOpResult ReadPointerValue(Id pointer, ShaderVariable &ret); Debugger DeviceOpResult ReadFromPointer(const ShaderVariable &ptr, ShaderVariable &ret) const; DeviceOpResult GetPointerValue(const ShaderVariable &v, ShaderVariable &ret) const;
This commit is contained in:
@@ -356,16 +356,22 @@ DeviceOpResult ThreadState::WritePointerValue(Id pointer, const ShaderVariable &
|
||||
{
|
||||
// if this is a write to a SSBO pointer, don't record any alias changes, just record a no-op
|
||||
// change to this pointer
|
||||
basechange.after = basechange.before = debugger.GetPointerValue(ids[pointer]);
|
||||
if(debugger.GetPointerValue(ids[pointer], basechange.before) == DeviceOpResult::NeedsDevice)
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
basechange.after = basechange.before;
|
||||
if(debugger.WriteThroughPointer(var, val) == DeviceOpResult::NeedsDevice)
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
pendingDebugState.changes.push_back(basechange);
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
rdcarray<ShaderVariableChange> changes;
|
||||
basechange.before = debugger.GetPointerValue(ids[ptrid]);
|
||||
DeviceOpResult opResult;
|
||||
// Check if the base is available (cached), if it is available then so are all its aliases.
|
||||
opResult = debugger.GetPointerValue(ids[ptrid], basechange.before);
|
||||
if(opResult == DeviceOpResult::NeedsDevice)
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
|
||||
rdcarray<ShaderVariableChange> changes;
|
||||
rdcarray<Id> &pointers = pointersForId[ptrid];
|
||||
|
||||
changes.resize(pointers.size());
|
||||
@@ -375,26 +381,32 @@ DeviceOpResult ThreadState::WritePointerValue(Id pointer, const ShaderVariable &
|
||||
{
|
||||
Id id = pointers[i];
|
||||
if(id != ptrid && live.contains(id))
|
||||
changes[i].before = debugger.GetPointerValue(ids[id]);
|
||||
{
|
||||
opResult = debugger.GetPointerValue(ids[id], changes[i].before);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
if(debugger.WriteThroughPointer(var, val) == DeviceOpResult::NeedsDevice)
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
opResult = debugger.WriteThroughPointer(var, val);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
|
||||
// now evaluate the value after
|
||||
for(size_t i = 0; i < pointers.size(); i++)
|
||||
{
|
||||
Id id = pointers[i];
|
||||
if(id != ptrid && live.contains(id))
|
||||
changes[i].after = debugger.GetPointerValue(ids[id]);
|
||||
{
|
||||
opResult = debugger.GetPointerValue(ids[id], changes[i].after);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
// For GSM memory update the global data as well as the local cache, do not send the changes to the UI
|
||||
auto gsmPtrIt = gsmPointers.find(pointer);
|
||||
if(gsmPtrIt != gsmPointers.end())
|
||||
{
|
||||
if(debugger.WriteThroughPointer(gsmPtrIt->second, val) == DeviceOpResult::NeedsDevice)
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
opResult = debugger.WriteThroughPointer(gsmPtrIt->second, val);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
}
|
||||
|
||||
// if the pointer we're writing is one of the aliased pointers, be sure we add it even if
|
||||
@@ -419,7 +431,8 @@ DeviceOpResult ThreadState::WritePointerValue(Id pointer, const ShaderVariable &
|
||||
|
||||
// always add a change for the base storage variable written itself, even if that's a no-op.
|
||||
// This one is not included in any of the pointers lists above
|
||||
basechange.after = debugger.GetPointerValue(ids[ptrid]);
|
||||
opResult = debugger.GetPointerValue(ids[ptrid], basechange.after);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
|
||||
// if this is the first local write, mark this variable as becoming alive here, instead of at
|
||||
// its declaration
|
||||
@@ -439,9 +452,9 @@ DeviceOpResult ThreadState::WritePointerValue(Id pointer, const ShaderVariable &
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
ShaderVariable ThreadState::ReadPointerValue(Id pointer)
|
||||
DeviceOpResult ThreadState::ReadPointerValue(Id pointer, ShaderVariable &ret)
|
||||
{
|
||||
return debugger.ReadFromPointer(GetSrc(pointer));
|
||||
return debugger.ReadFromPointer(GetSrc(pointer), ret);
|
||||
}
|
||||
|
||||
void ThreadState::DebugBreak()
|
||||
@@ -456,6 +469,22 @@ void ThreadState::SetDst(Id id, const ShaderVariable &val)
|
||||
if(IsPendingResultPending())
|
||||
return;
|
||||
|
||||
ShaderVariable cur = val;
|
||||
cur.name = GetRawName(id);
|
||||
|
||||
ShaderVariable afterVal;
|
||||
DeviceOpResult opResult;
|
||||
if(m_State)
|
||||
{
|
||||
// Check if the variable is available (cached)
|
||||
opResult = debugger.GetPointerValue(cur, afterVal);
|
||||
if(opResult == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(m_State && ContainsNaNInf(val))
|
||||
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf;
|
||||
|
||||
@@ -466,8 +495,7 @@ void ThreadState::SetDst(Id id, const ShaderVariable &val)
|
||||
if(prev.name.empty() && prev.type == VarType::Unknown)
|
||||
callstack.back()->idsCreated.push_back(id);
|
||||
|
||||
ids[id] = val;
|
||||
ids[id].name = GetRawName(id);
|
||||
ids[id] = cur;
|
||||
|
||||
lastWrite[id] = m_State ? m_State->stepIndex : nextInstruction;
|
||||
|
||||
@@ -494,8 +522,12 @@ void ThreadState::SetDst(Id id, const ShaderVariable &val)
|
||||
{
|
||||
ShaderVariableChange change;
|
||||
if(wasLive)
|
||||
change.before = debugger.GetPointerValue(prev);
|
||||
change.after = debugger.GetPointerValue(ids[id]);
|
||||
{
|
||||
// The variable was live and written to, it should be cached
|
||||
opResult = debugger.GetPointerValue(prev, change.before);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
}
|
||||
change.after = afterVal;
|
||||
pendingDebugState.changes.push_back(change);
|
||||
}
|
||||
}
|
||||
@@ -511,12 +543,14 @@ void ThreadState::ProcessScopeChange(const rdcarray<Id> &oldLive, const rdcarray
|
||||
|
||||
const rdcarray<Id> &liveGlobals = debugger.GetLiveGlobals();
|
||||
|
||||
ShaderVariable val;
|
||||
for(const Id &id : oldLive)
|
||||
{
|
||||
if(liveGlobals.contains(id))
|
||||
continue;
|
||||
|
||||
pendingDebugState.changes.push_back({debugger.GetPointerValue(ids[id])});
|
||||
RDCASSERTEQUAL(debugger.GetPointerValue(ids[id], val), DeviceOpResult::Succeeded);
|
||||
pendingDebugState.changes.push_back({val});
|
||||
|
||||
if(ids[id].type == VarType::GPUPointer && !debugger.IsOpaquePointer(ids[id]) &&
|
||||
!debugger.IsPhysicalPointer(ids[id]))
|
||||
@@ -532,7 +566,8 @@ void ThreadState::ProcessScopeChange(const rdcarray<Id> &oldLive, const rdcarray
|
||||
if(liveGlobals.contains(id))
|
||||
continue;
|
||||
|
||||
pendingDebugState.changes.push_back({ShaderVariable(), debugger.GetPointerValue(ids[id])});
|
||||
RDCASSERTEQUAL(debugger.GetPointerValue(ids[id], val), DeviceOpResult::Succeeded);
|
||||
pendingDebugState.changes.push_back({ShaderVariable(), val});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,7 +866,13 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
(void)load.memoryAccess;
|
||||
|
||||
// get the pointer value, evaluate it (i.e. dereference) and store the result
|
||||
SetDst(load.result, ReadPointerValue(load.pointer));
|
||||
ShaderVariable val;
|
||||
if(ReadPointerValue(load.pointer, val) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
SetDst(load.result, val);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -843,7 +884,10 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
(void)store.memoryAccess;
|
||||
|
||||
if(WritePointerValue(store.pointer, GetSrc(store.object)) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -855,8 +899,19 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
(void)copy.memoryAccess0;
|
||||
(void)copy.memoryAccess1;
|
||||
|
||||
if(WritePointerValue(copy.target, ReadPointerValue(copy.source)) == DeviceOpResult::NeedsDevice)
|
||||
ShaderVariable val;
|
||||
{
|
||||
if(ReadPointerValue(copy.source, val) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(WritePointerValue(copy.target, val) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -948,7 +1003,14 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
ShaderVariable result;
|
||||
result.rows = result.columns = 1;
|
||||
|
||||
ShaderBindIndex bind = debugger.GetPointerValue(structPointer).GetBindIndex();
|
||||
ShaderVariable val;
|
||||
if(debugger.GetPointerValue(structPointer, val) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
|
||||
ShaderBindIndex bind = val.GetBindIndex();
|
||||
|
||||
uint64_t byteLen = debugger.GetAPIWrapper()->GetBufferLength(bind) - offset;
|
||||
|
||||
@@ -1080,7 +1142,9 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
debugger.MakeCompositePointer(ids[extract.composite], extract.composite, extract.indexes);
|
||||
|
||||
// then evaluate it, to get the extracted value
|
||||
SetDst(extract.result, debugger.ReadFromPointer(ptr));
|
||||
ShaderVariable val;
|
||||
RDCASSERTEQUAL(debugger.ReadFromPointer(ptr, val), DeviceOpResult::Succeeded);
|
||||
SetDst(extract.result, val);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -4055,6 +4119,9 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
{
|
||||
if(hasReturnValueData)
|
||||
SetDst(call.result, returnValue);
|
||||
if(IsPendingResultPending())
|
||||
break;
|
||||
|
||||
returnValue = ShaderVariable();
|
||||
hasReturnValueData = false;
|
||||
// The instruction after a function call is defined to be a convergence point, mark that we entered it
|
||||
@@ -4139,11 +4206,18 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
// read/write texel use.
|
||||
OpImageTexelPointer ptr(it);
|
||||
|
||||
ShaderVariable val;
|
||||
if(ReadPointerValue(ptr.image, val) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
|
||||
ShaderVariable result;
|
||||
result.rows = 0;
|
||||
result.columns = 0;
|
||||
result.type = VarType::Struct;
|
||||
result.members = {ReadPointerValue(ptr.image), GetSrc(ptr.coordinate), GetSrc(ptr.sample)};
|
||||
result.members = {val, GetSrc(ptr.coordinate), GetSrc(ptr.sample)};
|
||||
result.members[0].name = "image";
|
||||
result.members[1].name = "coord";
|
||||
result.members[2].name = "sample";
|
||||
@@ -4164,7 +4238,11 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
result = ReadPointerValue(load.pointer);
|
||||
if(ReadPointerValue(load.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4204,7 +4282,10 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
if(WritePointerValue(store.pointer, value) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4232,9 +4313,16 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
result = ReadPointerValue(excg.pointer);
|
||||
if(WritePointerValue(excg.pointer, value) == DeviceOpResult::NeedsDevice)
|
||||
if(ReadPointerValue(excg.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
if(WritePointerValue(excg.pointer, value) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4286,7 +4374,11 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
result = ReadPointerValue(cmpexcg.pointer);
|
||||
if(ReadPointerValue(cmpexcg.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4309,7 +4401,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
}
|
||||
}
|
||||
|
||||
SetDst(cmpexcg.result, result);
|
||||
ShaderVariable ssaResult(result);
|
||||
|
||||
uint64_t resultVal = 0, compareVal = 0;
|
||||
|
||||
@@ -4329,7 +4421,10 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
if(WritePointerValue(cmpexcg.pointer, value) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4342,6 +4437,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
}
|
||||
}
|
||||
}
|
||||
SetDst(cmpexcg.result, ssaResult);
|
||||
break;
|
||||
}
|
||||
case Op::AtomicIIncrement:
|
||||
@@ -4358,7 +4454,11 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
result = ReadPointerValue(atomic.pointer);
|
||||
if(ReadPointerValue(atomic.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4381,7 +4481,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
}
|
||||
}
|
||||
|
||||
SetDst(atomic.result, result);
|
||||
ShaderVariable ssaResult(result);
|
||||
|
||||
{
|
||||
#undef _IMPL
|
||||
@@ -4398,7 +4498,10 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
if(WritePointerValue(atomic.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4409,6 +4512,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetDst(atomic.result, ssaResult);
|
||||
break;
|
||||
}
|
||||
case Op::AtomicFAddEXT:
|
||||
@@ -4436,7 +4540,11 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
result = ReadPointerValue(atomic.pointer);
|
||||
if(ReadPointerValue(atomic.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4459,7 +4567,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
}
|
||||
}
|
||||
|
||||
SetDst(atomic.result, result);
|
||||
ShaderVariable ssaResult(result);
|
||||
|
||||
if(opdata.op == Op::AtomicIAdd)
|
||||
{
|
||||
@@ -4547,7 +4655,10 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
if(ptr.members.empty())
|
||||
{
|
||||
if(WritePointerValue(atomic.pointer, result) == DeviceOpResult::NeedsDevice)
|
||||
{
|
||||
SetStepNeedsDeviceThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4558,6 +4669,8 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SetDst(atomic.result, ssaResult);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,11 +95,11 @@ public:
|
||||
|
||||
virtual ResourceId GetShaderID() = 0;
|
||||
|
||||
virtual uint64_t GetBufferLength(ShaderBindIndex bind) = 0;
|
||||
virtual uint64_t GetBufferLength(const ShaderBindIndex &bind) = 0;
|
||||
|
||||
virtual void ReadBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
||||
virtual void ReadBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
||||
void *dst) = 0;
|
||||
virtual void WriteBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
||||
virtual void WriteBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
||||
const void *src) = 0;
|
||||
|
||||
virtual void ReadAddress(uint64_t address, uint64_t byteSize, void *dst) = 0;
|
||||
@@ -115,6 +115,8 @@ public:
|
||||
|
||||
virtual uint32_t GetThreadProperty(uint32_t threadIndex, ThreadProperty prop) = 0;
|
||||
virtual bool IsImageCached(const ShaderBindIndex &bind) = 0;
|
||||
virtual bool IsBufferCached(const ShaderBindIndex &bind) = 0;
|
||||
virtual bool IsBufferCached(uint64_t address) = 0;
|
||||
|
||||
enum TextureType
|
||||
{
|
||||
@@ -336,7 +338,7 @@ struct ThreadState
|
||||
|
||||
const ShaderVariable &GetSrc(Id id) const;
|
||||
DeviceOpResult WritePointerValue(Id pointer, const ShaderVariable &val);
|
||||
ShaderVariable ReadPointerValue(Id pointer);
|
||||
DeviceOpResult ReadPointerValue(Id pointer, ShaderVariable &ret);
|
||||
|
||||
void DebugBreak();
|
||||
|
||||
@@ -624,8 +626,8 @@ public:
|
||||
rdcstr GetHumanName(Id id) const;
|
||||
void AllocateVariable(Id id, Id typeId, ShaderVariable &outVar) const;
|
||||
|
||||
ShaderVariable ReadFromPointer(const ShaderVariable &v) const;
|
||||
ShaderVariable GetPointerValue(const ShaderVariable &v) const;
|
||||
DeviceOpResult ReadFromPointer(const ShaderVariable &ptr, ShaderVariable &ret) const;
|
||||
DeviceOpResult GetPointerValue(const ShaderVariable &v, ShaderVariable &ret) const;
|
||||
uint64_t GetPointerByteOffset(const ShaderVariable &ptr) const;
|
||||
DebugAPIWrapper::TextureType GetTextureType(const ShaderVariable &img) const;
|
||||
ShaderVariable MakePointerVariable(Id id, const ShaderVariable *v, uint8_t scalar0 = 0xff,
|
||||
|
||||
@@ -2597,15 +2597,25 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
|
||||
}
|
||||
|
||||
// globals won't be filled out by entering the entry point, ensure their change is registered.
|
||||
ShaderVariable val;
|
||||
DeviceOpResult opResult;
|
||||
for(const Id &v : liveGlobals)
|
||||
initial.changes.push_back({ShaderVariable(), GetPointerValue(active.ids[v])});
|
||||
{
|
||||
opResult = GetPointerValue(active.ids[v], val);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
initial.changes.push_back({ShaderVariable(), val});
|
||||
}
|
||||
|
||||
if(m_DebugInfo.valid)
|
||||
{
|
||||
// debug info can refer to constants for source variable values. Add an initial change for any
|
||||
// that are so referenced
|
||||
for(const Id &v : m_DebugInfo.constants)
|
||||
initial.changes.push_back({ShaderVariable(), GetPointerValue(active.ids[v])});
|
||||
{
|
||||
opResult = GetPointerValue(active.ids[v], val);
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
initial.changes.push_back({ShaderVariable(), val});
|
||||
}
|
||||
}
|
||||
|
||||
ret.push_back(std::move(initial));
|
||||
@@ -3143,36 +3153,39 @@ DebugAPIWrapper::TextureType Debugger::GetTextureType(const ShaderVariable &img)
|
||||
return getTextureType(img);
|
||||
}
|
||||
|
||||
ShaderVariable Debugger::GetPointerValue(const ShaderVariable &ptr) const
|
||||
DeviceOpResult Debugger::GetPointerValue(const ShaderVariable &ptr, ShaderVariable &ret) const
|
||||
{
|
||||
// opaque pointers display as their inner value
|
||||
if(IsOpaquePointer(ptr))
|
||||
{
|
||||
const ShaderVariable *inner = getPointer(ptr);
|
||||
ShaderVariable ret = *inner;
|
||||
ret = *inner;
|
||||
ret.name = ptr.name;
|
||||
// inherit any array index from the pointer
|
||||
ShaderBindIndex bind = ret.GetBindIndex();
|
||||
bind.arrayElement = getBindArrayIndex(ptr);
|
||||
ret.SetBindIndex(bind);
|
||||
return ret;
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
// physical pointers which haven't been dereferenced are returned as-is, they're ready for display
|
||||
else if(IsPhysicalPointer(ptr) && !checkPointerFlags(ptr, PointerFlags::DereferencedPhysical))
|
||||
{
|
||||
return ptr;
|
||||
ret = ptr;
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
// every other kind of pointer displays as its contents
|
||||
return ReadFromPointer(ptr);
|
||||
return ReadFromPointer(ptr, ret);
|
||||
}
|
||||
|
||||
ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
DeviceOpResult Debugger::ReadFromPointer(const ShaderVariable &ptr, ShaderVariable &ret) const
|
||||
{
|
||||
if(ptr.type != VarType::GPUPointer)
|
||||
return ptr;
|
||||
{
|
||||
ret = ptr;
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
ShaderVariable ret;
|
||||
// values for setting up pointer reads, either from a physical pointer or from an opaque pointer
|
||||
rdcspv::Id typeId;
|
||||
Decorations parentDecorations;
|
||||
@@ -3182,7 +3195,10 @@ ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
std::function<void(uint64_t offset, uint64_t size, void *dst)> pointerReadCallback;
|
||||
if(IsPhysicalPointer(ptr))
|
||||
{
|
||||
CHECK_DEBUGGER_THREAD();
|
||||
baseAddress = ptr.GetPointer().pointer;
|
||||
if(!IsDeviceThread() && !apiWrapper->IsBufferCached(baseAddress))
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
|
||||
if(checkPointerFlags(ptr, PointerFlags::DereferencedPhysical))
|
||||
typeId = getBufferTypeId(ptr);
|
||||
else
|
||||
@@ -3203,7 +3219,6 @@ ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
Decorations::Flags(parentDecorations.flags | Decorations::HasMatrixStride);
|
||||
parentDecorations.matrixStride = varMatrixStride;
|
||||
}
|
||||
baseAddress = ptr.GetPointer().pointer;
|
||||
pointerReadCallback = [this, baseAddress](uint64_t offset, uint64_t size, void *dst) {
|
||||
apiWrapper->ReadAddress(baseAddress + offset, size, dst);
|
||||
};
|
||||
@@ -3213,11 +3228,13 @@ ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
const ShaderVariable *inner = getPointer(ptr);
|
||||
if(inner->type == VarType::ReadWriteResource && checkPointerFlags(*inner, PointerFlags::SSBO))
|
||||
{
|
||||
CHECK_DEBUGGER_THREAD();
|
||||
typeId = getBufferTypeId(ptr);
|
||||
byteOffset = getByteOffset(ptr);
|
||||
bind = inner->GetBindIndex();
|
||||
bind.arrayElement = getBindArrayIndex(ptr);
|
||||
if(!IsDeviceThread() && !apiWrapper->IsBufferCached(bind))
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
|
||||
uint32_t varMatrixStride = getMatrixStride(ptr);
|
||||
if(varMatrixStride != 0)
|
||||
{
|
||||
@@ -3320,7 +3337,7 @@ ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
rdcstr(), readCallback);
|
||||
|
||||
ret.name = ptr.name;
|
||||
return ret;
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
// this is the case of 'reading' from a pointer where the data is entirely contained within the
|
||||
@@ -3384,7 +3401,7 @@ ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return DeviceOpResult::Succeeded;
|
||||
}
|
||||
|
||||
Id Debugger::GetPointerBaseId(const ShaderVariable &ptr) const
|
||||
@@ -3450,6 +3467,10 @@ DeviceOpResult Debugger::WriteThroughPointer(ShaderVariable &ptr, const ShaderVa
|
||||
|
||||
if(IsPhysicalPointer(ptr))
|
||||
{
|
||||
baseAddress = ptr.GetPointer().pointer;
|
||||
if(!IsDeviceThread() && !apiWrapper->IsBufferCached(baseAddress))
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
|
||||
if(checkPointerFlags(ptr, PointerFlags::DereferencedPhysical))
|
||||
typeId = getBufferTypeId(ptr);
|
||||
else
|
||||
@@ -3468,7 +3489,6 @@ DeviceOpResult Debugger::WriteThroughPointer(ShaderVariable &ptr, const ShaderVa
|
||||
Decorations::Flags(parentDecorations.flags | Decorations::HasMatrixStride);
|
||||
parentDecorations.matrixStride = varMatrixStride;
|
||||
}
|
||||
baseAddress = ptr.GetPointer().pointer;
|
||||
pointerWriteCallback = [this, baseAddress](uint64_t offset, uint64_t size, const void *src) {
|
||||
apiWrapper->WriteAddress(baseAddress + offset, size, src);
|
||||
};
|
||||
@@ -3482,6 +3502,9 @@ DeviceOpResult Debugger::WriteThroughPointer(ShaderVariable &ptr, const ShaderVa
|
||||
byteOffset = getByteOffset(ptr);
|
||||
bind = inner->GetBindIndex();
|
||||
bind.arrayElement = getBindArrayIndex(ptr);
|
||||
if(!IsDeviceThread() && !apiWrapper->IsBufferCached(bind))
|
||||
return DeviceOpResult::NeedsDevice;
|
||||
|
||||
uint32_t varMatrixStride = getMatrixStride(ptr);
|
||||
if(varMatrixStride != 0)
|
||||
{
|
||||
@@ -4737,7 +4760,9 @@ void Debugger::InternalStepThread(uint32_t lane, rdcarray<ShaderDebugState> *ret
|
||||
{
|
||||
thread.live.erase(l);
|
||||
ShaderVariableChange change;
|
||||
change.before = GetPointerValue(thread.ids[id]);
|
||||
DeviceOpResult opResult = GetPointerValue(thread.ids[id], change.before);
|
||||
// The variable was live and written to, it should be cached
|
||||
RDCASSERTEQUAL(opResult, DeviceOpResult::Succeeded);
|
||||
activeDebugState.changes.push_back(change);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -295,48 +295,78 @@ public:
|
||||
|
||||
virtual ResourceId GetShaderID() override { return m_ShaderID; }
|
||||
|
||||
virtual uint64_t GetBufferLength(ShaderBindIndex bind) override
|
||||
virtual uint64_t GetBufferLength(const ShaderBindIndex &bind) override
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
return PopulateBuffer(bind).size();
|
||||
rdcspv::DeviceOpResult opResult;
|
||||
size_t length = 0;
|
||||
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
||||
bool succeeded = BufferFunction(
|
||||
bind, [&length](bytebuf *data) { length = data->size(); }, opResult);
|
||||
RDCASSERT(succeeded);
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
||||
return length;
|
||||
}
|
||||
|
||||
virtual void ReadBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
||||
virtual void ReadBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
||||
void *dst) override
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
const bytebuf &data = PopulateBuffer(bind);
|
||||
|
||||
if(offset + byteSize <= data.size())
|
||||
memcpy(dst, data.data() + (size_t)offset, (size_t)byteSize);
|
||||
rdcspv::DeviceOpResult opResult;
|
||||
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
||||
bool succeeded = BufferFunction(
|
||||
bind,
|
||||
[offset, byteSize, dst](bytebuf *data) {
|
||||
if(offset + byteSize <= data->size())
|
||||
memcpy(dst, data->data() + (size_t)offset, (size_t)byteSize);
|
||||
},
|
||||
opResult);
|
||||
RDCASSERT(succeeded);
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
||||
}
|
||||
|
||||
virtual void WriteBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
||||
virtual void WriteBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
||||
const void *src) override
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
bytebuf &data = PopulateBuffer(bind);
|
||||
|
||||
if(offset + byteSize <= data.size())
|
||||
memcpy(data.data() + (size_t)offset, src, (size_t)byteSize);
|
||||
rdcspv::DeviceOpResult opResult;
|
||||
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
||||
bool succeeded = BufferFunction(
|
||||
bind,
|
||||
[offset, byteSize, src](bytebuf *data) {
|
||||
if(offset + byteSize <= data->size())
|
||||
memcpy(data->data() + (size_t)offset, src, (size_t)byteSize);
|
||||
},
|
||||
opResult);
|
||||
RDCASSERT(succeeded);
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
||||
}
|
||||
|
||||
virtual void ReadAddress(uint64_t address, uint64_t byteSize, void *dst) override
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
size_t offset;
|
||||
const bytebuf &data = PopulateBuffer(address, offset);
|
||||
if(offset + byteSize <= data.size())
|
||||
memcpy(dst, data.data() + offset, (size_t)byteSize);
|
||||
rdcspv::DeviceOpResult opResult;
|
||||
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
||||
bool succeeded = BufferFunction(
|
||||
address,
|
||||
[byteSize, dst](bytebuf *data, size_t offset) {
|
||||
if(offset + byteSize <= data->size())
|
||||
memcpy(dst, data->data() + offset, (size_t)byteSize);
|
||||
},
|
||||
opResult);
|
||||
RDCASSERT(succeeded);
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
||||
}
|
||||
|
||||
virtual void WriteAddress(uint64_t address, uint64_t byteSize, const void *src) override
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
size_t offset;
|
||||
bytebuf &data = PopulateBuffer(address, offset);
|
||||
if(offset + byteSize <= data.size())
|
||||
memcpy(data.data() + offset, src, (size_t)byteSize);
|
||||
rdcspv::DeviceOpResult opResult;
|
||||
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
||||
bool succeeded = BufferFunction(
|
||||
address,
|
||||
[byteSize, src](bytebuf *data, size_t offset) {
|
||||
if(offset + byteSize <= data->size())
|
||||
memcpy(data->data() + offset, src, (size_t)byteSize);
|
||||
},
|
||||
opResult);
|
||||
RDCASSERT(succeeded);
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
@@ -1768,6 +1798,7 @@ private:
|
||||
typedef rdcpair<ResourceId, float> SamplerBiasKey;
|
||||
std::map<SamplerBiasKey, VkSampler> m_BiasSamplers;
|
||||
|
||||
Threading::RWLock bufferCacheLock;
|
||||
std::map<ShaderBindIndex, bytebuf> bufferCache;
|
||||
|
||||
VkCommandBuffer queuedOpCmdBuffer = VK_NULL_HANDLE;
|
||||
@@ -1866,33 +1897,175 @@ private:
|
||||
return m_SamplerDescriptors[a];
|
||||
}
|
||||
|
||||
bytebuf &PopulateBuffer(uint64_t address, size_t &offs)
|
||||
// Called from any thread
|
||||
ShaderBindIndex GenerateBufferBind(const uint64_t address, size_t &offs)
|
||||
{
|
||||
ResourceId id;
|
||||
uint64_t ptrOffs;
|
||||
m_pDriver->GetResIDFromAddr(address, id, ptrOffs);
|
||||
offs = size_t(ptrOffs);
|
||||
|
||||
ShaderBindIndex bind;
|
||||
bind.arrayElement = (address - offs) & 0xFFFFFFFFU;
|
||||
|
||||
return bind;
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
bool IsBufferCached(const ShaderBindIndex &bind) override
|
||||
{
|
||||
SCOPED_READLOCK(bufferCacheLock);
|
||||
return bufferCache.find(bind) != bufferCache.end();
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
bool IsBufferCached(uint64_t address) override
|
||||
{
|
||||
size_t offs = 0;
|
||||
ShaderBindIndex bind = GenerateBufferBind(address, offs);
|
||||
return IsBufferCached(bind);
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
bytebuf *GetBufferDataFromCache(const ShaderBindIndex &bind, rdcspv::DeviceOpResult &opResult)
|
||||
{
|
||||
// Calling function responsible for acquiring bufferCache Read lock
|
||||
auto findIt = bufferCache.find(bind);
|
||||
if(findIt != bufferCache.end())
|
||||
{
|
||||
opResult = rdcspv::DeviceOpResult::Succeeded;
|
||||
return &findIt->second;
|
||||
}
|
||||
|
||||
opResult = rdcspv::DeviceOpResult::Failed;
|
||||
|
||||
// Not in the cache : populate must happen on the device thread
|
||||
if(!IsDeviceThread())
|
||||
opResult = rdcspv::DeviceOpResult::NeedsDevice;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
bool BufferFunction(const ShaderBindIndex &bind, const std::function<void(bytebuf *data)> &func,
|
||||
rdcspv::DeviceOpResult &opResult)
|
||||
{
|
||||
bool isCached = false;
|
||||
{
|
||||
SCOPED_READLOCK(bufferCacheLock);
|
||||
isCached = GetBufferDataFromCache(bind, opResult) != NULL;
|
||||
if(opResult == rdcspv::DeviceOpResult::NeedsDevice)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isCached)
|
||||
{
|
||||
// Add buffer data to the cache : cache should not be locked by this thread
|
||||
PopulateBuffer(bind);
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_READLOCK(bufferCacheLock);
|
||||
bytebuf *result = GetBufferDataFromCache(bind, opResult);
|
||||
if(result)
|
||||
{
|
||||
// Guarantee the buffer cache readlock whilst the function is called
|
||||
func(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Failed);
|
||||
opResult = rdcspv::DeviceOpResult::Failed;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
bool BufferFunction(uint64_t address, const std::function<void(bytebuf *data, size_t offset)> &func,
|
||||
rdcspv::DeviceOpResult &opResult)
|
||||
{
|
||||
size_t offs = 0;
|
||||
ShaderBindIndex bind = GenerateBufferBind(address, offs);
|
||||
bool isCached = false;
|
||||
{
|
||||
SCOPED_READLOCK(bufferCacheLock);
|
||||
isCached = GetBufferDataFromCache(bind, opResult) != NULL;
|
||||
if(opResult == rdcspv::DeviceOpResult::NeedsDevice)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isCached)
|
||||
{
|
||||
// Add buffer data to the cache : cache should not be locked by this thread
|
||||
PopulateBuffer(address, bind);
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_READLOCK(bufferCacheLock);
|
||||
bytebuf *result = GetBufferDataFromCache(bind, opResult);
|
||||
if(result)
|
||||
{
|
||||
// Guarantee the buffer cache readlock whilst the function is called
|
||||
func(result, offs);
|
||||
return true;
|
||||
}
|
||||
|
||||
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Failed);
|
||||
opResult = rdcspv::DeviceOpResult::Failed;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called from the replay manager thread (the debugger thread)
|
||||
void PopulateBuffer(uint64_t address, ShaderBindIndex bind)
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
// pick a non-overlapping bind namespace for direct pointer access
|
||||
ShaderBindIndex bind;
|
||||
ResourceId id;
|
||||
uint64_t ptrOffs;
|
||||
|
||||
m_pDriver->GetResIDFromAddr(address, id, ptrOffs);
|
||||
if(id == ResourceId())
|
||||
{
|
||||
ShaderBindIndex noBind;
|
||||
CHECK_DEVICE_THREAD();
|
||||
bind.arrayElement = 0;
|
||||
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
||||
auto insertIt = bufferCache.insert(std::make_pair(noBind, bytebuf()));
|
||||
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
||||
MessageSource::RuntimeWarning,
|
||||
StringFormat::Fmt("invalid or OOB pointer access detected ."));
|
||||
return insertIt.first->second;
|
||||
return;
|
||||
}
|
||||
bind.arrayElement = (address - offs) & 0xFFFFFFFFU;
|
||||
offs = size_t(ptrOffs);
|
||||
|
||||
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
||||
bytebuf &data = insertIt.first->second;
|
||||
if(insertIt.second)
|
||||
// if the resources might be dirty from side-effects from the action, replay back to right
|
||||
// before it.
|
||||
if(m_ResourcesDirty)
|
||||
{
|
||||
VkMarkerRegion region("un-dirtying resources");
|
||||
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
||||
m_ResourcesDirty = false;
|
||||
}
|
||||
|
||||
bytebuf data;
|
||||
m_pDriver->GetDebugManager()->GetBufferData(id, 0, 0, data);
|
||||
|
||||
{
|
||||
// Insert atomically with all the data filled in : to prevent race conditions
|
||||
SCOPED_WRITELOCK(bufferCacheLock);
|
||||
auto insertIt = bufferCache.insert(std::make_pair(bind, data));
|
||||
RDCASSERT(insertIt.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called from the replay manager thread (the debugger thread)
|
||||
void PopulateBuffer(const ShaderBindIndex &bind)
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
bytebuf data;
|
||||
|
||||
bool valid = true;
|
||||
const Descriptor &bufData = GetDescriptor("accessing buffer value", bind, valid);
|
||||
if(valid)
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
// if the resources might be dirty from side-effects from the action, replay back to right
|
||||
// before it.
|
||||
if(m_ResourcesDirty)
|
||||
@@ -1901,42 +2074,21 @@ private:
|
||||
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
||||
m_ResourcesDirty = false;
|
||||
}
|
||||
m_pDriver->GetDebugManager()->GetBufferData(id, 0, 0, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bytebuf &PopulateBuffer(ShaderBindIndex bind)
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
||||
bytebuf &data = insertIt.first->second;
|
||||
if(insertIt.second)
|
||||
{
|
||||
CHECK_DEVICE_THREAD();
|
||||
bool valid = true;
|
||||
const Descriptor &bufData = GetDescriptor("accessing buffer value", bind, valid);
|
||||
if(valid)
|
||||
if(bufData.resource != ResourceId())
|
||||
{
|
||||
// if the resources might be dirty from side-effects from the action, replay back to right
|
||||
// before it.
|
||||
if(m_ResourcesDirty)
|
||||
{
|
||||
VkMarkerRegion region("un-dirtying resources");
|
||||
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
||||
m_ResourcesDirty = false;
|
||||
}
|
||||
|
||||
if(bufData.resource != ResourceId())
|
||||
{
|
||||
m_pDriver->GetReplay()->GetBufferData(
|
||||
m_pDriver->GetResourceManager()->GetLiveID(bufData.resource), bufData.byteOffset,
|
||||
bufData.byteSize, data);
|
||||
}
|
||||
m_pDriver->GetReplay()->GetBufferData(
|
||||
m_pDriver->GetResourceManager()->GetLiveID(bufData.resource), bufData.byteOffset,
|
||||
bufData.byteSize, data);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
{
|
||||
// Insert atomically with all the data filled in : to prevent race conditions
|
||||
SCOPED_WRITELOCK(bufferCacheLock);
|
||||
auto insertIt = bufferCache.insert(std::make_pair(bind, data));
|
||||
RDCASSERT(insertIt.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called from the replay manager thread (the debugger thread)
|
||||
|
||||
Reference in New Issue
Block a user