Skip to content

Commit b7b8f70

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 a3e57a8 commit b7b8f70

7 files changed

+662
-64
lines changed

framework/encode/custom_vulkan_encoder_commands.h

+20
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,26 @@ struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkBindImageMemory2KHR>
495495
}
496496
};
497497

498+
template <>
499+
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCreateBuffer>
500+
{
501+
template <typename... Args>
502+
static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args)
503+
{
504+
manager->PostProcess_vkCreateBuffer(result, args...);
505+
}
506+
};
507+
508+
template <>
509+
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCreateImage>
510+
{
511+
template <typename... Args>
512+
static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args)
513+
{
514+
manager->PostProcess_vkCreateImage(result, args...);
515+
}
516+
};
517+
498518
template <>
499519
struct CustomEncoderPostCall<format::ApiCallId::ApiCall_vkCmdBeginRenderPass>
500520
{

framework/encode/vulkan_capture_manager.h

+150-1
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ class VulkanCaptureManager : public ApiCaptureManager
534534
}
535535

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

@@ -816,6 +920,50 @@ class VulkanCaptureManager : public ApiCaptureManager
816920
}
817921
}
818922

923+
void PostProcess_vkCreateBuffer(VkResult result,
924+
VkDevice device,
925+
const VkBufferCreateInfo* pCreateInfo,
926+
const VkAllocationCallbacks* pAllocator,
927+
VkBuffer* pBuffer)
928+
{
929+
if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr))
930+
{
931+
assert(state_tracker_ != nullptr);
932+
933+
auto buffer_wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::BufferWrapper>(*pBuffer);
934+
935+
if (buffer_wrapper->is_sparse_buffer)
936+
{
937+
// We will need to set the bind_device for handling sparse buffers. There will be no subsequent
938+
// vkBindBufferMemory, vkBindBufferMemory2 or vkBindBufferMemory2KHR calls for sparse buffer, so we
939+
// assign bind_device to the device that created the buffer.
940+
buffer_wrapper->bind_device = vulkan_wrappers::GetWrapper<vulkan_wrappers::DeviceWrapper>(device);
941+
}
942+
}
943+
}
944+
945+
void PostProcess_vkCreateImage(VkResult result,
946+
VkDevice device,
947+
const VkImageCreateInfo* pCreateInfo,
948+
const VkAllocationCallbacks* pAllocator,
949+
VkImage* pImage)
950+
{
951+
if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr))
952+
{
953+
assert(state_tracker_ != nullptr);
954+
955+
auto image_wrapper = vulkan_wrappers::GetWrapper<vulkan_wrappers::ImageWrapper>(*pImage);
956+
957+
if (image_wrapper->is_sparse_image)
958+
{
959+
// We will need to set the bind_device for handling sparse images. There will be no subsequent
960+
// vkBindImageMemory, vkBindImageMemory2, or vkBindImageMemory2KHR calls for sparse image, so we assign
961+
// bind_device to the device that created the image.
962+
image_wrapper->bind_device = vulkan_wrappers::GetWrapper<vulkan_wrappers::DeviceWrapper>(device);
963+
}
964+
}
965+
}
966+
819967
void PostProcess_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer,
820968
const VkRenderPassBeginInfo* pRenderPassBegin,
821969
VkSubpassContents)
@@ -1380,6 +1528,7 @@ class VulkanCaptureManager : public ApiCaptureManager
13801528
std::unique_ptr<VulkanStateTracker> state_tracker_;
13811529
HardwareBufferMap hardware_buffers_;
13821530
std::mutex deferred_operation_mutex;
1531+
std::mutex sparse_resource_mutex;
13831532
};
13841533

13851534
GFXRECON_END_NAMESPACE(encode)

framework/encode/vulkan_handle_wrappers.h

+10
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,10 @@ struct BufferWrapper : public HandleWrapper<VkBuffer>
210211
const void* bind_pnext{ nullptr };
211212
std::unique_ptr<uint8_t[]> bind_pnext_memory;
212213

214+
bool is_sparse_buffer{ false };
215+
std::map<VkDeviceSize, VkSparseMemoryBind> sparse_memory_bind_map;
216+
VkQueue sparse_bind_queue;
217+
213218
format::HandleId bind_memory_id{ format::kNullHandleId };
214219
VkDeviceSize bind_offset{ 0 };
215220
uint32_t queue_family_index{ 0 };
@@ -227,6 +232,11 @@ struct ImageWrapper : public HandleWrapper<VkImage>
227232
const void* bind_pnext{ nullptr };
228233
std::unique_ptr<uint8_t[]> bind_pnext_memory;
229234

235+
bool is_sparse_image{ false };
236+
std::map<VkDeviceSize, VkSparseMemoryBind> sparse_opaque_memory_bind_map;
237+
graphics::VulkanSubresourceSparseImageMemoryBindMap sparse_subresource_memory_bind_map;
238+
VkQueue sparse_bind_queue;
239+
230240
format::HandleId bind_memory_id{ format::kNullHandleId };
231241
VkDeviceSize bind_offset{ 0 };
232242
uint32_t queue_family_index{ 0 };

framework/encode/vulkan_state_tracker_initializers.h

+10
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ inline void InitializeState<VkDevice, vulkan_wrappers::BufferWrapper, VkBufferCr
605605

606606
wrapper->created_size = create_info->size;
607607

608+
if ((create_info->flags & VK_BUFFER_CREATE_SPARSE_BINDING_BIT) != 0)
609+
{
610+
wrapper->is_sparse_buffer = true;
611+
}
612+
608613
// TODO: Do we need to track the queue family that the buffer is actually used with?
609614
if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr))
610615
{
@@ -638,6 +643,11 @@ inline void InitializeState<VkDevice, vulkan_wrappers::ImageWrapper, VkImageCrea
638643
wrapper->samples = create_info->samples;
639644
wrapper->tiling = create_info->tiling;
640645

646+
if ((create_info->flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT) != 0)
647+
{
648+
wrapper->is_sparse_image = true;
649+
}
650+
641651
// TODO: Do we need to track the queue family that the image is actually used with?
642652
if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr))
643653
{

0 commit comments

Comments
 (0)