Skip to content

Commit 92cc531

Browse files
Add sparse image and buffer support
The current trim handling for buffers and images does not support sparse buffers or sparse images. It only allows one image or buffer binding to a single memory range of a single memory object. As a result, it cannot manage sparse resources, which has led to missing asset issues in trim trace for some titles that utilize sparse resources. This commit introduces support for sparse buffers and sparse images (using opaque memory binding only), effectively resolving the missing asset issue. Originally by Ming Zheng <[email protected]>
1 parent 43faa4a commit 92cc531

7 files changed

+655
-40
lines changed

framework/encode/custom_vulkan_encoder_commands.h

+20
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,26 @@ struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkBindImageMemory2KHR>
505505
}
506506
};
507507

508+
template <>
509+
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCreateBuffer>
510+
{
511+
template <typename... Args>
512+
static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args)
513+
{
514+
manager->PostProcess_vkCreateBuffer(result, args...);
515+
}
516+
};
517+
518+
template <>
519+
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCreateImage>
520+
{
521+
template <typename... Args>
522+
static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args)
523+
{
524+
manager->PostProcess_vkCreateImage(result, args...);
525+
}
526+
};
527+
508528
template <>
509529
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCmdBeginRenderPass>
510530
{

framework/encode/vulkan_capture_manager.h

+150-1
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ class VulkanCaptureManager : public ApiCaptureManager
551551
}
552552

553553
void PostProcess_vkQueueBindSparse(
554-
VkResult result, VkQueue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence)
554+
VkResult result, VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence)
555555
{
556556
if (IsCaptureModeTrack() && (result == VK_SUCCESS))
557557
{
@@ -563,6 +563,110 @@ class VulkanCaptureManager : public ApiCaptureManager
563563
pBindInfo[i].signalSemaphoreCount,
564564
pBindInfo[i].pSignalSemaphores);
565565
}
566+
567+
// In default mode, the capture manager uses a shared mutex to capture every API function. As a result,
568+
// multiple threads may access the sparse resource maps concurrently. Therefore, we use a dedicated mutex
569+
// for write access to these maps.
570+
const std::lock_guard<std::mutex> lock(sparse_resource_mutex);
571+
for (uint32_t bind_info_index = 0; bind_info_index < bindInfoCount; bind_info_index++)
572+
{
573+
auto& bind_info = pBindInfo[bind_info_index];
574+
575+
// TODO: add device group support. In the following handling, we assume that the system only has one
576+
// physical device or that resourceDeviceIndex and memoryDeviceIndex of VkDeviceGroupBindSparseInfo in
577+
// the pnext chain are zero.
578+
579+
if (bind_info.pBufferBinds != nullptr)
580+
{
581+
// The title binds sparse buffers to memory ranges, so we need to track the buffer binding
582+
// information. The following updates will reflect the latest binding states for all buffers in this
583+
// vkQueueBindSparse command, covering both fully-resident and partially-resident buffers.
584+
for (uint32_t buffer_bind_index = 0; buffer_bind_index < bind_info.bufferBindCount;
585+
buffer_bind_index++)
586+
{
587+
auto& buffer_bind = bind_info.pBufferBinds[buffer_bind_index];
588+
auto sparse_buffer = buffer_bind.buffer;
589+
auto wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::BufferWrapper>(sparse_buffer);
590+
591+
if (wrapper != nullptr)
592+
{
593+
wrapper->sparse_bind_queue = queue;
594+
for (uint32_t bind_memory_range_index = 0; bind_memory_range_index < buffer_bind.bindCount;
595+
bind_memory_range_index++)
596+
{
597+
auto& bind_memory_range = buffer_bind.pBinds[bind_memory_range_index];
598+
graphics::UpdateSparseMemoryBindMap(wrapper->sparse_memory_bind_map, bind_memory_range);
599+
}
600+
}
601+
}
602+
}
603+
604+
if (bind_info.pImageOpaqueBinds != nullptr)
605+
{
606+
// The title binds sparse images to opaque memory ranges, so we need to track the image binding
607+
// information. The following handling will update the latest binding states for all images in this
608+
// vkQueueBindSparse command, which utilizes opaque memory binding. There are two cases covered by
609+
// the tracking. In the first case, the sparse image exclusively uses opaque memory binding. For
610+
// this case, the target title treats the binding memory ranges as a linear unified region. This
611+
// should represent a fully-resident binding because this linear region is entirely opaque, meaning
612+
// there is no application-visible mapping between texel locations and memory offsets. In another
613+
// case, the image utilizes subresource sparse memory binding, just binding only its mip tail region
614+
// to an opaque memory range. For this situation, we use the sparse_opaque_memory_bind_map and
615+
// sparse_subresource_memory_bind_map of the image wrapper to track the subresource bindings and
616+
// opaque bindings separately.
617+
for (uint32_t image_opaque_bind_index = 0; image_opaque_bind_index < bind_info.imageOpaqueBindCount;
618+
image_opaque_bind_index++)
619+
{
620+
auto& image_opaque_bind = bind_info.pImageOpaqueBinds[image_opaque_bind_index];
621+
auto sparse_image = image_opaque_bind.image;
622+
auto wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::ImageWrapper>(sparse_image);
623+
624+
if (wrapper != nullptr)
625+
{
626+
wrapper->sparse_bind_queue = queue;
627+
628+
for (uint32_t bind_memory_range_index = 0;
629+
bind_memory_range_index < image_opaque_bind.bindCount;
630+
bind_memory_range_index++)
631+
{
632+
auto& bind_memory_range = image_opaque_bind.pBinds[bind_memory_range_index];
633+
graphics::UpdateSparseMemoryBindMap(wrapper->sparse_opaque_memory_bind_map,
634+
bind_memory_range);
635+
}
636+
}
637+
}
638+
}
639+
640+
if (bind_info.pImageBinds != nullptr)
641+
{
642+
// The title binds subresources of a sparse image to memory ranges, which requires us to keep track
643+
// of the sparse image subresource binding information. It's important to note that while the image
644+
// mainly use subresource sparse memory binding, its mip tail region must be bound to an opaque
645+
// memory range. Therefore, we use the sparse_opaque_memory_bind_map and
646+
// sparse_subresource_memory_bind_map of the image wrapper to separately track both the
647+
// subresource bindings and the opaque bindings.
648+
for (uint32_t image_bind_index = 0; image_bind_index < bind_info.imageBindCount; image_bind_index++)
649+
{
650+
auto& image_bind = bind_info.pImageBinds[image_bind_index];
651+
auto sparse_image = image_bind.image;
652+
auto wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::ImageWrapper>(sparse_image);
653+
654+
if (wrapper != nullptr)
655+
{
656+
wrapper->sparse_bind_queue = queue;
657+
658+
for (uint32_t bind_memory_range_index = 0; bind_memory_range_index < image_bind.bindCount;
659+
bind_memory_range_index++)
660+
{
661+
auto& bind_memory_range = image_bind.pBinds[bind_memory_range_index];
662+
// TODO: Implement handling for tracking binding information of sparse image
663+
// subresources.
664+
GFXRECON_LOG_ERROR_ONCE("Binding of sparse image blocks is not supported!");
665+
}
666+
}
667+
}
668+
}
669+
}
566670
}
567671
}
568672

@@ -842,6 +946,50 @@ class VulkanCaptureManager : public ApiCaptureManager
842946
}
843947
}
844948

949+
void PostProcess_vkCreateBuffer(VkResult result,
950+
VkDevice device,
951+
const VkBufferCreateInfo* pCreateInfo,
952+
const VkAllocationCallbacks* pAllocator,
953+
VkBuffer* pBuffer)
954+
{
955+
if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr))
956+
{
957+
assert(state_tracker_ != nullptr);
958+
959+
auto buffer_wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::BufferWrapper>(*pBuffer);
960+
961+
if (buffer_wrapper->is_sparse_buffer)
962+
{
963+
// We will need to set the bind_device for handling sparse buffers. There will be no subsequent
964+
// vkBindBufferMemory, vkBindBufferMemory2 or vkBindBufferMemory2KHR calls for sparse buffer, so we
965+
// assign bind_device to the device that created the buffer.
966+
buffer_wrapper->bind_device = vulkan_wrappers::GetWrapper<vulkan_wrappers::DeviceWrapper>(device);
967+
}
968+
}
969+
}
970+
971+
void PostProcess_vkCreateImage(VkResult result,
972+
VkDevice device,
973+
const VkImageCreateInfo* pCreateInfo,
974+
const VkAllocationCallbacks* pAllocator,
975+
VkImage* pImage)
976+
{
977+
if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr))
978+
{
979+
assert(state_tracker_ != nullptr);
980+
981+
auto image_wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::ImageWrapper>(*pImage);
982+
983+
if (image_wrapper->is_sparse_image)
984+
{
985+
// We will need to set the bind_device for handling sparse images. There will be no subsequent
986+
// vkBindImageMemory, vkBindImageMemory2, or vkBindImageMemory2KHR calls for sparse image, so we assign
987+
// bind_device to the device that created the image.
988+
image_wrapper->bind_device = vulkan_wrappers::GetWrapper<vulkan_wrappers::DeviceWrapper>(device);
989+
}
990+
}
991+
}
992+
845993
void PostProcess_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer,
846994
const VkRenderPassBeginInfo* pRenderPassBegin,
847995
VkSubpassContents)
@@ -1662,6 +1810,7 @@ class VulkanCaptureManager : public ApiCaptureManager
16621810
std::unique_ptr<VulkanStateTracker> state_tracker_;
16631811
HardwareBufferMap hardware_buffers_;
16641812
std::mutex deferred_operation_mutex;
1813+
std::mutex sparse_resource_mutex;
16651814
};
16661815

16671816
GFXRECON_END_NAMESPACE(encode)

framework/encode/vulkan_handle_wrappers.h

+28
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "format/format.h"
3131
#include "generated/generated_vulkan_dispatch_table.h"
3232
#include "graphics/vulkan_device_util.h"
33+
#include "graphics/vulkan_resources_util.h"
3334
#include "util/defines.h"
3435
#include "util/memory_output_stream.h"
3536
#include "util/page_guard_manager.h"
@@ -210,6 +211,19 @@ struct BufferWrapper : public HandleWrapper<VkBuffer>, AssetWrapperBase
210211
VkBufferUsageFlags usage{ 0 };
211212

212213
std::set<BufferViewWrapper*> buffer_views;
214+
215+
DeviceWrapper* bind_device{ nullptr };
216+
const void* bind_pnext{ nullptr };
217+
std::unique_ptr<uint8_t[]> bind_pnext_memory;
218+
219+
bool is_sparse_buffer{ false };
220+
std::map<VkDeviceSize, VkSparseMemoryBind> sparse_memory_bind_map;
221+
VkQueue sparse_bind_queue;
222+
223+
format::HandleId bind_memory_id{ format::kNullHandleId };
224+
VkDeviceSize bind_offset{ 0 };
225+
uint32_t queue_family_index{ 0 };
226+
VkDeviceSize created_size{ 0 };
213227
};
214228

215229
struct ImageViewWrapper;
@@ -226,6 +240,20 @@ struct ImageWrapper : public HandleWrapper<VkImage>, AssetWrapperBase
226240
bool is_swapchain_image{ false };
227241

228242
std::set<ImageViewWrapper*> image_views;
243+
244+
DeviceWrapper* bind_device{ nullptr };
245+
const void* bind_pnext{ nullptr };
246+
std::unique_ptr<uint8_t[]> bind_pnext_memory;
247+
248+
bool is_sparse_image{ false };
249+
std::map<VkDeviceSize, VkSparseMemoryBind> sparse_opaque_memory_bind_map;
250+
graphics::VulkanSubresourceSparseImageMemoryBindMap sparse_subresource_memory_bind_map;
251+
VkQueue sparse_bind_queue;
252+
253+
format::HandleId bind_memory_id{ format::kNullHandleId };
254+
VkDeviceSize bind_offset{ 0 };
255+
uint32_t queue_family_index{ 0 };
256+
std::set<VkSwapchainKHR> parent_swapchains;
229257
};
230258

231259
struct SamplerWrapper : public HandleWrapper<VkSampler>

framework/encode/vulkan_state_tracker_initializers.h

+12
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,13 @@ inline void InitializeState<VkDevice, vulkan_wrappers::BufferWrapper, VkBufferCr
608608
wrapper->create_call_id = create_call_id;
609609
wrapper->create_parameters = std::move(create_parameters);
610610

611+
wrapper->created_size = create_info->size;
612+
613+
if ((create_info->flags & VK_BUFFER_CREATE_SPARSE_BINDING_BIT) != 0)
614+
{
615+
wrapper->is_sparse_buffer = true;
616+
}
617+
611618
// TODO: Do we need to track the queue family that the buffer is actually used with?
612619
if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr))
613620
{
@@ -641,6 +648,11 @@ inline void InitializeState<VkDevice, vulkan_wrappers::ImageWrapper, VkImageCrea
641648
wrapper->samples = create_info->samples;
642649
wrapper->tiling = create_info->tiling;
643650

651+
if ((create_info->flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT) != 0)
652+
{
653+
wrapper->is_sparse_image = true;
654+
}
655+
644656
// TODO: Do we need to track the queue family that the image is actually used with?
645657
if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr))
646658
{

0 commit comments

Comments
 (0)