mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-13 01:05:44 +00:00
1055 lines
40 KiB
C++
1055 lines
40 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-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 "vk_manager.h"
|
|
#include <algorithm>
|
|
#include "vk_core.h"
|
|
|
|
// debugging logging for barriers
|
|
#if 0
|
|
#define TRDBG(...) RDCLOG(__VA_ARGS__)
|
|
#else
|
|
#define TRDBG(...)
|
|
#endif
|
|
|
|
template <typename SrcBarrierType>
|
|
void VulkanResourceManager::RecordSingleBarrier(
|
|
rdcarray<rdcpair<ResourceId, ImageRegionState>> &dststates, ResourceId id,
|
|
const SrcBarrierType &t, uint32_t nummips, uint32_t numslices)
|
|
{
|
|
// if this is a single barrier for depth and stencil, and we are handling separate depth/stencil,
|
|
// split it to ease processing
|
|
if(m_Core->SeparateDepthStencil() &&
|
|
t.subresourceRange.aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))
|
|
{
|
|
SrcBarrierType tmp = t;
|
|
tmp.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
RecordSingleBarrier(dststates, id, tmp, nummips, numslices);
|
|
tmp.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
RecordSingleBarrier(dststates, id, tmp, nummips, numslices);
|
|
return;
|
|
}
|
|
|
|
bool done = false;
|
|
|
|
size_t i = 0;
|
|
for(; i < dststates.size(); i++)
|
|
{
|
|
rdcpair<ResourceId, ImageRegionState> &state = dststates[i];
|
|
|
|
// image barriers are handled by initially inserting one subresource range for each aspect,
|
|
// and whenever we need more fine-grained detail we split it immediately for one range for
|
|
// each subresource in that aspect. Thereafter if a barrier comes in that covers multiple
|
|
// subresources, we update all matching ranges.
|
|
|
|
// find the states matching this id
|
|
if(state.first < id)
|
|
continue;
|
|
if(state.first != id)
|
|
break;
|
|
|
|
// skip states that don't match aspect mask when handling separate aspects for depth/stencil
|
|
if(m_Core->SeparateDepthStencil() &&
|
|
state.second.subresourceRange.aspectMask != t.subresourceRange.aspectMask)
|
|
continue;
|
|
|
|
state.second.dstQueueFamilyIndex = t.dstQueueFamilyIndex;
|
|
|
|
{
|
|
// we've found a range that completely matches our region, doesn't matter if that's
|
|
// a whole image and the barrier is the whole image, or it's one subresource.
|
|
// note that for images with only one array/mip slice (e.g. render targets) we'll never
|
|
// really have to worry about the else{} branch
|
|
if(state.second.subresourceRange.baseMipLevel == t.subresourceRange.baseMipLevel &&
|
|
state.second.subresourceRange.levelCount == nummips &&
|
|
state.second.subresourceRange.baseArrayLayer == t.subresourceRange.baseArrayLayer &&
|
|
state.second.subresourceRange.layerCount == numslices)
|
|
{
|
|
// verify
|
|
// RDCASSERT(it->second.newLayout == t.oldLayout);
|
|
|
|
// apply it (prevstate is from the start of all barriers accumulated, so only set once)
|
|
if(state.second.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
state.second.oldLayout = t.oldLayout;
|
|
state.second.newLayout = t.newLayout;
|
|
|
|
done = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// this handles the case where the barrier covers a number of subresources and we need
|
|
// to update each matching subresource. If the barrier was only one mip & array slice
|
|
// it would have hit the case above. Find each subresource within the range, update it,
|
|
// and continue (marking as done so whenever we stop finding matching ranges, we are
|
|
// satisfied.
|
|
//
|
|
// note that regardless of how we lay out our subresources (slice-major or mip-major) the
|
|
// new range could be sparse, but that's OK as we only break out of the loop once we go past
|
|
// the whole aspect. Any subresources that don't match the range, after the split, will fail
|
|
// to meet any of the handled cases, so we'll just continue processing.
|
|
if(state.second.subresourceRange.levelCount == 1 &&
|
|
state.second.subresourceRange.layerCount == 1 &&
|
|
state.second.subresourceRange.baseMipLevel >= t.subresourceRange.baseMipLevel &&
|
|
state.second.subresourceRange.baseMipLevel < t.subresourceRange.baseMipLevel + nummips &&
|
|
state.second.subresourceRange.baseArrayLayer >= t.subresourceRange.baseArrayLayer &&
|
|
state.second.subresourceRange.baseArrayLayer <
|
|
t.subresourceRange.baseArrayLayer + numslices)
|
|
{
|
|
// apply it (prevstate is from the start of all barriers accumulated, so only set once)
|
|
if(state.second.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
state.second.oldLayout = t.oldLayout;
|
|
state.second.newLayout = t.newLayout;
|
|
|
|
// continue as there might be more, but we're done
|
|
done = true;
|
|
continue;
|
|
}
|
|
// finally handle the case where we have a range that covers a whole image but we need to
|
|
// split it. If the barrier covered the whole image too it would have hit the very first
|
|
// case, so we know that the barrier doesn't cover the whole range.
|
|
// Also, if we've already done the split this case won't be hit and we'll either fall into
|
|
// the case above, or we'll finish as we've covered the whole barrier.
|
|
else if(state.second.subresourceRange.levelCount > 1 ||
|
|
state.second.subresourceRange.layerCount > 1)
|
|
{
|
|
const uint32_t levelCount = state.second.subresourceRange.levelCount;
|
|
const uint32_t layerCount = state.second.subresourceRange.layerCount;
|
|
|
|
size_t count = levelCount * layerCount;
|
|
|
|
// reset layer/level count
|
|
state.second.subresourceRange.levelCount = 1;
|
|
state.second.subresourceRange.layerCount = 1;
|
|
|
|
rdcpair<ResourceId, ImageRegionState> existing = state;
|
|
|
|
// insert new copies of the current state to expand out the subresources. Only insert
|
|
// count-1 as we want count entries total - one per subresource
|
|
for(size_t sub = 0; sub < count - 1; sub++)
|
|
dststates.insert(i, existing);
|
|
|
|
for(size_t sub = 0; sub < count; sub++)
|
|
{
|
|
rdcpair<ResourceId, ImageRegionState> &subState = dststates[i + sub];
|
|
|
|
// slice-major, update base of each subresource
|
|
subState.second.subresourceRange.baseArrayLayer = uint32_t(sub / levelCount);
|
|
subState.second.subresourceRange.baseMipLevel = uint32_t(sub % levelCount);
|
|
}
|
|
|
|
// can't use state here, as it may no longer be valid if the inserts above resized the
|
|
// array
|
|
rdcpair<ResourceId, ImageRegionState> &firstState = dststates[i];
|
|
|
|
// the loop will continue after this point and look at the next subresources
|
|
// so we need to check to see if the first subresource lies in the range here
|
|
if(firstState.second.subresourceRange.baseMipLevel >= t.subresourceRange.baseMipLevel &&
|
|
firstState.second.subresourceRange.baseMipLevel <
|
|
t.subresourceRange.baseMipLevel + nummips &&
|
|
firstState.second.subresourceRange.baseArrayLayer >= t.subresourceRange.baseArrayLayer &&
|
|
firstState.second.subresourceRange.baseArrayLayer <
|
|
t.subresourceRange.baseArrayLayer + numslices)
|
|
{
|
|
// apply it (prevstate is from the start of all barriers accumulated, so only set
|
|
// once)
|
|
if(firstState.second.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
firstState.second.oldLayout = t.oldLayout;
|
|
firstState.second.newLayout = t.newLayout;
|
|
|
|
// continue as there might be more, but we're done
|
|
done = true;
|
|
}
|
|
|
|
// continue processing from here
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise continue to try and find the subresource range
|
|
}
|
|
|
|
if(done)
|
|
return;
|
|
|
|
// we don't have an existing barrier for this memory region, insert into place. it points to
|
|
// where it should be inserted
|
|
VkImageSubresourceRange subRange = t.subresourceRange;
|
|
subRange.levelCount = nummips;
|
|
subRange.layerCount = numslices;
|
|
dststates.insert(i, make_rdcpair(id, ImageRegionState(VK_QUEUE_FAMILY_IGNORED, subRange,
|
|
t.oldLayout, t.newLayout)));
|
|
}
|
|
|
|
void VulkanResourceManager::RecordBarriers(rdcarray<rdcpair<ResourceId, ImageRegionState>> &states,
|
|
const std::map<ResourceId, ImageLayouts> &layouts,
|
|
uint32_t numBarriers, const VkImageMemoryBarrier *barriers)
|
|
{
|
|
TRDBG("Recording %u barriers", numBarriers);
|
|
|
|
for(uint32_t ti = 0; ti < numBarriers; ti++)
|
|
{
|
|
const VkImageMemoryBarrier &t = barriers[ti];
|
|
|
|
// ignore barriers that are do-nothing. Best case this doesn't change our tracking at all and
|
|
// worst case this is a KHR_synchronization2 barrier that should not change the layout.
|
|
if(t.oldLayout == t.newLayout)
|
|
continue;
|
|
|
|
ResourceId id = IsReplayMode(m_State) ? GetNonDispWrapper(t.image)->id : GetResID(t.image);
|
|
|
|
if(id == ResourceId())
|
|
{
|
|
RDCERR("Couldn't get ID for image %p in barrier", GetWrapped(t.image));
|
|
continue;
|
|
}
|
|
|
|
uint32_t nummips = t.subresourceRange.levelCount;
|
|
uint32_t numslices = t.subresourceRange.layerCount;
|
|
|
|
auto it = layouts.find(id);
|
|
|
|
if(nummips == VK_REMAINING_MIP_LEVELS)
|
|
{
|
|
if(it != layouts.end())
|
|
nummips = it->second.imageInfo.levelCount - t.subresourceRange.baseMipLevel;
|
|
else
|
|
nummips = 1;
|
|
}
|
|
|
|
if(numslices == VK_REMAINING_ARRAY_LAYERS)
|
|
{
|
|
if(it != layouts.end())
|
|
numslices = it->second.imageInfo.layerCount - t.subresourceRange.baseArrayLayer;
|
|
else
|
|
numslices = 1;
|
|
}
|
|
|
|
RecordSingleBarrier(states, id, t, nummips, numslices);
|
|
}
|
|
|
|
TRDBG("Post-record, there are %u states", (uint32_t)states.size());
|
|
}
|
|
|
|
void VulkanResourceManager::RecordBarriers(rdcflatmap<ResourceId, ImageState> &states,
|
|
uint32_t queueFamilyIndex, uint32_t numBarriers,
|
|
const VkImageMemoryBarrier2 *barriers)
|
|
{
|
|
rdcarray<VkImageMemoryBarrier> downcast;
|
|
downcast.reserve(numBarriers);
|
|
VkImageMemoryBarrier b = {};
|
|
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
for(uint32_t i = 0; i < numBarriers; i++)
|
|
{
|
|
// just truncate, the lower bits all match
|
|
b.srcAccessMask = uint32_t(barriers[i].srcAccessMask);
|
|
b.dstAccessMask = uint32_t(barriers[i].dstAccessMask);
|
|
b.oldLayout = barriers[i].oldLayout;
|
|
b.newLayout = barriers[i].newLayout;
|
|
b.srcQueueFamilyIndex = barriers[i].srcQueueFamilyIndex;
|
|
b.dstQueueFamilyIndex = barriers[i].dstQueueFamilyIndex;
|
|
b.image = barriers[i].image;
|
|
b.subresourceRange = barriers[i].subresourceRange;
|
|
downcast.push_back(b);
|
|
}
|
|
RecordBarriers(states, queueFamilyIndex, (uint32_t)downcast.size(), downcast.data());
|
|
}
|
|
|
|
void VulkanResourceManager::MergeBarriers(rdcarray<rdcpair<ResourceId, ImageRegionState>> &dststates,
|
|
rdcarray<rdcpair<ResourceId, ImageRegionState>> &srcstates)
|
|
{
|
|
TRDBG("Merging %u states", (uint32_t)srcstates.size());
|
|
|
|
for(size_t ti = 0; ti < srcstates.size(); ti++)
|
|
{
|
|
const ImageRegionState &t = srcstates[ti].second;
|
|
RecordSingleBarrier(dststates, srcstates[ti].first, t, t.subresourceRange.levelCount,
|
|
t.subresourceRange.layerCount);
|
|
}
|
|
|
|
TRDBG("Post-merge, there are %u states", (uint32_t)dststates.size());
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
void VulkanResourceManager::SerialiseImageStates(SerialiserType &ser,
|
|
std::map<ResourceId, LockingImageState> &states)
|
|
{
|
|
SERIALISE_ELEMENT_LOCAL(NumImages, (uint32_t)states.size()).Important();
|
|
|
|
auto srcit = states.begin();
|
|
|
|
for(uint32_t i = 0; i < NumImages; i++)
|
|
{
|
|
SERIALISE_ELEMENT_LOCAL(Image, (ResourceId)(srcit->first)).TypedAs("VkImage"_lit);
|
|
if(ser.IsWriting())
|
|
{
|
|
LockedImageStateRef lockedState = srcit->second.LockWrite();
|
|
::ImageState &ImageState = *lockedState;
|
|
SERIALISE_ELEMENT(ImageState);
|
|
++srcit;
|
|
}
|
|
else
|
|
{
|
|
bool hasLiveRes = HasLiveResource(Image);
|
|
|
|
ImageState imageState;
|
|
|
|
if(ser.VersionLess(0x11))
|
|
{
|
|
ImageLayouts imageLayouts;
|
|
{
|
|
ImageLayouts &ImageState = imageLayouts;
|
|
SERIALISE_ELEMENT(ImageState);
|
|
}
|
|
if(IsReplayingAndReading() && hasLiveRes)
|
|
{
|
|
if(imageLayouts.imageInfo.extent.depth > 1)
|
|
imageLayouts.imageInfo.imageType = VK_IMAGE_TYPE_3D;
|
|
|
|
imageState = ImageState(VK_NULL_HANDLE, imageLayouts.imageInfo, eFrameRef_Unknown);
|
|
|
|
rdcarray<ImageSubresourceStateForRange> subresourceStates;
|
|
subresourceStates.reserve(imageLayouts.subresourceStates.size());
|
|
|
|
for(ImageRegionState &st : imageLayouts.subresourceStates)
|
|
{
|
|
ImageSubresourceStateForRange p;
|
|
p.range = st.subresourceRange;
|
|
p.range.sliceCount = imageLayouts.imageInfo.extent.depth;
|
|
p.state.oldQueueFamilyIndex = m_Core->RemapQueue(st.dstQueueFamilyIndex);
|
|
p.state.newQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
p.state.oldLayout = st.newLayout;
|
|
p.state.newLayout = imageState.GetImageInfo().initialLayout;
|
|
p.state.refType = eFrameRef_Unknown;
|
|
subresourceStates.push_back(p);
|
|
}
|
|
|
|
if(!subresourceStates.empty())
|
|
{
|
|
std::sort(subresourceStates.begin(), subresourceStates.end(),
|
|
ImageSubresourceStateForRange::CompareRangeBegin);
|
|
imageState.subresourceStates.FromArray(subresourceStates);
|
|
}
|
|
imageState.maxRefType = eFrameRef_Unknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
{
|
|
::ImageState &ImageState = imageState;
|
|
SERIALISE_ELEMENT(ImageState);
|
|
}
|
|
if(IsReplayingAndReading() && hasLiveRes)
|
|
{
|
|
imageState.newQueueFamilyTransfers.clear();
|
|
for(auto it = imageState.subresourceStates.begin();
|
|
it != imageState.subresourceStates.end(); ++it)
|
|
{
|
|
// Set the current image state (`newLayout`, `newQueueFamilyIndex`, `refType`) to the
|
|
// initial image state, so that calling `ResetToOldState` will move the image from the
|
|
// initial state to the state it was in at the beginning of the capture.
|
|
ImageSubresourceState &state = it->state();
|
|
state.newLayout = imageState.GetImageInfo().initialLayout;
|
|
state.oldQueueFamilyIndex = m_Core->RemapQueue(state.oldQueueFamilyIndex);
|
|
state.newQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
}
|
|
}
|
|
}
|
|
if(hasLiveRes)
|
|
{
|
|
ResourceId liveid = GetLiveID(Image);
|
|
|
|
if(IsLoading(m_State))
|
|
{
|
|
auto stit = states.find(liveid);
|
|
if(stit == states.end())
|
|
{
|
|
imageState.subresourceStates.Unsplit();
|
|
states.insert({liveid, LockingImageState(imageState)});
|
|
}
|
|
else
|
|
{
|
|
auto st = stit->second.LockWrite();
|
|
st->MergeCaptureBeginState(imageState);
|
|
st->subresourceStates.Unsplit();
|
|
}
|
|
}
|
|
else if(IsActiveReplaying(m_State))
|
|
{
|
|
auto current = states.find(liveid)->second.LockRead();
|
|
auto stit = states.find(liveid);
|
|
for(auto subit = imageState.subresourceStates.begin();
|
|
subit != imageState.subresourceStates.end(); ++subit)
|
|
{
|
|
uint32_t aspectIndex = 0;
|
|
for(auto it = ImageAspectFlagIter::begin(imageState.GetImageInfo().Aspects());
|
|
it != ImageAspectFlagIter::end() && ((*it) & subit->range().aspectMask) == 0;
|
|
++it, ++aspectIndex)
|
|
{
|
|
}
|
|
auto currentSub = current->subresourceStates.SubresourceIndexValue(
|
|
aspectIndex, subit->range().baseMipLevel, subit->range().baseArrayLayer,
|
|
subit->range().baseDepthSlice);
|
|
FrameRefType expectedRef = subit->state().refType;
|
|
if(current->m_Storage)
|
|
expectedRef = eFrameRef_ReadBeforeWrite;
|
|
RDCASSERT(currentSub.refType == expectedRef || expectedRef == eFrameRef_Unknown);
|
|
RDCASSERT(currentSub.oldLayout == subit->state().oldLayout ||
|
|
subit->state().oldLayout == UNKNOWN_PREV_IMG_LAYOUT);
|
|
RDCASSERT(currentSub.oldQueueFamilyIndex == subit->state().oldQueueFamilyIndex ||
|
|
subit->state().oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
template void VulkanResourceManager::SerialiseImageStates(
|
|
WriteSerialiser &ser, std::map<ResourceId, LockingImageState> &states);
|
|
template void VulkanResourceManager::SerialiseImageStates(
|
|
ReadSerialiser &ser, std::map<ResourceId, LockingImageState> &states);
|
|
|
|
template <class SerialiserType>
|
|
void DoSerialise(SerialiserType &ser, MemRefInterval &el)
|
|
{
|
|
SERIALISE_MEMBER(memory);
|
|
SERIALISE_MEMBER(start);
|
|
SERIALISE_MEMBER(refType);
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
bool VulkanResourceManager::Serialise_DeviceMemoryRefs(SerialiserType &ser,
|
|
rdcarray<MemRefInterval> &data)
|
|
{
|
|
SERIALISE_ELEMENT(data);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// unpack data into m_MemFrameRefs
|
|
auto it_data = data.begin();
|
|
while(it_data != data.end())
|
|
{
|
|
ResourceId mem = it_data->memory;
|
|
|
|
auto res = m_MemFrameRefs.insert(std::pair<ResourceId, MemRefs>(mem, MemRefs()));
|
|
RDCASSERTMSG("MemRefIntervals for each memory resource must be contiguous", res.second);
|
|
Intervals<FrameRefType> &rangeRefs = res.first->second.rangeRefs;
|
|
|
|
auto it_ints = rangeRefs.begin();
|
|
uint64_t last = 0;
|
|
FrameRefType lastRef = eFrameRef_None;
|
|
while(it_data != data.end() && it_data->memory == mem)
|
|
{
|
|
uint64_t start = it_data->start;
|
|
if(start & 0x3)
|
|
{
|
|
// start is not a multiple of 4. We need to shift start to a multiple of 4 to satisfy the
|
|
// alignment requirements of `vkCmdFillBuffer`.
|
|
|
|
uint64_t nextDWord = AlignUp4(start);
|
|
|
|
// Compute the overall ref type for the dword, including all the ref types of intervals
|
|
// intersecting the dword
|
|
FrameRefType overlapRef = lastRef;
|
|
for(; it_data != data.end() && it_data->start < nextDWord && it_data->memory == mem;
|
|
++it_data)
|
|
overlapRef = ComposeFrameRefsDisjoint(overlapRef, it_data->refType);
|
|
|
|
--it_data;
|
|
// it_data now points to the last interval intersecting the dword.
|
|
|
|
if(overlapRef == lastRef)
|
|
{
|
|
// The ref type for the overlap dword is the same as the ref type of the previous
|
|
// interval; move the entire overlap dword into the previous interval, which means the
|
|
// start of this interval moves up to the the next higher dword.
|
|
start = nextDWord;
|
|
}
|
|
else if(overlapRef == it_data->refType)
|
|
{
|
|
// The ref type for the overlap dword is the same as for this interval; move the entire
|
|
// overlap dword into this interval, which means the start of this interval moves down
|
|
// to the next lower dword.
|
|
start = nextDWord - 4;
|
|
}
|
|
else
|
|
{
|
|
// The ref type of the overlap dword matches neither the previous interval nor this
|
|
// interval; insert a new interval for the overlap.
|
|
if(last < nextDWord - 4)
|
|
it_ints->split(nextDWord - 4);
|
|
it_ints->setValue(overlapRef);
|
|
last = nextDWord - 4;
|
|
start = nextDWord;
|
|
}
|
|
}
|
|
RDCASSERTMSG("MemRefInterval starts must be increasing", start >= last);
|
|
|
|
if(last < start)
|
|
it_ints->split(start);
|
|
it_ints->setValue(it_data->refType);
|
|
last = start;
|
|
lastRef = it_data->refType;
|
|
it_data++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template bool VulkanResourceManager::Serialise_DeviceMemoryRefs(ReadSerialiser &ser,
|
|
rdcarray<MemRefInterval> &data);
|
|
template bool VulkanResourceManager::Serialise_DeviceMemoryRefs(WriteSerialiser &ser,
|
|
rdcarray<MemRefInterval> &data);
|
|
|
|
bool VulkanResourceManager::Serialise_ImageRefs(ReadSerialiser &ser,
|
|
std::map<ResourceId, LockingImageState> &states)
|
|
{
|
|
rdcarray<ImgRefsPair> data;
|
|
SERIALISE_ELEMENT(data);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// unpack data into states
|
|
for(auto it = data.begin(); it != data.end(); ++it)
|
|
{
|
|
if(!HasLiveResource(it->image))
|
|
continue;
|
|
ResourceId liveid = GetLiveID(it->image);
|
|
|
|
auto stit = states.find(liveid);
|
|
if(stit == states.end())
|
|
{
|
|
RDCWARN("Found ImgRefs for unknown image");
|
|
}
|
|
else
|
|
{
|
|
LockedImageStateRef imst = stit->second.LockWrite();
|
|
imst->subresourceStates.FromImgRefs(it->imgRefs);
|
|
FrameRefType maxRefType = eFrameRef_None;
|
|
for(auto subit = imst->subresourceStates.begin(); subit != imst->subresourceStates.end();
|
|
++subit)
|
|
{
|
|
maxRefType = ComposeFrameRefsDisjoint(maxRefType, subit->state().refType);
|
|
}
|
|
imst->maxRefType = maxRefType;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VulkanResourceManager::InsertDeviceMemoryRefs(WriteSerialiser &ser)
|
|
{
|
|
rdcarray<MemRefInterval> data;
|
|
MemRefs emptyMemRefs;
|
|
for(auto memIt = m_DeviceMemories.begin(); memIt != m_DeviceMemories.end(); memIt++)
|
|
{
|
|
MemRefs *memRefs = NULL;
|
|
auto it = m_MemFrameRefs.find(*memIt);
|
|
if(it == m_MemFrameRefs.end())
|
|
memRefs = &emptyMemRefs;
|
|
else
|
|
memRefs = &it->second;
|
|
Intervals<FrameRefType> &rangeRefs = memRefs->rangeRefs;
|
|
for(auto jt = rangeRefs.begin(); jt != rangeRefs.end(); jt++)
|
|
data.push_back({*memIt, jt->start(), jt->value()});
|
|
}
|
|
|
|
for(ResourceId dead : m_DeadDeviceMemories)
|
|
m_DeviceMemories.erase(dead);
|
|
m_DeadDeviceMemories.clear();
|
|
|
|
uint64_t sizeEstimate = data.size() * sizeof(MemRefInterval) + 32;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CHUNK(VulkanChunk::DeviceMemoryRefs, sizeEstimate);
|
|
Serialise_DeviceMemoryRefs(ser, data);
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::MarkSparseMapReferenced(const ResourceInfo *sparse)
|
|
{
|
|
if(sparse == NULL)
|
|
{
|
|
RDCERR("Unexpected NULL sparse mapping");
|
|
return;
|
|
}
|
|
|
|
for(size_t a = 0; a <= sparse->altSparseAspects.size(); a++)
|
|
{
|
|
const Sparse::PageTable &table = a < sparse->altSparseAspects.size()
|
|
? sparse->altSparseAspects[a].table
|
|
: sparse->sparseTable;
|
|
|
|
uint32_t numSubs = table.getNumSubresources();
|
|
const Sparse::MipTail &mipTail = table.getMipTail();
|
|
for(uint32_t s = 0; s < numSubs + mipTail.mappings.size(); s++)
|
|
{
|
|
const Sparse::PageRangeMapping &mapping =
|
|
s < numSubs ? table.getSubresource(s) : table.getMipTail().mappings[s - numSubs];
|
|
|
|
if(s < numSubs && table.isSubresourceInMipTail(s))
|
|
continue;
|
|
|
|
if(mapping.hasSingleMapping())
|
|
{
|
|
MarkMemoryFrameReferenced(
|
|
mapping.singleMapping.memory, mapping.singleMapping.offset,
|
|
mapping.singlePageReused ? table.getPageByteSize() : table.getSubresourceByteSize(s),
|
|
eFrameRef_Read);
|
|
}
|
|
else
|
|
{
|
|
// this is a huge perf cliff as we've lost any batching and we perform as badly as if every
|
|
// page was mapped to a different resource, so we hope applications don't hit this often.
|
|
for(const Sparse::Page &page : mapping.pages)
|
|
{
|
|
MarkMemoryFrameReferenced(page.memory, page.offset, table.getPageByteSize(),
|
|
eFrameRef_Read);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::SetInternalResource(ResourceId id)
|
|
{
|
|
if(!RenderDoc::Inst().IsReplayApp())
|
|
{
|
|
VkResourceRecord *record = GetResourceRecord(id);
|
|
if(record)
|
|
record->InternalResource = true;
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::ApplyBarriers(uint32_t queueFamilyIndex,
|
|
rdcarray<rdcpair<ResourceId, ImageRegionState>> &states,
|
|
std::map<ResourceId, ImageLayouts> &layouts)
|
|
{
|
|
TRDBG("Applying %u barriers", (uint32_t)states.size());
|
|
|
|
for(size_t ti = 0; ti < states.size(); ti++)
|
|
{
|
|
ResourceId id = states[ti].first;
|
|
ImageRegionState &t = states[ti].second;
|
|
|
|
TRDBG("Applying barrier to %s", ToStr(GetOriginalID(id)).c_str());
|
|
|
|
auto stit = layouts.find(id);
|
|
|
|
if(stit == layouts.end())
|
|
{
|
|
TRDBG("Didn't find ID in image layouts");
|
|
continue;
|
|
}
|
|
|
|
// apply any ownership transfer
|
|
stit->second.queueFamilyIndex = t.dstQueueFamilyIndex;
|
|
|
|
// if there's no ownership transfer, it's implicitly owned by the current queue
|
|
if(t.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
|
|
stit->second.queueFamilyIndex = queueFamilyIndex;
|
|
|
|
const ImageInfo &imageInfo = stit->second.imageInfo;
|
|
|
|
uint32_t nummips = t.subresourceRange.levelCount;
|
|
uint32_t numslices = t.subresourceRange.layerCount;
|
|
if(nummips == VK_REMAINING_MIP_LEVELS)
|
|
nummips = imageInfo.levelCount;
|
|
if(numslices == VK_REMAINING_ARRAY_LAYERS)
|
|
numslices = imageInfo.layerCount;
|
|
|
|
if(nummips == 0)
|
|
nummips = 1;
|
|
if(numslices == 0)
|
|
numslices = 1;
|
|
|
|
if(t.oldLayout == t.newLayout)
|
|
continue;
|
|
|
|
TRDBG("Barrier of %s (%u->%u, %u->%u) from %s to %s", ToStr(t.subresourceRange.aspect).c_str(),
|
|
t.subresourceRange.baseMipLevel, t.subresourceRange.levelCount,
|
|
t.subresourceRange.baseArrayLayer, t.subresourceRange.layerCount,
|
|
ToStr(t.oldLayout).c_str(), ToStr(t.newLayout).c_str());
|
|
|
|
bool done = false;
|
|
|
|
TRDBG("Matching image has %u subresource states", stit->second.subresourceStates.size());
|
|
|
|
for(size_t i = 0; i < stit->second.subresourceStates.size(); i++)
|
|
{
|
|
ImageRegionState &state = stit->second.subresourceStates[i];
|
|
TRDBG(".. state %s (%u->%u, %u->%u) from %s to %s",
|
|
ToStr(state.subresourceRange.aspect).c_str(), state.range.baseMipLevel,
|
|
state.range.levelCount, state.range.baseArrayLayer, state.range.layerCount,
|
|
ToStr(state.oldLayout).c_str(), ToStr(state.newLayout).c_str());
|
|
|
|
// image barriers are handled by initially inserting one subresource range for the whole
|
|
// object,
|
|
// and whenever we need more fine-grained detail we split it immediately.
|
|
// Thereafter if a barrier comes in that covers multiple subresources, we update all matching
|
|
// ranges.
|
|
|
|
// ignore states of different aspects when depth/stencil aspects are split
|
|
if(m_Core->SeparateDepthStencil() &&
|
|
state.subresourceRange.aspectMask != t.subresourceRange.aspectMask)
|
|
continue;
|
|
|
|
{
|
|
// we've found a range that completely matches our region, doesn't matter if that's
|
|
// a whole image and the barrier is the whole image, or it's one subresource.
|
|
// note that for images with only one array/mip slice (e.g. render targets) we'll never
|
|
// really have to worry about the else{} branch
|
|
if(state.subresourceRange.baseMipLevel == t.subresourceRange.baseMipLevel &&
|
|
state.subresourceRange.levelCount == nummips &&
|
|
state.subresourceRange.baseArrayLayer == t.subresourceRange.baseArrayLayer &&
|
|
state.subresourceRange.layerCount == numslices)
|
|
{
|
|
if(state.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
state.oldLayout = t.oldLayout;
|
|
t.oldLayout = state.newLayout;
|
|
state.newLayout = t.newLayout;
|
|
|
|
done = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// this handles the case where the barrier covers a number of subresources and we need
|
|
// to update each matching subresource. If the barrier was only one mip & array slice
|
|
// it would have hit the case above. Find each subresource within the range, update it,
|
|
// and continue (marking as done so whenever we stop finding matching ranges, we are
|
|
// satisfied.
|
|
//
|
|
// note that regardless of how we lay out our subresources (slice-major or mip-major) the
|
|
// new range could be sparse, but that's OK as we only break out of the loop once we go
|
|
// past the whole aspect. Any subresources that don't match the range, after the split,
|
|
// will fail to meet any of the handled cases, so we'll just continue processing.
|
|
if(state.subresourceRange.levelCount == 1 && state.subresourceRange.layerCount == 1 &&
|
|
state.subresourceRange.baseMipLevel >= t.subresourceRange.baseMipLevel &&
|
|
state.subresourceRange.baseMipLevel < t.subresourceRange.baseMipLevel + nummips &&
|
|
state.subresourceRange.baseArrayLayer >= t.subresourceRange.baseArrayLayer &&
|
|
state.subresourceRange.baseArrayLayer < t.subresourceRange.baseArrayLayer + numslices)
|
|
{
|
|
// apply it (prevstate is from the start of all barriers accumulated, so only set once)
|
|
if(state.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
state.oldLayout = t.oldLayout;
|
|
t.oldLayout = state.newLayout;
|
|
state.newLayout = t.newLayout;
|
|
|
|
// continue as there might be more, but we're done
|
|
done = true;
|
|
continue;
|
|
}
|
|
// finally handle the case where we have a range that covers a whole image but we need to
|
|
// split it. If the barrier covered the whole image too it would have hit the very first
|
|
// case, so we know that the barrier doesn't cover the whole range.
|
|
// Also, if we've already done the split this case won't be hit and we'll either fall into
|
|
// the case above, or we'll finish as we've covered the whole barrier.
|
|
else if(state.subresourceRange.levelCount > 1 || state.subresourceRange.layerCount > 1)
|
|
{
|
|
const uint32_t levelCount = state.subresourceRange.levelCount;
|
|
const uint32_t layerCount = state.subresourceRange.layerCount;
|
|
|
|
size_t count = levelCount * layerCount;
|
|
|
|
// reset layer/level count
|
|
state.subresourceRange.levelCount = 1;
|
|
state.subresourceRange.layerCount = 1;
|
|
|
|
// copy now, state will no longer be valid after inserting below
|
|
ImageRegionState existing = state;
|
|
|
|
// insert new copies of the current state to expand out the subresources. Only insert
|
|
// count-1 as we want count entries total - one per subresource
|
|
for(size_t sub = 0; sub < count - 1; sub++)
|
|
stit->second.subresourceStates.insert(i, existing);
|
|
|
|
for(size_t sub = 0; sub < count; sub++)
|
|
{
|
|
ImageRegionState &subState = stit->second.subresourceStates[i + sub];
|
|
|
|
// slice-major, update base of each subresource
|
|
subState.subresourceRange.baseArrayLayer = uint32_t(sub / levelCount);
|
|
subState.subresourceRange.baseMipLevel = uint32_t(sub % levelCount);
|
|
}
|
|
|
|
// can't use state here, as it may no longer be valid if the inserts above resized the
|
|
// array
|
|
ImageRegionState &firstState = stit->second.subresourceStates[i];
|
|
|
|
// the loop will continue after this point and look at the next subresources
|
|
// so we need to check to see if the first subresource lies in the range here
|
|
if(firstState.subresourceRange.baseMipLevel >= t.subresourceRange.baseMipLevel &&
|
|
firstState.subresourceRange.baseMipLevel < t.subresourceRange.baseMipLevel + nummips &&
|
|
firstState.subresourceRange.baseArrayLayer >= t.subresourceRange.baseArrayLayer &&
|
|
firstState.subresourceRange.baseArrayLayer <
|
|
t.subresourceRange.baseArrayLayer + numslices)
|
|
{
|
|
// apply it (prevstate is from the start of all barriers accumulated, so only set
|
|
// once)
|
|
if(firstState.oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
|
|
firstState.oldLayout = t.oldLayout;
|
|
t.oldLayout = firstState.newLayout;
|
|
firstState.newLayout = t.newLayout;
|
|
|
|
// continue as there might be more, but we're done
|
|
done = true;
|
|
}
|
|
|
|
// continue processing from here
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise continue to try and find the subresource range
|
|
}
|
|
|
|
if(!done)
|
|
RDCERR("Couldn't find subresource range to apply barrier to - invalid!");
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::RecordBarriers(rdcflatmap<ResourceId, ImageState> &states,
|
|
uint32_t queueFamilyIndex, uint32_t numBarriers,
|
|
const VkImageMemoryBarrier *barriers)
|
|
{
|
|
TRDBG("Recording %u barriers", numBarriers);
|
|
|
|
for(uint32_t ti = 0; ti < numBarriers; ti++)
|
|
{
|
|
const VkImageMemoryBarrier &t = barriers[ti];
|
|
|
|
// ignore barriers that are do-nothing. Best case this doesn't change our tracking at all and
|
|
// worst case this is a KHR_synchronization2 barrier that should not change the layout.
|
|
if(t.oldLayout == t.newLayout)
|
|
continue;
|
|
|
|
ResourceId id = IsReplayMode(m_State) ? GetNonDispWrapper(t.image)->id : GetResID(t.image);
|
|
|
|
if(id == ResourceId())
|
|
{
|
|
RDCERR("Couldn't get ID for image in barrier");
|
|
continue;
|
|
}
|
|
|
|
auto stateIt = states.find(id);
|
|
if(stateIt == states.end())
|
|
{
|
|
LockedConstImageStateRef globalState = m_Core->FindConstImageState(id);
|
|
if(!globalState)
|
|
{
|
|
RDCERR("Recording barrier for unknown image: %s", ToStr(id).c_str());
|
|
continue;
|
|
}
|
|
stateIt = states.insert({id, globalState->CommandBufferInitialState()}).first;
|
|
}
|
|
|
|
ImageState &state = stateIt->second;
|
|
state.RecordBarrier(t, queueFamilyIndex, m_Core->GetImageTransitionInfo());
|
|
}
|
|
|
|
TRDBG("Post-record, there are %u states", (uint32_t)states.size());
|
|
}
|
|
|
|
ResourceId VulkanResourceManager::GetFirstIDForHandle(uint64_t handle)
|
|
{
|
|
for(auto it = m_CurrentResourceMap.begin(); it != m_CurrentResourceMap.end(); ++it)
|
|
{
|
|
WrappedVkRes *res = it->second;
|
|
|
|
if(!res)
|
|
continue;
|
|
|
|
if(IsDispatchableRes(res))
|
|
{
|
|
WrappedVkDispRes *disp = (WrappedVkDispRes *)res;
|
|
if(disp->real.handle == handle)
|
|
return IsReplayMode(m_State) ? GetOriginalID(disp->id) : disp->id;
|
|
}
|
|
else
|
|
{
|
|
WrappedVkNonDispRes *nondisp = (WrappedVkNonDispRes *)res;
|
|
if(nondisp->real.handle == handle)
|
|
return IsReplayMode(m_State) ? GetOriginalID(nondisp->id) : nondisp->id;
|
|
}
|
|
}
|
|
|
|
return ResourceId();
|
|
}
|
|
|
|
void VulkanResourceManager::MarkMemoryFrameReferenced(ResourceId mem, VkDeviceSize offset,
|
|
VkDeviceSize size, FrameRefType refType)
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
FrameRefType maxRef = MarkMemoryReferenced(m_MemFrameRefs, mem, offset, size, refType);
|
|
if(IsCompleteWriteFrameRef(maxRef))
|
|
{
|
|
// check and make sure this is really a CompleteWrite
|
|
VkResourceRecord *record = GetResourceRecord(mem);
|
|
// if we are not writing the entire memory, degrade it to a partial write
|
|
if(offset != 0 || size != record->Length)
|
|
maxRef = eFrameRef_PartialWrite;
|
|
}
|
|
MarkResourceFrameReferenced(mem, maxRef, ComposeFrameRefsDisjoint);
|
|
}
|
|
|
|
void VulkanResourceManager::AddMemoryFrameRefs(ResourceId mem)
|
|
{
|
|
m_MemFrameRefs[mem] = MemRefs();
|
|
}
|
|
|
|
void VulkanResourceManager::AddDeviceMemory(ResourceId mem)
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
m_DeviceMemories.insert(mem);
|
|
}
|
|
|
|
void VulkanResourceManager::RemoveDeviceMemory(ResourceId mem)
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
if(IsActiveCapturing(m_State))
|
|
m_DeadDeviceMemories.push_back(mem);
|
|
else
|
|
m_DeviceMemories.erase(mem);
|
|
}
|
|
|
|
void VulkanResourceManager::MergeReferencedMemory(std::unordered_map<ResourceId, MemRefs> &memRefs)
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
for(auto j = memRefs.begin(); j != memRefs.end(); j++)
|
|
{
|
|
auto i = m_MemFrameRefs.find(j->first);
|
|
if(i == m_MemFrameRefs.end())
|
|
m_MemFrameRefs[j->first] = j->second;
|
|
else
|
|
i->second.Merge(j->second);
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::FixupStorageBufferMemory(
|
|
const std::unordered_set<VkResourceRecord *> &storageBuffers)
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
for(VkResourceRecord *buf : storageBuffers)
|
|
{
|
|
ResourceId id = buf->GetResourceID();
|
|
MemRefs ref;
|
|
ref.Update(buf->memOffset, buf->memSize, eFrameRef_ReadBeforeWrite);
|
|
|
|
auto i = m_MemFrameRefs.find(id);
|
|
if(i == m_MemFrameRefs.end())
|
|
m_MemFrameRefs[id] = ref;
|
|
else
|
|
i->second.Merge(ref, ComposeFrameRefsUnordered);
|
|
}
|
|
}
|
|
|
|
void VulkanResourceManager::ClearReferencedMemory()
|
|
{
|
|
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
|
|
|
|
m_MemFrameRefs.clear();
|
|
}
|
|
|
|
MemRefs *VulkanResourceManager::FindMemRefs(ResourceId mem)
|
|
{
|
|
auto it = m_MemFrameRefs.find(mem);
|
|
if(it != m_MemFrameRefs.end())
|
|
return &it->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void VulkanResourceManager::Begin_PrepareInitialBatch()
|
|
{
|
|
return m_Core->Begin_PrepareInitialBatch();
|
|
}
|
|
|
|
void VulkanResourceManager::End_PrepareInitialBatch()
|
|
{
|
|
return m_Core->End_PrepareInitialBatch();
|
|
}
|
|
|
|
bool VulkanResourceManager::Prepare_InitialState(WrappedVkRes *res)
|
|
{
|
|
return m_Core->Prepare_InitialState(res);
|
|
}
|
|
|
|
uint64_t VulkanResourceManager::GetSize_InitialState(ResourceId id, const VkInitialContents &initial)
|
|
{
|
|
return m_Core->GetSize_InitialState(id, initial);
|
|
}
|
|
|
|
bool VulkanResourceManager::Serialise_InitialState(WriteSerialiser &ser, ResourceId id,
|
|
VkResourceRecord *record,
|
|
const VkInitialContents *initial)
|
|
{
|
|
return m_Core->Serialise_InitialState(ser, id, record, initial);
|
|
}
|
|
|
|
void VulkanResourceManager::Create_InitialState(ResourceId id, WrappedVkRes *live, bool hasData)
|
|
{
|
|
return m_Core->Create_InitialState(id, live, hasData);
|
|
}
|
|
|
|
void VulkanResourceManager::Apply_InitialState(WrappedVkRes *live, const VkInitialContents &initial)
|
|
{
|
|
return m_Core->Apply_InitialState(live, initial);
|
|
}
|
|
|
|
rdcarray<ResourceId> VulkanResourceManager::InitialContentResources()
|
|
{
|
|
rdcarray<ResourceId> resources =
|
|
ResourceManager<VulkanResourceManagerConfiguration>::InitialContentResources();
|
|
std::sort(resources.begin(), resources.end(), [this](ResourceId a, ResourceId b) {
|
|
return m_InitialContents[a].data.type < m_InitialContents[b].data.type;
|
|
});
|
|
return resources;
|
|
}
|
|
|
|
bool VulkanResourceManager::ResourceTypeRelease(WrappedVkRes *res)
|
|
{
|
|
return m_Core->ReleaseResource(res);
|
|
}
|
|
|
|
bool VulkanResourceManager::IsResourceTrackedForPersistency(WrappedVkRes *const &res)
|
|
{
|
|
return IsPostponableRes(res);
|
|
}
|