Make sure going between WRITING_IDLE and WRITING_CAPFRAME is atomic

* This fixes a bug we had before, where we'd go into CAPFRAME then do a
  bunch of work before clearing the frame record.
* The threading around this probably needs more careful thought - e.g.
  the snapshotting and chunk insertion on end capture.
This commit is contained in:
baldurk
2016-02-07 18:43:07 +01:00
parent 18bcd75db7
commit 31274c7c1c
6 changed files with 128 additions and 82 deletions
-2
View File
@@ -535,8 +535,6 @@ void WrappedVulkan::EndCaptureFrame(VkImage presentImage)
void WrappedVulkan::AttemptCapture()
{
m_State = WRITING_CAPFRAME;
{
RDCDEBUG("Attempting capture");
+2
View File
@@ -145,6 +145,8 @@ private:
vector<VkResourceRecord *> m_CmdBufferRecords;
VulkanResourceManager *m_ResourceManager;
Threading::CriticalSection m_CapTransitionLock;
uint32_t m_FrameCounter;
@@ -273,10 +273,13 @@ VkResult WrappedVulkan::vkAllocDescriptorSets(
record->AddParent(GetResourceManager()->GetResourceRecord(layoutID));
// just always treat descriptor sets as dirty
if(m_State != WRITING_CAPFRAME)
GetResourceManager()->MarkDirtyResource(id);
else
GetResourceManager()->MarkPendingDirty(id);
{
SCOPED_LOCK(m_CapTransitionLock);
if(m_State != WRITING_CAPFRAME)
GetResourceManager()->MarkDirtyResource(id);
else
GetResourceManager()->MarkPendingDirty(id);
}
record->layout = layoutID;
m_CreationInfo.m_DescSetLayout[layoutID].CreateBindingsArray(record->descBindings);
@@ -473,8 +476,14 @@ void WrappedVulkan::vkUpdateDescriptorSets(
ObjDisp(device)->UpdateDescriptorSets(Unwrap(device), writeCount, unwrappedWrites, copyCount, unwrappedCopies);
}
bool capframe = false;
{
SCOPED_LOCK(m_CapTransitionLock);
capframe = (m_State == WRITING_CAPFRAME);
}
if(m_State == WRITING_CAPFRAME)
if(capframe)
{
// don't have to mark referenced any of the resources pointed to by the descriptor set - that's handled
// on queue submission by marking ref'd all the current bindings of the sets referenced by the cmd buffer
@@ -330,10 +330,72 @@ VkResult WrappedVulkan::vkQueueSubmit(
VkResult ret = ObjDisp(queue)->QueueSubmit(Unwrap(queue), cmdBufferCount, unwrapped, Unwrap(fence));
// VKTODOHIGH when maps are intercepted with local buffers, this will have to be
// done when not in capframe :(.
if(m_State == WRITING_CAPFRAME)
bool capframe = false;
for(uint32_t i=0; i < cmdBufferCount; i++)
{
ResourceId cmd = GetResID(pCmdBuffers[i]);
GetResourceManager()->ApplyTransitions(m_CmdBufferInfo[cmd].imgtransitions, m_ImageInfo);
VkResourceRecord *record = GetRecord(pCmdBuffers[i]);
// need to lock the whole section of code, not just the check on
// m_State, as we also need to make sure we don't check the state,
// start marking dirty resources then while we're doing so the
// state becomes capframe.
// the next sections where we mark resources referenced and add
// the submit chunk to the frame record don't have to be protected.
// Only the decision of whether we're inframe or not, and marking
// dirty.
{
SCOPED_LOCK(m_CapTransitionLock);
if(m_State == WRITING_CAPFRAME)
{
for(auto it = record->bakedCommands->dirtied.begin(); it != record->bakedCommands->dirtied.end(); ++it)
GetResourceManager()->MarkPendingDirty(*it);
capframe = true;
}
else
{
for(auto it = record->bakedCommands->dirtied.begin(); it != record->bakedCommands->dirtied.end(); ++it)
GetResourceManager()->MarkDirtyResource(*it);
}
}
if(capframe)
{
// for each bound descriptor set, mark it referenced as well as all resources currently bound to it
for(auto it = record->bakedCommands->boundDescSets.begin(); it != record->bakedCommands->boundDescSets.end(); ++it)
{
GetResourceManager()->MarkResourceFrameReferenced(GetResID(*it), eFrameRef_Read);
VkResourceRecord *setrecord = GetRecord(*it);
for(auto refit = setrecord->bindFrameRefs.begin(); refit != setrecord->bindFrameRefs.end(); ++refit)
GetResourceManager()->MarkResourceFrameReferenced(refit->first, refit->second.second);
}
// pull in frame refs from this baked command buffer
record->bakedCommands->AddResourceReferences(GetResourceManager());
// ref the parent command buffer by itself, this will pull in the cmd buffer pool
GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(), eFrameRef_Read);
if(fence != VK_NULL_HANDLE)
GetResourceManager()->MarkResourceFrameReferenced(GetResID(fence), eFrameRef_Read);
m_CmdBufferRecords.push_back(record->bakedCommands);
record->bakedCommands->AddRef();
}
record->dirtied.clear();
}
if(capframe)
{
// VKTODOHIGH when maps are intercepted with local buffers, this will have to be
// done when not in capframe :(.
for(auto it = m_MemoryInfo.begin(); it != m_MemoryInfo.end(); ++it)
{
// potential persistent map, force a full flush
@@ -364,65 +426,17 @@ VkResult WrappedVulkan::vkQueueSubmit(
}
}
}
}
if(m_State == WRITING_CAPFRAME)
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CONTEXT(QUEUE_SUBMIT);
Serialise_vkQueueSubmit(localSerialiser, queue, cmdBufferCount, pCmdBuffers, fence);
m_FrameCaptureRecord->AddChunk(scope.Get());
}
for(uint32_t i=0; i < cmdBufferCount; i++)
{
ResourceId cmd = GetResID(pCmdBuffers[i]);
GetResourceManager()->ApplyTransitions(m_CmdBufferInfo[cmd].imgtransitions, m_ImageInfo);
VkResourceRecord *record = GetRecord(pCmdBuffers[i]);
if(m_State == WRITING_CAPFRAME)
{
for(auto it = record->bakedCommands->dirtied.begin(); it != record->bakedCommands->dirtied.end(); ++it)
GetResourceManager()->MarkPendingDirty(*it);
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CONTEXT(QUEUE_SUBMIT);
Serialise_vkQueueSubmit(localSerialiser, queue, cmdBufferCount, pCmdBuffers, fence);
m_FrameCaptureRecord->AddChunk(scope.Get());
}
else
{
for(auto it = record->bakedCommands->dirtied.begin(); it != record->bakedCommands->dirtied.end(); ++it)
GetResourceManager()->MarkDirtyResource(*it);
}
if(m_State == WRITING_CAPFRAME)
{
// for each bound descriptor set, mark it referenced as well as all resources currently bound to it
for(auto it = record->bakedCommands->boundDescSets.begin(); it != record->bakedCommands->boundDescSets.end(); ++it)
{
GetResourceManager()->MarkResourceFrameReferenced(GetResID(*it), eFrameRef_Read);
VkResourceRecord *setrecord = GetRecord(*it);
for(auto refit = setrecord->bindFrameRefs.begin(); refit != setrecord->bindFrameRefs.end(); ++refit)
GetResourceManager()->MarkResourceFrameReferenced(refit->first, refit->second.second);
}
// pull in frame refs from this baked command buffer
record->bakedCommands->AddResourceReferences(GetResourceManager());
// ref the parent command buffer by itself, this will pull in the cmd buffer pool
GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(), eFrameRef_Read);
if(fence != VK_NULL_HANDLE)
GetResourceManager()->MarkResourceFrameReferenced(GetResID(fence), eFrameRef_Read);
m_CmdBufferRecords.push_back(record->bakedCommands);
record->bakedCommands->AddRef();
}
record->dirtied.clear();
}
return ret;
}
@@ -94,10 +94,13 @@ VkResult WrappedVulkan::vkAllocMemory(
// VKTODOMED always treat memory as dirty for now, so its initial state
// is guaranteed to be prepared
if(m_State != WRITING_CAPFRAME)
GetResourceManager()->MarkDirtyResource(id);
else
GetResourceManager()->MarkPendingDirty(id);
{
SCOPED_LOCK(m_CapTransitionLock);
if(m_State != WRITING_CAPFRAME)
GetResourceManager()->MarkDirtyResource(id);
else
GetResourceManager()->MarkPendingDirty(id);
}
}
else
{
@@ -235,7 +238,20 @@ void WrappedVulkan::vkUnmapMemory(
}
else
{
if(m_State >= WRITING_CAPFRAME)
// decide atomically if this chunk should be in-frame or not
// so that we're not in the else branch but haven't marked
// dirty when capframe starts, then we mark dirty while in-frame
bool capframe = false;
{
SCOPED_LOCK(m_CapTransitionLock);
capframe = (m_State == WRITING_CAPFRAME);
if(!capframe)
GetResourceManager()->MarkDirtyResource(GetResID(mem));
}
if(capframe)
{
if(!it->second.mapFlushed)
{
@@ -262,10 +278,6 @@ void WrappedVulkan::vkUnmapMemory(
// this is true for all non-coherent memory types.
}
}
else
{
GetResourceManager()->MarkDirtyResource(GetResID(mem));
}
it->second.mappedPtr = NULL;
SAFE_DELETE_ARRAY(it->second.refData);
@@ -619,8 +619,12 @@ VkResult WrappedVulkan::vkQueuePresentKHR(
GetResourceManager()->MarkResourceFrameReferenced(swapid, eFrameRef_Read);
EndCaptureFrame(backbuffer);
FinishCapture();
// transition back to IDLE atomically
{
SCOPED_LOCK(m_CapTransitionLock);
EndCaptureFrame(backbuffer);
FinishCapture();
}
byte *thpixels = NULL;
uint32_t thwidth = 0;
@@ -943,8 +947,6 @@ VkResult WrappedVulkan::vkQueuePresentKHR(
if(RenderDoc::Inst().ShouldTriggerCapture(m_FrameCounter) && m_State == WRITING_IDLE && m_FrameRecord.empty())
{
m_State = WRITING_CAPFRAME;
FetchFrameRecord record;
record.frameInfo.frameNumber = m_FrameCounter+1;
record.frameInfo.captureTime = Timing::GetUnixTimestamp();
@@ -953,10 +955,19 @@ VkResult WrappedVulkan::vkQueuePresentKHR(
GetResourceManager()->ClearReferencedResources();
GetResourceManager()->MarkResourceFrameReferenced(m_InstanceRecord->GetResourceID(), eFrameRef_Read);
GetResourceManager()->PrepareInitialContents();
AttemptCapture();
BeginCaptureFrame();
// need to do all this atomically so that no other commands
// will check to see if they need to markdirty or markpendingdirty
// and go into the frame record.
{
SCOPED_LOCK(m_CapTransitionLock);
GetResourceManager()->PrepareInitialContents();
AttemptCapture();
BeginCaptureFrame();
m_State = WRITING_CAPFRAME;
}
RDCLOG("Starting capture, frame %u", m_FrameCounter);
}