diff --git a/framework/encode/custom_vulkan_encoder_commands.h b/framework/encode/custom_vulkan_encoder_commands.h index 4a75b94303..f7aa9690ab 100644 --- a/framework/encode/custom_vulkan_encoder_commands.h +++ b/framework/encode/custom_vulkan_encoder_commands.h @@ -505,6 +505,26 @@ struct CustomEncoderPostCall } }; +template <> +struct CustomEncoderPostCall +{ + template + static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args) + { + manager->PostProcess_vkCreateBuffer(result, args...); + } +}; + +template <> +struct CustomEncoderPostCall +{ + template + static void Dispatch(VulkanCaptureManager* manager, VkResult result, Args... args) + { + manager->PostProcess_vkCreateImage(result, args...); + } +}; + template <> struct CustomEncoderPostCall { diff --git a/framework/encode/vulkan_capture_manager.h b/framework/encode/vulkan_capture_manager.h index 030442a683..b9afa95077 100644 --- a/framework/encode/vulkan_capture_manager.h +++ b/framework/encode/vulkan_capture_manager.h @@ -551,7 +551,7 @@ class VulkanCaptureManager : public ApiCaptureManager } void PostProcess_vkQueueBindSparse( - VkResult result, VkQueue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence) + VkResult result, VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence) { if (IsCaptureModeTrack() && (result == VK_SUCCESS)) { @@ -563,6 +563,110 @@ class VulkanCaptureManager : public ApiCaptureManager pBindInfo[i].signalSemaphoreCount, pBindInfo[i].pSignalSemaphores); } + + // In default mode, the capture manager uses a shared mutex to capture every API function. As a result, + // multiple threads may access the sparse resource maps concurrently. Therefore, we use a dedicated mutex + // for write access to these maps. + const std::lock_guard lock(sparse_resource_mutex); + for (uint32_t bind_info_index = 0; bind_info_index < bindInfoCount; bind_info_index++) + { + auto& bind_info = pBindInfo[bind_info_index]; + + // TODO: add device group support. In the following handling, we assume that the system only has one + // physical device or that resourceDeviceIndex and memoryDeviceIndex of VkDeviceGroupBindSparseInfo in + // the pnext chain are zero. + + if (bind_info.pBufferBinds != nullptr) + { + // The title binds sparse buffers to memory ranges, so we need to track the buffer binding + // information. The following updates will reflect the latest binding states for all buffers in this + // vkQueueBindSparse command, covering both fully-resident and partially-resident buffers. + for (uint32_t buffer_bind_index = 0; buffer_bind_index < bind_info.bufferBindCount; + buffer_bind_index++) + { + auto& buffer_bind = bind_info.pBufferBinds[buffer_bind_index]; + auto sparse_buffer = buffer_bind.buffer; + auto wrapper = vulkan_wrappers::GetWrapper(sparse_buffer); + + if (wrapper != nullptr) + { + wrapper->sparse_bind_queue = queue; + for (uint32_t bind_memory_range_index = 0; bind_memory_range_index < buffer_bind.bindCount; + bind_memory_range_index++) + { + auto& bind_memory_range = buffer_bind.pBinds[bind_memory_range_index]; + graphics::UpdateSparseMemoryBindMap(wrapper->sparse_memory_bind_map, bind_memory_range); + } + } + } + } + + if (bind_info.pImageOpaqueBinds != nullptr) + { + // The title binds sparse images to opaque memory ranges, so we need to track the image binding + // information. The following handling will update the latest binding states for all images in this + // vkQueueBindSparse command, which utilizes opaque memory binding. There are two cases covered by + // the tracking. In the first case, the sparse image exclusively uses opaque memory binding. For + // this case, the target title treats the binding memory ranges as a linear unified region. This + // should represent a fully-resident binding because this linear region is entirely opaque, meaning + // there is no application-visible mapping between texel locations and memory offsets. In another + // case, the image utilizes subresource sparse memory binding, just binding only its mip tail region + // to an opaque memory range. For this situation, we use the sparse_opaque_memory_bind_map and + // sparse_subresource_memory_bind_map of the image wrapper to track the subresource bindings and + // opaque bindings separately. + for (uint32_t image_opaque_bind_index = 0; image_opaque_bind_index < bind_info.imageOpaqueBindCount; + image_opaque_bind_index++) + { + auto& image_opaque_bind = bind_info.pImageOpaqueBinds[image_opaque_bind_index]; + auto sparse_image = image_opaque_bind.image; + auto wrapper = vulkan_wrappers::GetWrapper(sparse_image); + + if (wrapper != nullptr) + { + wrapper->sparse_bind_queue = queue; + + for (uint32_t bind_memory_range_index = 0; + bind_memory_range_index < image_opaque_bind.bindCount; + bind_memory_range_index++) + { + auto& bind_memory_range = image_opaque_bind.pBinds[bind_memory_range_index]; + graphics::UpdateSparseMemoryBindMap(wrapper->sparse_opaque_memory_bind_map, + bind_memory_range); + } + } + } + } + + if (bind_info.pImageBinds != nullptr) + { + // The title binds subresources of a sparse image to memory ranges, which requires us to keep track + // of the sparse image subresource binding information. It's important to note that while the image + // mainly use subresource sparse memory binding, its mip tail region must be bound to an opaque + // memory range. Therefore, we use the sparse_opaque_memory_bind_map and + // sparse_subresource_memory_bind_map of the image wrapper to separately track both the + // subresource bindings and the opaque bindings. + for (uint32_t image_bind_index = 0; image_bind_index < bind_info.imageBindCount; image_bind_index++) + { + auto& image_bind = bind_info.pImageBinds[image_bind_index]; + auto sparse_image = image_bind.image; + auto wrapper = vulkan_wrappers::GetWrapper(sparse_image); + + if (wrapper != nullptr) + { + wrapper->sparse_bind_queue = queue; + + for (uint32_t bind_memory_range_index = 0; bind_memory_range_index < image_bind.bindCount; + bind_memory_range_index++) + { + auto& bind_memory_range = image_bind.pBinds[bind_memory_range_index]; + // TODO: Implement handling for tracking binding information of sparse image + // subresources. + GFXRECON_LOG_ERROR_ONCE("Binding of sparse image blocks is not supported!"); + } + } + } + } + } } } @@ -842,6 +946,50 @@ class VulkanCaptureManager : public ApiCaptureManager } } + void PostProcess_vkCreateBuffer(VkResult result, + VkDevice device, + const VkBufferCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkBuffer* pBuffer) + { + if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr)) + { + assert(state_tracker_ != nullptr); + + auto buffer_wrapper = vulkan_wrappers::GetWrapper(*pBuffer); + + if (buffer_wrapper->is_sparse_buffer) + { + // We will need to set the bind_device for handling sparse buffers. There will be no subsequent + // vkBindBufferMemory, vkBindBufferMemory2 or vkBindBufferMemory2KHR calls for sparse buffer, so we + // assign bind_device to the device that created the buffer. + buffer_wrapper->bind_device = vulkan_wrappers::GetWrapper(device); + } + } + } + + void PostProcess_vkCreateImage(VkResult result, + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage) + { + if (IsCaptureModeTrack() && (result == VK_SUCCESS) && (pCreateInfo != nullptr)) + { + assert(state_tracker_ != nullptr); + + auto image_wrapper = vulkan_wrappers::GetWrapper(*pImage); + + if (image_wrapper->is_sparse_image) + { + // We will need to set the bind_device for handling sparse images. There will be no subsequent + // vkBindImageMemory, vkBindImageMemory2, or vkBindImageMemory2KHR calls for sparse image, so we assign + // bind_device to the device that created the image. + image_wrapper->bind_device = vulkan_wrappers::GetWrapper(device); + } + } + } + void PostProcess_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents) @@ -1662,6 +1810,7 @@ class VulkanCaptureManager : public ApiCaptureManager std::unique_ptr state_tracker_; HardwareBufferMap hardware_buffers_; std::mutex deferred_operation_mutex; + std::mutex sparse_resource_mutex; }; GFXRECON_END_NAMESPACE(encode) diff --git a/framework/encode/vulkan_handle_wrappers.h b/framework/encode/vulkan_handle_wrappers.h index f50d2f9120..567123b8f8 100644 --- a/framework/encode/vulkan_handle_wrappers.h +++ b/framework/encode/vulkan_handle_wrappers.h @@ -30,6 +30,7 @@ #include "format/format.h" #include "generated/generated_vulkan_dispatch_table.h" #include "graphics/vulkan_device_util.h" +#include "graphics/vulkan_resources_util.h" #include "util/defines.h" #include "util/memory_output_stream.h" #include "util/page_guard_manager.h" @@ -210,6 +211,19 @@ struct BufferWrapper : public HandleWrapper, AssetWrapperBase VkBufferUsageFlags usage{ 0 }; std::set buffer_views; + + DeviceWrapper* bind_device{ nullptr }; + const void* bind_pnext{ nullptr }; + std::unique_ptr bind_pnext_memory; + + bool is_sparse_buffer{ false }; + std::map sparse_memory_bind_map; + VkQueue sparse_bind_queue; + + format::HandleId bind_memory_id{ format::kNullHandleId }; + VkDeviceSize bind_offset{ 0 }; + uint32_t queue_family_index{ 0 }; + VkDeviceSize created_size{ 0 }; }; struct ImageViewWrapper; @@ -226,6 +240,20 @@ struct ImageWrapper : public HandleWrapper, AssetWrapperBase bool is_swapchain_image{ false }; std::set image_views; + + DeviceWrapper* bind_device{ nullptr }; + const void* bind_pnext{ nullptr }; + std::unique_ptr bind_pnext_memory; + + bool is_sparse_image{ false }; + std::map sparse_opaque_memory_bind_map; + graphics::VulkanSubresourceSparseImageMemoryBindMap sparse_subresource_memory_bind_map; + VkQueue sparse_bind_queue; + + format::HandleId bind_memory_id{ format::kNullHandleId }; + VkDeviceSize bind_offset{ 0 }; + uint32_t queue_family_index{ 0 }; + std::set parent_swapchains; }; struct SamplerWrapper : public HandleWrapper diff --git a/framework/encode/vulkan_state_tracker_initializers.h b/framework/encode/vulkan_state_tracker_initializers.h index 2361053118..6712598ede 100644 --- a/framework/encode/vulkan_state_tracker_initializers.h +++ b/framework/encode/vulkan_state_tracker_initializers.h @@ -608,6 +608,13 @@ inline void InitializeStatecreate_call_id = create_call_id; wrapper->create_parameters = std::move(create_parameters); + wrapper->created_size = create_info->size; + + if ((create_info->flags & VK_BUFFER_CREATE_SPARSE_BINDING_BIT) != 0) + { + wrapper->is_sparse_buffer = true; + } + // TODO: Do we need to track the queue family that the buffer is actually used with? if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr)) { @@ -641,6 +648,11 @@ inline void InitializeStatesamples = create_info->samples; wrapper->tiling = create_info->tiling; + if ((create_info->flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT) != 0) + { + wrapper->is_sparse_image = true; + } + // TODO: Do we need to track the queue family that the image is actually used with? if ((create_info->queueFamilyIndexCount > 0) && (create_info->pQueueFamilyIndices != nullptr)) { diff --git a/framework/encode/vulkan_state_writer.cpp b/framework/encode/vulkan_state_writer.cpp index c75a0c8431..0369b1eb0f 100644 --- a/framework/encode/vulkan_state_writer.cpp +++ b/framework/encode/vulkan_state_writer.cpp @@ -2020,7 +2020,7 @@ void VulkanStateWriter::ProcessBufferMemory(const vulkan_wrappers::DeviceWrapper const uint8_t* bytes = nullptr; std::vector data; - assert((buffer_wrapper != nullptr) && (memory_wrapper != nullptr)); + assert(buffer_wrapper != nullptr); if (snapshot_entry.need_staging_copy) { @@ -2034,6 +2034,7 @@ void VulkanStateWriter::ProcessBufferMemory(const vulkan_wrappers::DeviceWrapper } else { + assert(memory_wrapper != nullptr); assert((memory_wrapper->mapped_data == nullptr) || (memory_wrapper->mapped_offset == 0)); VkResult result = VK_SUCCESS; @@ -2582,22 +2583,21 @@ void VulkanStateWriter::WriteBufferMemoryState(const VulkanStateTable& state_tab state_table.VisitWrappers([&](vulkan_wrappers::BufferWrapper* wrapper) { assert(wrapper != nullptr); - - // Perform memory binding. - const vulkan_wrappers::DeviceMemoryWrapper* memory_wrapper = - state_table.GetDeviceMemoryWrapper(wrapper->bind_memory_id); - - if (memory_wrapper != nullptr) + if (!wrapper->is_sparse_buffer) { - const vulkan_wrappers::DeviceWrapper* device_wrapper = wrapper->bind_device; - assert(device_wrapper != nullptr); + // Perform memory binding for non-sparse buffer. + const vulkan_wrappers::DeviceMemoryWrapper* memory_wrapper = + state_table.GetDeviceMemoryWrapper(wrapper->bind_memory_id); - if (write_memory_state) + if (memory_wrapper != nullptr) { + const vulkan_wrappers::DeviceWrapper* device_wrapper = wrapper->bind_device; + const VulkanDeviceTable* device_table = &device_wrapper->layer_table; + + assert((device_wrapper != nullptr) && (device_table != nullptr)); + // Write memory requirements query before bind command. - VkMemoryRequirements memory_requirements; - const VulkanDeviceTable* device_table = &device_wrapper->layer_table; - assert(device_table != nullptr); + VkMemoryRequirements memory_requirements; device_table->GetBufferMemoryRequirements( device_wrapper->handle, wrapper->handle, &memory_requirements); @@ -2637,17 +2637,127 @@ void VulkanStateWriter::WriteBufferMemoryState(const VulkanStateTable& state_tab WriteFunctionCall(format::ApiCall_vkBindBufferMemory2, ¶meter_stream_); } parameter_stream_.Clear(); + + // Group buffers with memory bindings by device for memory snapshot. + ResourceSnapshotQueueFamilyTable& snapshot_table = (*resources)[device_wrapper]; + ResourceSnapshotInfo& snapshot_entry = snapshot_table[wrapper->queue_family_index]; + + BufferSnapshotInfo snapshot_info; + snapshot_info.buffer_wrapper = wrapper; + snapshot_info.memory_wrapper = memory_wrapper; + snapshot_info.memory_properties = GetMemoryProperties(device_wrapper, memory_wrapper); + snapshot_info.need_staging_copy = !IsBufferReadable(snapshot_info.memory_properties, memory_wrapper); + + if ((*max_resource_size) < wrapper->created_size) + { + (*max_resource_size) = wrapper->created_size; + } + + if (snapshot_info.need_staging_copy && ((*max_staging_copy_size) < wrapper->created_size)) + { + (*max_staging_copy_size) = wrapper->created_size; + } + + snapshot_entry.buffers.emplace_back(snapshot_info); + } + } + else + { + // Perform memory binding for sparse buffer. + const vulkan_wrappers::DeviceWrapper* device_wrapper = wrapper->bind_device; + const VulkanDeviceTable* device_table = &device_wrapper->layer_table; + assert((device_wrapper != nullptr) && (device_table != nullptr)); + + // We do not need to use sparse_resource_mutex for the access to the following sparse resource maps, as the + // writing states operation is included in the trim start handling, which is protected by an exclusive lock. + // No other API capturing handling occurs concurrently. + if (wrapper->sparse_memory_bind_map.size() != 0) + { + std::vector sparse_memory_binds; + VkSparseBufferMemoryBindInfo buffer_memory_bind_info = {}; + + // Write memory requirements query before vkQueueBindSparse command. For sparse buffer, the alignment of + // VkMemoryRequirements is the sparse block size in bytes which represents both the memory alignment + // requirement and the binding granularity (in bytes) for sparse buffer. + VkMemoryRequirements memory_requirements; + + device_table->GetBufferMemoryRequirements( + device_wrapper->handle, wrapper->handle, &memory_requirements); + + encoder_.EncodeHandleIdValue(device_wrapper->handle_id); + encoder_.EncodeHandleIdValue(wrapper->handle_id); + EncodeStructPtr(&encoder_, &memory_requirements); + + WriteFunctionCall(format::ApiCall_vkGetBufferMemoryRequirements, ¶meter_stream_); + parameter_stream_.Clear(); + + const vulkan_wrappers::QueueWrapper* sparse_bind_queue_wrapper = + vulkan_wrappers::GetWrapper(wrapper->sparse_bind_queue); + + if ((wrapper->sparse_bind_queue != VK_NULL_HANDLE) && (sparse_bind_queue_wrapper != nullptr)) + { + for (auto& item : wrapper->sparse_memory_bind_map) + { + sparse_memory_binds.push_back(item.second); + } + + buffer_memory_bind_info.buffer = wrapper->handle; + buffer_memory_bind_info.bindCount = sparse_memory_binds.size(); + buffer_memory_bind_info.pBinds = sparse_memory_binds.data(); + + VkBindSparseInfo bind_sparse_info{}; + bind_sparse_info.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO; + bind_sparse_info.pNext = nullptr; + bind_sparse_info.waitSemaphoreCount = 0; + bind_sparse_info.pWaitSemaphores = nullptr; + bind_sparse_info.bufferBindCount = 1; + bind_sparse_info.pBufferBinds = &buffer_memory_bind_info; + bind_sparse_info.imageOpaqueBindCount = 0; + bind_sparse_info.pImageOpaqueBinds = nullptr; + bind_sparse_info.imageBindCount = 0; + bind_sparse_info.pImageBinds = nullptr; + bind_sparse_info.signalSemaphoreCount = 0; + bind_sparse_info.pSignalSemaphores = nullptr; + + encoder_.EncodeVulkanHandleValue(wrapper->sparse_bind_queue); + encoder_.EncodeUInt32Value(1); + EncodeStructArray(&encoder_, &bind_sparse_info, 1); + encoder_.EncodeVulkanHandleValue(VK_NULL_HANDLE); + encoder_.EncodeEnumValue(VK_SUCCESS); + WriteFunctionCall(format::ApiCall_vkQueueBindSparse, ¶meter_stream_); + + parameter_stream_.Clear(); + + encoder_.EncodeHandleIdValue(device_wrapper->handle_id); + encoder_.EncodeEnumValue(VK_SUCCESS); + + WriteFunctionCall(format::ApiCall_vkDeviceWaitIdle, ¶meter_stream_); + } + else + { + GFXRECON_LOG_WARNING("Unable to generate vkQueueBindSparse for the sparse buffer (id = %d) due to " + "the related sparse bind queue or its wrapper is invalid.", + wrapper->handle_id); + } } + parameter_stream_.Clear(); + // Group buffers with memory bindings by device for memory snapshot. ResourceSnapshotQueueFamilyTable& snapshot_table = (*resources)[device_wrapper]; ResourceSnapshotInfo& snapshot_entry = snapshot_table[wrapper->queue_family_index]; BufferSnapshotInfo snapshot_info; - snapshot_info.buffer_wrapper = wrapper; - snapshot_info.memory_wrapper = memory_wrapper; - snapshot_info.memory_properties = GetMemoryProperties(device_wrapper, memory_wrapper); - snapshot_info.need_staging_copy = !IsBufferReadable(snapshot_info.memory_properties, memory_wrapper); + snapshot_info.buffer_wrapper = wrapper; + snapshot_info.memory_wrapper = nullptr; + + // We enforce the memory properties to be `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`, and we set + // `need_staging_copy` to true for sparse buffers. When dumping buffer data, there are two distinct code + // paths: one involves a staging copy, while the other requires mapping host-visible memory. This latter + // method requires the buffer to be bound to a single range of a single memory, which is not applicable for + // sparse buffers. Therefore, we set the two values to use staging copy for dumping sparse buffers. + snapshot_info.memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + snapshot_info.need_staging_copy = true; // Staging copy is needed for sparse buffer. if ((*max_resource_size) < wrapper->size) { @@ -2680,7 +2790,8 @@ void VulkanStateWriter::WriteImageMemoryState(const VulkanStateTable& state_tabl state_table.GetDeviceMemoryWrapper(wrapper->bind_memory_id); if ((wrapper->is_swapchain_image && memory_wrapper == nullptr && wrapper->bind_device != nullptr) || - (!wrapper->is_swapchain_image && memory_wrapper != nullptr)) + (!wrapper->is_swapchain_image && memory_wrapper != nullptr) || + (!wrapper->is_swapchain_image && wrapper->is_sparse_image && wrapper->bind_device != nullptr)) { const vulkan_wrappers::DeviceWrapper* device_wrapper = wrapper->bind_device; assert(device_wrapper != nullptr); @@ -2688,10 +2799,11 @@ void VulkanStateWriter::WriteImageMemoryState(const VulkanStateTable& state_tabl // Write memory requirements query before bind command. if (write_memory_state) { - VkMemoryRequirements memory_requirements; const VulkanDeviceTable* device_table = &device_wrapper->layer_table; assert(device_table != nullptr); + VkMemoryRequirements memory_requirements; + device_table->GetImageMemoryRequirements(device_wrapper->handle, wrapper->handle, &memory_requirements); encoder_.EncodeHandleIdValue(device_wrapper->handle_id); @@ -2702,34 +2814,115 @@ void VulkanStateWriter::WriteImageMemoryState(const VulkanStateTable& state_tabl parameter_stream_.Clear(); // Write memory bind command. - if (wrapper->bind_pnext == nullptr) + if (!wrapper->is_sparse_image) { - encoder_.EncodeHandleIdValue(device_wrapper->handle_id); - encoder_.EncodeHandleIdValue(wrapper->handle_id); - encoder_.EncodeHandleIdValue(memory_wrapper->handle_id); - encoder_.EncodeUInt64Value(wrapper->bind_offset); - encoder_.EncodeEnumValue(VK_SUCCESS); + if (wrapper->bind_pnext == nullptr) + { + encoder_.EncodeHandleIdValue(device_wrapper->handle_id); + encoder_.EncodeHandleIdValue(wrapper->handle_id); + encoder_.EncodeHandleIdValue(memory_wrapper->handle_id); + encoder_.EncodeUInt64Value(wrapper->bind_offset); + encoder_.EncodeEnumValue(VK_SUCCESS); - WriteFunctionCall(format::ApiCall_vkBindImageMemory, ¶meter_stream_); + WriteFunctionCall(format::ApiCall_vkBindImageMemory, ¶meter_stream_); + } + else + { + encoder_.EncodeHandleIdValue(device_wrapper->handle_id); + encoder_.EncodeUInt32Value(1); + + VkBindImageMemoryInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + info.pNext = wrapper->bind_pnext; + info.image = wrapper->handle; + info.memory = memory_wrapper->handle; + info.memoryOffset = wrapper->bind_offset; + EncodeStructArray(&encoder_, &info, 1); + encoder_.EncodeEnumValue(VK_SUCCESS); + + WriteFunctionCall(format::ApiCall_vkBindImageMemory2, ¶meter_stream_); + } } - else + } + else + { + const vulkan_wrappers::DeviceWrapper* device_wrapper = wrapper->bind_device; + const VulkanDeviceTable* device_table = &device_wrapper->layer_table; + assert((device_wrapper != nullptr) && (device_table != nullptr)); + + const vulkan_wrappers::QueueWrapper* sparse_bind_queue_wrapper = + vulkan_wrappers::GetWrapper(wrapper->sparse_bind_queue); + + GFXRECON_ASSERT((wrapper->sparse_bind_queue != VK_NULL_HANDLE) && + (sparse_bind_queue_wrapper != nullptr)); + + if ((wrapper->sparse_opaque_memory_bind_map.size() != 0) || + (wrapper->sparse_subresource_memory_bind_map.size() != 0)) { - encoder_.EncodeHandleIdValue(device_wrapper->handle_id); + std::vector sparse_memory_binds; + VkSparseImageOpaqueMemoryBindInfo image_opaque_memory_bind_info = {}; + + for (auto& item : wrapper->sparse_opaque_memory_bind_map) + { + sparse_memory_binds.push_back(item.second); + } + + image_opaque_memory_bind_info.image = wrapper->handle; + image_opaque_memory_bind_info.bindCount = sparse_memory_binds.size(); + image_opaque_memory_bind_info.pBinds = + (sparse_memory_binds.size() == 0) ? nullptr : sparse_memory_binds.data(); + + std::vector sparse_image_memory_binds; + VkSparseImageMemoryBindInfo image_memory_bind_info = {}; + + for (auto& subresource_bind_map : wrapper->sparse_subresource_memory_bind_map) + { + auto& offset_3d_to_memory_range_map = subresource_bind_map.second; + + for (auto& item : offset_3d_to_memory_range_map) + { + sparse_image_memory_binds.push_back(item.second); + } + } + + image_memory_bind_info.image = wrapper->handle; + image_memory_bind_info.bindCount = sparse_image_memory_binds.size(); + image_memory_bind_info.pBinds = + (sparse_image_memory_binds.size() == 0) ? nullptr : sparse_image_memory_binds.data(); + + VkBindSparseInfo bind_sparse_info{}; + + bind_sparse_info.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO; + bind_sparse_info.pNext = nullptr; + bind_sparse_info.waitSemaphoreCount = 0; + bind_sparse_info.pWaitSemaphores = nullptr; + bind_sparse_info.bufferBindCount = 0; + bind_sparse_info.pBufferBinds = nullptr; + bind_sparse_info.imageOpaqueBindCount = (image_opaque_memory_bind_info.bindCount == 0) ? 0 : 1; + bind_sparse_info.pImageOpaqueBinds = + (image_opaque_memory_bind_info.bindCount == 0) ? nullptr : &image_opaque_memory_bind_info; + bind_sparse_info.imageBindCount = (image_memory_bind_info.bindCount == 0) ? 0 : 1; + bind_sparse_info.pImageBinds = + (image_memory_bind_info.bindCount == 0) ? nullptr : &image_memory_bind_info; + bind_sparse_info.signalSemaphoreCount = 0; + bind_sparse_info.pSignalSemaphores = nullptr; + + encoder_.EncodeVulkanHandleValue(wrapper->sparse_bind_queue); encoder_.EncodeUInt32Value(1); + EncodeStructArray(&encoder_, &bind_sparse_info, 1); + encoder_.EncodeVulkanHandleValue(VK_NULL_HANDLE); + encoder_.EncodeEnumValue(VK_SUCCESS); + WriteFunctionCall(format::ApiCall_vkQueueBindSparse, ¶meter_stream_); - VkBindImageMemoryInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; - info.pNext = wrapper->bind_pnext; - info.image = wrapper->handle; - info.memory = memory_wrapper->handle; - info.memoryOffset = wrapper->bind_offset; - EncodeStructArray(&encoder_, &info, 1); + parameter_stream_.Clear(); + + encoder_.EncodeHandleIdValue(device_wrapper->handle_id); encoder_.EncodeEnumValue(VK_SUCCESS); - WriteFunctionCall(format::ApiCall_vkBindImageMemory2, ¶meter_stream_); + WriteFunctionCall(format::ApiCall_vkDeviceWaitIdle, ¶meter_stream_); } - parameter_stream_.Clear(); } + parameter_stream_.Clear(); VkMemoryPropertyFlags memory_properties = 0; if (memory_wrapper != nullptr) @@ -2743,6 +2936,13 @@ void VulkanStateWriter::WriteImageMemoryState(const VulkanStateTable& state_tabl (wrapper->tiling == VK_IMAGE_TILING_LINEAR) && ((memory_properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + if (wrapper->is_sparse_image) + { + // The process for dumping host-visible image data requires binding the entire image to a single memory + // range. Since this is not applicable to sparse images, we set is_writable to false. + is_writable = false; + } + // If an image is not host writable and has not been transitioned from the undefined or preinitialized // layouts, no data could have been loaded into it and its data will be omitted from the state snapshot. if (is_transitioned || is_writable) @@ -2756,7 +2956,11 @@ void VulkanStateWriter::WriteImageMemoryState(const VulkanStateTable& state_tabl *device_wrapper->physical_device->layer_table_ref, device_wrapper->physical_device->memory_properties); - bool need_staging_copy = !IsImageReadable(memory_properties, memory_wrapper, wrapper); + // Sparse images require staging copy for the following process because dumping image data with mapping + // memory needs binding the entire image to a single memory range. Sparse image opaque binding allows + // binding to multiple memory objects and various memory ranges. + bool need_staging_copy = + !IsImageReadable(memory_properties, memory_wrapper, wrapper) || wrapper->is_sparse_image; std::vector aspects; bool combined_depth_stencil; diff --git a/framework/graphics/vulkan_resources_util.cpp b/framework/graphics/vulkan_resources_util.cpp index dc84917474..69fd1b2225 100644 --- a/framework/graphics/vulkan_resources_util.cpp +++ b/framework/graphics/vulkan_resources_util.cpp @@ -2077,6 +2077,137 @@ VkResult VulkanResourcesUtil::WriteToImageResourceStaging(VkImage return result; } +bool GetIntersectForSparseMemoryBind(uint32_t new_bind_resource_offset, + uint32_t new_bind_resource_size, + uint32_t existing_bind_resource_offset, + uint32_t existing_bind_resource_size, + uint32_t& intersection_resource_offset, + uint32_t& intersection_resource_size, + std::vector& remaining_resource_offsets, + std::vector& remaining_resource_sizes, + bool& new_bind_range_include_existing_bind_tange, + bool& existing_bind_range_include_new_bind_tange) +{ + bool intersection_exist = false; + uint32_t intersection_start = std::max(new_bind_resource_offset, existing_bind_resource_offset); + uint32_t intersection_end = std::min(new_bind_resource_offset + new_bind_resource_size, + existing_bind_resource_offset + existing_bind_resource_size); + + existing_bind_range_include_new_bind_tange = false; + new_bind_range_include_existing_bind_tange = false; + + if (intersection_start < intersection_end) + { + intersection_exist = true; + intersection_resource_offset = intersection_start; + intersection_resource_size = intersection_end - intersection_start; + + if ((intersection_resource_offset == new_bind_resource_offset) && + (intersection_resource_size == new_bind_resource_size)) + { + existing_bind_range_include_new_bind_tange = true; + } + + if ((intersection_resource_offset == existing_bind_resource_offset) && + (intersection_resource_size == existing_bind_resource_size)) + { + new_bind_range_include_existing_bind_tange = true; + } + + if (intersection_resource_offset > existing_bind_resource_offset) + { + remaining_resource_offsets.push_back(existing_bind_resource_offset); + remaining_resource_sizes.push_back(intersection_resource_offset - existing_bind_resource_offset); + } + + if ((intersection_resource_offset + intersection_resource_size) < + (existing_bind_resource_offset + existing_bind_resource_size)) + { + remaining_resource_offsets.push_back(intersection_resource_offset + intersection_resource_size); + remaining_resource_sizes.push_back((existing_bind_resource_offset + existing_bind_resource_size) - + (intersection_resource_offset + intersection_resource_size)); + } + } + + return intersection_exist; +} + +void UpdateSparseMemoryBindMap(std::map& sparse_memory_bind_map, + const VkSparseMemoryBind& new_sparse_memory_bind) +{ + std::vector all_remaining_existing_bind_ranges{}; + std::vector delete_existing_bind_ranges{}; + + VkDeviceSize search_key = new_sparse_memory_bind.resourceOffset + new_sparse_memory_bind.size; + auto iterator = sparse_memory_bind_map.lower_bound(search_key); + bool is_intersected_with_any_existing_bind = false; + + bool ignored = false; + + if ((sparse_memory_bind_map.size() != 0) && (iterator != sparse_memory_bind_map.begin())) + { + for (auto item = sparse_memory_bind_map.begin(); item != iterator; item++) + { + uint32_t intersection_resource_offset, intersection_resource_size; + std::vector remaining_resource_offsets, remaining_resource_sizes; + bool new_bind_range_include_existing_bind_tange, existing_bind_range_include_new_bind_tange; + + bool is_intersected = GetIntersectForSparseMemoryBind(new_sparse_memory_bind.resourceOffset, + new_sparse_memory_bind.size, + item->second.resourceOffset, + item->second.size, + intersection_resource_offset, + intersection_resource_size, + remaining_resource_offsets, + remaining_resource_sizes, + new_bind_range_include_existing_bind_tange, + existing_bind_range_include_new_bind_tange); + + if (is_intersected) + { + is_intersected_with_any_existing_bind = false; + + VkSparseMemoryBind add_sparse_memory_bind = { 0, 0, item->second.memory, 0, item->second.flags }; + GFXRECON_ASSERT(item->second.flags == new_sparse_memory_bind.flags); + + uint32_t index = 0; + for (auto& bind_offset : remaining_resource_offsets) + { + add_sparse_memory_bind.resourceOffset = bind_offset; + add_sparse_memory_bind.size = remaining_resource_sizes[index]; + add_sparse_memory_bind.memoryOffset = + item->second.memoryOffset + bind_offset - item->second.resourceOffset; + all_remaining_existing_bind_ranges.push_back(add_sparse_memory_bind); + + index++; + } + + delete_existing_bind_ranges.push_back(item->second); + } + } + } + + if (is_intersected_with_any_existing_bind) + { + for (auto& delete_item : delete_existing_bind_ranges) + { + sparse_memory_bind_map.erase(delete_item.resourceOffset); + } + + size_t index = 0, remaining_range_base = 0; + + for (auto add_item : all_remaining_existing_bind_ranges) + { + sparse_memory_bind_map[add_item.resourceOffset] = add_item; + } + } + + if (new_sparse_memory_bind.memory != VK_NULL_HANDLE) + { + sparse_memory_bind_map[new_sparse_memory_bind.resourceOffset] = new_sparse_memory_bind; + } +} + bool VulkanResourcesUtil::IsBlitSupported(VkFormat src_format, VkImageTiling src_image_tiling, VkFormat dst_format, diff --git a/framework/graphics/vulkan_resources_util.h b/framework/graphics/vulkan_resources_util.h index 87c154221a..a55f3af59f 100644 --- a/framework/graphics/vulkan_resources_util.h +++ b/framework/graphics/vulkan_resources_util.h @@ -31,6 +31,7 @@ #include "vulkan/vulkan_core.h" #include +#include GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(graphics) @@ -280,6 +281,76 @@ bool FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& memory_properti uint32_t* found_index, VkMemoryPropertyFlags* found_flags); +struct VkOffset3DComparator +{ + bool operator()(const VkOffset3D& l, const VkOffset3D& r) const + { + bool result = (l.x < r.x); + + if (l.x == r.x) + { + result = (l.y < r.y); + + if (l.y == r.y) + { + result = (l.z < r.z); + } + } + + return result; + } +}; + +typedef std::map + VulkanOffset3DSparseImageMemoryBindMap; + +struct VkImageSubresourceComparator +{ + bool operator()(const VkImageSubresource& l, const VkImageSubresource& r) const + { + bool result = (l.arrayLayer < r.arrayLayer); + + if (l.arrayLayer == r.arrayLayer) + { + result = (l.mipLevel < r.mipLevel); + + if (l.mipLevel == r.mipLevel) + { + result = (l.aspectMask < r.aspectMask); + } + } + + return result; + } +}; + +typedef std:: + map + VulkanSubresourceSparseImageMemoryBindMap; + +// Get the intersection of the new bind range and the existing bind range for sparse buffer or sparse image (opaque +// bind). +// If the intersection range exists, further get the remaining ranges for the existing bind range after removing +// the intersection range. For instance, if the new/existing bind range (offset, size) are (196608, 327680) and (0, +// 655360) respectively, the old range (0, 655360) completely covers the new range (196608, 327680). The intersection +// range is (196608, 327680), and the remaining ranges for the existing bind are (0, 196608) and (524288, 131072). So +// for the return of the function, remaining_resource_offsets will be a std::vector of [0, 524288], and +// remaining_resource_sizes will be [196608, 131072]. +// +bool GetIntersectForSparseMemoryBind(uint32_t new_bind_resource_offset, + uint32_t new_bind_resource_size, + uint32_t existing_bind_resource_offset, + uint32_t existing_bind_resource_size, + uint32_t& intersection_resource_offset, + uint32_t& intersection_resource_size, + std::vector& remaining_resource_offsets, + std::vector& remaining_resource_sizes, + bool& new_bind_range_include_existing_bind_tange, + bool& existing_bind_range_include_new_bind_tange); + +void UpdateSparseMemoryBindMap(std::map& sparse_memory_bind_map, + const VkSparseMemoryBind& new_sparse_memory_bind); + bool GetImageTexelSize(VkFormat format, VkDeviceSize* texel_size, bool* is_texel_block_size, @@ -323,7 +394,7 @@ bool NextRowTexelCoordinates(VkImageType imageType, uint32_t& z, uint32_t& layer); +GFXRECON_END_NAMESPACE(graphics) GFXRECON_END_NAMESPACE(gfxrecon) -GFXRECON_END_NAMESPACE(encode) #endif /* GFXRECON_GRAPHICS_VULKAN_RESOURCES_UTIL_H */ diff --git a/test/test_apps/CMakeLists.txt b/test/test_apps/CMakeLists.txt index e5e44fe9a2..f81b2d54f7 100644 --- a/test/test_apps/CMakeLists.txt +++ b/test/test_apps/CMakeLists.txt @@ -33,3 +33,4 @@ add_subdirectory(multisample-depth) add_subdirectory(pipeline-binaries) add_subdirectory(host-image-copy) add_subdirectory(shader-objects) +add_subdirectory(sparse-resources) diff --git a/test/test_apps/sparse-resources/CMakeLists.txt b/test/test_apps/sparse-resources/CMakeLists.txt new file mode 100644 index 0000000000..0bffe109c1 --- /dev/null +++ b/test/test_apps/sparse-resources/CMakeLists.txt @@ -0,0 +1,74 @@ +############################################################################### +# Copyright (c) 2018-2025 LunarG, Inc. +# All rights reserved +# +# 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. +# +# Author: LunarG Team +# Description: CMake script for sparse-resources test app +############################################################################### + +add_executable(gfxrecon-testapp-sparse-resources "") + +target_sources(gfxrecon-testapp-sparse-resources + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/app.cpp + ${CMAKE_CURRENT_LIST_DIR}/../common/test_app_base.cpp) + +target_include_directories(gfxrecon-testapp-sparse-resources PUBLIC + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../common) + +target_link_libraries(gfxrecon-testapp-sparse-resources + gfxrecon_application + gfxrecon_decode + gfxrecon_graphics + gfxrecon_format + gfxrecon_util + SDL3::SDL3 + platform_specific) + +if (MSVC) + # Force inclusion of "gfxrecon_disable_popup_result" variable in linking. + # On 32-bit windows, MSVC prefixes symbols with "_" but on 64-bit windows it doesn't. + if (CMAKE_SIZEOF_VOID_P EQUAL 4) + target_link_options(gfxrecon-replay PUBLIC "LINKER:/Include:_gfxrecon_disable_popup_result") + else () + target_link_options(gfxrecon-replay PUBLIC "LINKER:/Include:gfxrecon_disable_popup_result") + endif () +endif () + +common_build_directives(gfxrecon-testapp-sparse-resources) + +add_custom_command( + TARGET gfxrecon-testapp-sparse-resources + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_LIST_DIR}/shaders ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}) +if (WIN32) + add_custom_command(TARGET gfxrecon-testapp-sparse-resources POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND_EXPAND_LISTS) +endif () + +install(TARGETS gfxrecon-testapp-sparse-resources RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_dependencies(gfxrecon-testapps gfxrecon-testapp-sparse-resources) \ No newline at end of file diff --git a/test/test_apps/sparse-resources/app.cpp b/test/test_apps/sparse-resources/app.cpp new file mode 100644 index 0000000000..ac1ef2e031 --- /dev/null +++ b/test/test_apps/sparse-resources/app.cpp @@ -0,0 +1,950 @@ +/* +** Copyright (c) 2018-2023 Valve Corporation +** Copyright (c) 2018-2025 LunarG, Inc. +** +** 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 + +#include + +#include + +#include + +GFXRECON_BEGIN_NAMESPACE(gfxrecon) +GFXRECON_BEGIN_NAMESPACE(test_app) +GFXRECON_BEGIN_NAMESPACE(sparse_resources) + +const size_t MAX_FRAMES_IN_FLIGHT = 1; +const size_t STAGING_BUFFER_SIZE = 16 * 1024 * 1024; +const VkFormat IMAGE_FORMAT = VK_FORMAT_R8G8B8A8_SRGB; + +class App : public gfxrecon::test::TestAppBase +{ + private: + VkQueue graphics_queue_; + VkQueue present_queue_; + + std::vector framebuffers_; + + VkRenderPass render_pass_; + VkDescriptorPool descriptor_pool_; + VkDescriptorSetLayout descriptor_layout_; + VkDescriptorSet descriptor_set_; + VkPipelineLayout pipeline_layout_; + VkPipeline graphics_pipeline_; + + VkBuffer staging_buffer_; + uint8_t* staging_buffer_ptr_; + VkImage image0_; + VkImageView image0_view_; + const uint32_t image_size_ = 16; + const uint32_t mip_levels_ = 1; + //VkSparseImageMemoryRequirements image_mem_reqs; + VkDeviceMemory image_backing_memory_; + VkDeviceMemory staging_backing_memory_; + uint32_t device_memory_type_; + uint32_t staging_memory_type_; + + uint32_t sparse_binding_granularity_; + VkExtent3D sparse_block_granularity_; + + VkCommandPool command_pools_[MAX_FRAMES_IN_FLIGHT]; + VkCommandBuffer command_buffers_[MAX_FRAMES_IN_FLIGHT]; + + size_t current_frame_ = 0; + size_t last_frame_ = MAX_FRAMES_IN_FLIGHT - 1; + + gfxrecon::test::Sync sync_; + std::vector sparse_binding_semaphores_; + VkFence immediate_fence_; + + void create_render_pass(); + void create_graphics_pipeline(); + void create_framebuffers(); + void create_descriptor_set(); + void create_staging_buffer(); + void create_images(); + void determine_memory_heaps(); + void configure_instance_builder(test::InstanceBuilder& instance_builder); + void configure_physical_device_selector(test::PhysicalDeviceSelector& phys_device_selector); + bool frame(const int frame_num); + void setup(); +}; + +void App::configure_instance_builder(test::InstanceBuilder& instance_builder) +{ + instance_builder.desire_api_version(VK_MAKE_VERSION(1, 3, 0)); +} + +void App::configure_physical_device_selector(test::PhysicalDeviceSelector& phys_device_selector) +{ + VkPhysicalDeviceFeatures feats = {}; + feats.sparseBinding = true; + feats.sparseResidencyBuffer = true; + feats.sparseResidencyAliased = true; + feats.sparseResidencyImage2D = true; + phys_device_selector.set_required_features(feats); + + phys_device_selector.add_required_extension("VK_KHR_maintenance2"); + + phys_device_selector.prefer_gpu_device_type(test::PreferredDeviceType::discrete); +} + +void App::create_render_pass() +{ + VkAttachmentDescription color_attachment = {}; + color_attachment.format = init.swapchain.image_format; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference color_attachment_ref = {}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + + VkRenderPassCreateInfo render_pass_info = {}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = 1; + render_pass_info.pAttachments = &color_attachment; + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 0; + + auto result = init.disp.createRenderPass(&render_pass_info, nullptr, &render_pass_); + VERIFY_VK_RESULT("failed to create render pass", result); +} + +void App::create_graphics_pipeline() +{ + auto vert_module = gfxrecon::test::readShaderFromFile(init.disp, "tri.vert.spv"); + auto frag_module = gfxrecon::test::readShaderFromFile(init.disp, "tri.frag.spv"); + + VkPipelineShaderStageCreateInfo vert_stage_info = {}; + vert_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_stage_info.module = vert_module; + vert_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo frag_stage_info = {}; + frag_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_stage_info.module = frag_module; + frag_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo shader_stages[] = { vert_stage_info, frag_stage_info }; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.vertexBindingDescriptionCount = 0; + vertex_input_info.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)init.swapchain.extent.width; + viewport.height = (float)init.swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = init.swapchain.extent; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &colorBlendAttachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipeline_layout_info = {}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = 1; + pipeline_layout_info.pSetLayouts = &descriptor_layout_; + pipeline_layout_info.pushConstantRangeCount = 0; + + auto result = init.disp.createPipelineLayout(&pipeline_layout_info, nullptr, &pipeline_layout_); + VERIFY_VK_RESULT("failed to create pipeline layout", result); + + std::vector dynamic_states = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineDynamicStateCreateInfo dynamic_info = {}; + dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_info.dynamicStateCount = static_cast(dynamic_states.size()); + dynamic_info.pDynamicStates = dynamic_states.data(); + + VkGraphicsPipelineCreateInfo pipeline_info = {}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_info; + pipeline_info.layout = pipeline_layout_; + pipeline_info.renderPass = render_pass_; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + + result = init.disp.createGraphicsPipelines(VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &graphics_pipeline_); + VERIFY_VK_RESULT("failed to create graphics pipeline", result); + + init.disp.destroyShaderModule(frag_module, nullptr); + init.disp.destroyShaderModule(vert_module, nullptr); +} + +void App::create_framebuffers() +{ + framebuffers_.resize(init.swapchain_image_views.size()); + + for (size_t i = 0; i < init.swapchain_image_views.size(); i++) + { + VkImageView attachments[] = { init.swapchain_image_views[i] }; + + VkFramebufferCreateInfo framebuffer_info = {}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = render_pass_; + framebuffer_info.attachmentCount = 1; + framebuffer_info.pAttachments = attachments; + framebuffer_info.width = init.swapchain.extent.width; + framebuffer_info.height = init.swapchain.extent.height; + framebuffer_info.layers = 1; + + auto result = init.disp.createFramebuffer(&framebuffer_info, nullptr, &framebuffers_[i]); + VERIFY_VK_RESULT("failed to create framebuffer", result); + } +} + +void App::create_descriptor_set() { + VkDescriptorPoolSize pool_sizes[2] = {}; + pool_sizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + pool_sizes[0].descriptorCount = 1; + pool_sizes[1].type = VK_DESCRIPTOR_TYPE_SAMPLER; + pool_sizes[1].descriptorCount = 1; + + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = 1; + pool_info.poolSizeCount = 2; + pool_info.pPoolSizes = pool_sizes; + VkResult result = init.disp.createDescriptorPool(&pool_info, nullptr, &descriptor_pool_); + VERIFY_VK_RESULT("Failed to create descriptor pool", result); + + // Create immutable sampler + VkSamplerCreateInfo sampler_info = {}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.pNext = nullptr; + sampler_info.flags = 0; + sampler_info.magFilter = VK_FILTER_NEAREST; + sampler_info.minFilter = VK_FILTER_NEAREST; + sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.mipLodBias = 0.0; + sampler_info.anisotropyEnable = VK_FALSE; + sampler_info.maxAnisotropy = 1.0; + sampler_info.compareEnable = VK_FALSE; + sampler_info.compareOp = VK_COMPARE_OP_NEVER; + sampler_info.minLod = 0.0; + sampler_info.maxLod = 1.0; + sampler_info.borderColor = {}; + sampler_info.unnormalizedCoordinates = VK_FALSE; + + VkSampler sampler; + result = init.disp.createSampler(&sampler_info, nullptr, &sampler); + VERIFY_VK_RESULT("Failed to create sampler.", result); + + VkDescriptorSetLayoutBinding bindings[2] = {}; + bindings[0].binding = 0; + bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + bindings[0].descriptorCount = 1; + bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[0].pImmutableSamplers = nullptr; + bindings[1].binding = 1; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[1].pImmutableSamplers = &sampler; + + VkDescriptorSetLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = 2; + layout_info.pBindings = bindings; + result = init.disp.createDescriptorSetLayout(&layout_info, nullptr, &descriptor_layout_); + VERIFY_VK_RESULT("Failed to create descriptor set layout", result); + + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.descriptorPool = descriptor_pool_; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &descriptor_layout_; + + result = init.disp.allocateDescriptorSets(&alloc_info, &descriptor_set_); + VERIFY_VK_RESULT("Failed to allocate descriptor sets", result); +} + +void App::determine_memory_heaps() +{ + VkPhysicalDeviceMemoryProperties props = init.physical_device.memory_properties; + + // Search for largest device-only memory heap + VkDeviceSize largest_seen = 0; + for (int i = 0; i < props.memoryTypeCount; ++i) + { + VkMemoryType type = props.memoryTypes[i]; + bool device_local = type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + VkMemoryHeap heap = props.memoryHeaps[type.heapIndex]; + + if ((type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && + (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + staging_memory_type_ = i; + } + else if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) + { + if (heap.size > largest_seen) + { + device_memory_type_ = i; + largest_seen = heap.size; + } + } + } +} + +void App::create_staging_buffer() +{ + // Create buffer object + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.pNext = nullptr; + buffer_info.flags = 0; + buffer_info.size = STAGING_BUFFER_SIZE; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + buffer_info.queueFamilyIndexCount = 1; + uint32_t idx = init.device.get_queue_index(test::QueueType::graphics).value(); + buffer_info.pQueueFamilyIndices = &idx; + VkResult result = init.disp.createBuffer(&buffer_info, nullptr, &staging_buffer_); + VERIFY_VK_RESULT("failed to create staging buffer", result); + + // Allocate and bind buffer memory + VkMemoryRequirements mem_reqs = {}; + init.disp.getBufferMemoryRequirements(staging_buffer_, &mem_reqs); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = mem_reqs.size; + alloc_info.memoryTypeIndex = staging_memory_type_; + result = init.disp.allocateMemory(&alloc_info, nullptr, &staging_backing_memory_); + VERIFY_VK_RESULT("failed to allocate staging buffer memory", result); + result = init.disp.bindBufferMemory(staging_buffer_, staging_backing_memory_, 0); + VERIFY_VK_RESULT("failed to bind staging buffer memory", result); + + // Map buffer + init.disp.mapMemory(staging_backing_memory_, 0, STAGING_BUFFER_SIZE, 0, (void**)&staging_buffer_ptr_); +} + +void App::create_images() +{ + // Get VkSparseImageFormatProperties for 2D R8G8B8A8_SRGB images + VkPhysicalDevice pd = init.physical_device.physical_device; + uint32_t prop_count = 0; + init.inst_disp.getPhysicalDeviceSparseImageFormatProperties( + pd, + IMAGE_FORMAT, + VK_IMAGE_TYPE_2D, + VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_TILING_OPTIMAL, + &prop_count, + nullptr + ); + std::vector props; + props.resize(prop_count); + init.inst_disp.getPhysicalDeviceSparseImageFormatProperties( + pd, + IMAGE_FORMAT, + VK_IMAGE_TYPE_2D, + VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_TILING_OPTIMAL, + &prop_count, + props.data() + ); + for (VkSparseImageFormatProperties prop : props) { + if (prop.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) { + sparse_block_granularity_ = prop.imageGranularity; + } + } + + // Create image object + VkImageCreateInfo image_info = {}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.pNext = nullptr; + image_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = IMAGE_FORMAT; + image_info.extent.width = image_size_; + image_info.extent.height = image_size_; + image_info.extent.depth = 1; + image_info.mipLevels = mip_levels_; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.queueFamilyIndexCount = 1; + uint32_t idx = init.device.get_queue_index(test::QueueType::graphics).value(); + image_info.pQueueFamilyIndices = &idx; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkResult result = init.disp.createImage(&image_info, nullptr, &image0_); + VERIFY_VK_RESULT("failed to create image", result); + + // Get memory requirements + VkMemoryRequirements image0_reqs = {}; + init.disp.getImageMemoryRequirements(image0_, &image0_reqs); + sparse_binding_granularity_ = image0_reqs.alignment; + + // uint32_t sparse_reqs_count = 0; + // init.disp.getImageSparseMemoryRequirements(image0_, &sparse_reqs_count, nullptr); + // std::vector sparse_reqs; + // sparse_reqs.resize(sparse_reqs_count); + // init.disp.getImageSparseMemoryRequirements(image0_, &sparse_reqs_count, sparse_reqs.data()); + // assert(sparse_reqs.size() == 1); + // image_mem_reqs = sparse_reqs[0]; + + // Allocate image backing memory + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = image0_reqs.size; + alloc_info.memoryTypeIndex = device_memory_type_; + result = init.disp.allocateMemory(&alloc_info, nullptr, &image_backing_memory_); + VERIFY_VK_RESULT("Failed to allocate image memory", result); + + // Create image view object + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.pNext = nullptr; + view_info.flags = 0; + view_info.image = image0_; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = IMAGE_FORMAT; + view_info.components = {}; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.baseMipLevel = 0; + view_info.subresourceRange.levelCount = mip_levels_; + view_info.subresourceRange.baseArrayLayer = 0; + view_info.subresourceRange.layerCount = 1; + result = init.disp.createImageView(&view_info, nullptr, &image0_view_); + VERIFY_VK_RESULT("Failed to create image view", result); + + // Write image data to staging buffer + for (int y = 0; y < image_size_; ++y) { + for (int x = 0; x < image_size_; ++x) { + // Pointer to first byte of current pixel + // This is an R8G8B8A8 format + uint8_t* first_byte = staging_buffer_ptr_ + 4 * (y * image_size_ + x); + bool is_purple = x % 2 + y % 2 == 1; + first_byte[0] = is_purple ? 0xFF : 0x00; + first_byte[2] = is_purple ? 0xFF : 0x00; + } + } + + // Need to start command buffer for image upload + VkCommandBuffer cmd = command_buffers_[0]; + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + result = init.disp.beginCommandBuffer(cmd, &begin_info); + VERIFY_VK_RESULT("failed to begin command buffer", result); + + + // Memory barrier to transition into transfer dst optimal + { + VkImageMemoryBarrier image_barrier = {}; + image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_barrier.image = image0_; + image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_barrier.subresourceRange.layerCount = 1; + image_barrier.subresourceRange.levelCount = 1; + image_barrier.srcAccessMask = VK_ACCESS_NONE; + image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + init.disp.cmdPipelineBarrier(cmd, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &image_barrier); + } + + // Copy image data from staging buffer + VkBufferImageCopy image_copy = {}; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset.x = 0; + image_copy.imageOffset.y = 0; + image_copy.imageOffset.z = 0; + image_copy.imageExtent.width = image_size_; + image_copy.imageExtent.height = image_size_; + image_copy.imageExtent.depth = 1; + + init.disp.cmdCopyBufferToImage(cmd, staging_buffer_, image0_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + // Memory barrier to transition into read-only optimal + { + VkImageMemoryBarrier image_barrier = {}; + image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_barrier.image = image0_; + image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_barrier.subresourceRange.layerCount = 1; + image_barrier.subresourceRange.levelCount = 1; + image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + init.disp.cmdPipelineBarrier(cmd, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &image_barrier); + } + + result = init.disp.endCommandBuffer(cmd); + VERIFY_VK_RESULT("failed to end command buffer", result); + + // Queue submit + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 0; + submit_info.signalSemaphoreCount = 0; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &cmd; + init.disp.queueSubmit(graphics_queue_, 1, &submit_info, immediate_fence_); + + // Wait for submission + result = init.disp.waitForFences(1, &immediate_fence_, VK_TRUE, ~0); + VERIFY_VK_RESULT("failed to wait for upload fence", result); + result = init.disp.resetFences(1, &immediate_fence_); + VERIFY_VK_RESULT("failed to reset upload fence", result); + + // Update descriptor set + VkDescriptorImageInfo desc_image = {}; + desc_image.sampler = 0; + desc_image.imageView = image0_view_; + desc_image.imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet write = {}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.pNext = nullptr; + write.dstSet = descriptor_set_; + write.dstBinding = 0; + write.dstArrayElement = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write.pImageInfo = &desc_image; + write.pBufferInfo = nullptr; + write.pTexelBufferView = nullptr; + init.disp.updateDescriptorSets(1, &write, 0, nullptr); +} + +void App::setup() +{ + auto graphics_queue = init.device.get_queue(gfxrecon::test::QueueType::graphics); + if (!graphics_queue.has_value()) + throw std::runtime_error("could not get graphics queue"); + graphics_queue_ = *graphics_queue; + + auto present_queue = init.device.get_queue(gfxrecon::test::QueueType::present); + if (!present_queue.has_value()) + throw std::runtime_error("could not get present queue"); + present_queue_ = *present_queue; + + // Create semaphore for sparse binding + sparse_binding_semaphores_.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + info.pNext = nullptr; + info.flags = 0; + init.disp.createSemaphore(&info, nullptr, &sparse_binding_semaphores_[i]); + } + + // Create fences + { + VkFenceCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + info.pNext = nullptr; + info.flags = 0; + init.disp.createFence(&info, nullptr, &immediate_fence_); + VkFenceCreateInfo info2 = {}; + info2.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + info2.pNext = nullptr; + info2.flags = 0; + init.disp.createFence(&info2, nullptr, &immediate_fence_); + } + + auto queue_family_index = init.device.get_queue_index(gfxrecon::test::QueueType::graphics); + if (!queue_family_index) + throw std::runtime_error("could not find graphics queue"); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) + { + VkCommandPool& command_pool = command_pools_[i]; + command_pool = gfxrecon::test::create_command_pool(init.disp, *queue_family_index); + + VkCommandBufferAllocateInfo allocate_info = {}; + allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocate_info.commandBufferCount = 1; + allocate_info.commandPool = command_pool; + VkResult result = init.disp.allocateCommandBuffers(&allocate_info, &command_buffers_[i]); + VERIFY_VK_RESULT("failed to allocate command buffer", result); + } + + sync_ = gfxrecon::test::create_sync_objects(init.swapchain, init.disp, MAX_FRAMES_IN_FLIGHT); + + determine_memory_heaps(); + create_descriptor_set(); + create_render_pass(); + create_graphics_pipeline(); + create_framebuffers(); + create_staging_buffer(); + create_images(); +} + +bool App::frame(const int frame_num) +{ + init.disp.waitForFences(1, &sync_.in_flight_fences[current_frame_], VK_TRUE, UINT64_MAX); + + uint32_t image_index = 0; + VkResult result = init.disp.acquireNextImageKHR( + init.swapchain, UINT64_MAX, sync_.available_semaphores[current_frame_], VK_NULL_HANDLE, &image_index); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) + { + TestAppBase::recreate_swapchain(true); + return true; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) + { + throw gfxrecon::test::vulkan_exception("failed to acquire next image", result); + } + + if (sync_.image_in_flight[current_frame_] != VK_NULL_HANDLE) + { + init.disp.waitForFences(1, &sync_.image_in_flight[current_frame_], VK_TRUE, UINT64_MAX); + } + sync_.image_in_flight[current_frame_] = sync_.in_flight_fences[current_frame_]; + + init.disp.resetCommandPool(command_pools_[current_frame_], 0); + VkCommandBuffer command_buffer = command_buffers_[current_frame_]; + + { + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + result = init.disp.beginCommandBuffer(command_buffer, &begin_info); + VERIFY_VK_RESULT("failed to create command buffer", result); + + { + VkImageMemoryBarrier image_barrier = {}; + image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_barrier.image = init.swapchain_images[image_index]; + image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + image_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + image_barrier.srcAccessMask = VK_ACCESS_NONE; + image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + init.disp.cmdPipelineBarrier(command_buffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &image_barrier); + } + + VkRenderPassBeginInfo render_pass_info = {}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_info.renderPass = render_pass_; + render_pass_info.framebuffer = framebuffers_[image_index]; + render_pass_info.renderArea.offset = { 0, 0 }; + render_pass_info.renderArea.extent = init.swapchain.extent; + VkClearValue clearColor{ { { 0.0f, 0.0f, 1.0f, 1.0f } } }; + render_pass_info.clearValueCount = 1; + render_pass_info.pClearValues = &clearColor; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)init.swapchain.extent.width; + viewport.height = (float)init.swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = init.swapchain.extent; + + init.disp.cmdSetViewport(command_buffer, 0, 1, &viewport); + init.disp.cmdSetScissor(command_buffer, 0, 1, &scissor); + init.disp.cmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); + init.disp.cmdBindDescriptorSets( + command_buffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline_layout_, + 0, + 1, + &descriptor_set_, + 0, + nullptr + ); + init.disp.cmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_); + + init.disp.cmdDraw(command_buffer, 3, 1, 0, 0); + + init.disp.cmdEndRenderPass(command_buffer); + + { + VkImageMemoryBarrier image_barrier = {}; + image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_barrier.image = init.swapchain_images[image_index]; + image_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + image_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + image_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + image_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + image_barrier.dstAccessMask = VK_ACCESS_NONE; + init.disp.cmdPipelineBarrier(command_buffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &image_barrier); + } + + result = init.disp.endCommandBuffer(command_buffer); + VERIFY_VK_RESULT("failed to end command buffer", result); + } + + // Bind sparse memory + static bool first_frame = true; + if (first_frame) { + // VkSparseImageMemoryBind bind = {}; + // bind.subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + // bind.subresource.mipLevel = 0; + // bind.subresource.arrayLayer = 0; + // bind.offset.x = 0; + // bind.offset.y = 0; + // bind.offset.z = 0; + // bind.extent.width = image_size_; + // bind.extent.height = image_size_; + // bind.extent.depth = 1; + // bind.memory = image_backing_memory_; + // bind.memoryOffset = 0; + // bind.flags = 0; + + VkSparseMemoryBind bind = {}; + bind.resourceOffset = 0; + //bind.size = 4 * image_size_ * image_size_; + bind.size = sparse_binding_granularity_; + bind.memory = image_backing_memory_; + bind.memoryOffset = 0; + bind.flags = 0; + //bind.flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT; + + VkSparseImageOpaqueMemoryBindInfo im_bind_info = {}; + im_bind_info.image = image0_; + im_bind_info.bindCount = 1; + im_bind_info.pBinds = &bind; + + VkBindSparseInfo sparse_info = {}; + sparse_info.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO; + sparse_info.pNext = nullptr; + + sparse_info.bufferBindCount = 0; + sparse_info.pBufferBinds = nullptr; + sparse_info.imageOpaqueBindCount = 1; + sparse_info.pImageOpaqueBinds = &im_bind_info; + sparse_info.imageBindCount = 0; + sparse_info.pImageBinds = nullptr; + result = init.disp.queueBindSparse(graphics_queue_, 1, &sparse_info, immediate_fence_); + VERIFY_VK_RESULT("Failed to bind sparse memory", result); + + result = init.disp.waitForFences(1, &immediate_fence_, VK_TRUE, ~0); + VERIFY_VK_RESULT("Failed to wait for sparse binding fence.", result); + result = init.disp.resetFences(1, &immediate_fence_); + VERIFY_VK_RESULT("failed to reset sparse binding fence", result); + } + first_frame = false; + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore wait_semaphores[] = { + sync_.available_semaphores[current_frame_] + }; + VkPipelineStageFlags wait_stages[] = { + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + }; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = wait_semaphores; + submitInfo.pWaitDstStageMask = wait_stages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &command_buffer; + + + VkSemaphore signal_semaphores[] = { sync_.finished_semaphore[current_frame_] }; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signal_semaphores; + + init.disp.resetFences(1, &sync_.in_flight_fences[current_frame_]); + + result = init.disp.queueSubmit(graphics_queue_, 1, &submitInfo, sync_.in_flight_fences[current_frame_]); + VERIFY_VK_RESULT("failed to submit queue", result); + + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = signal_semaphores; + + VkSwapchainKHR swapChains[] = { init.swapchain }; + present_info.swapchainCount = 1; + present_info.pSwapchains = swapChains; + + present_info.pImageIndices = &image_index; + + result = init.disp.queuePresentKHR(present_queue_, &present_info); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) + { + TestAppBase::recreate_swapchain(true); + return true; + } + VERIFY_VK_RESULT("failed to present queue", result); + + last_frame_ = current_frame_; + current_frame_ = (current_frame_ + 1) % MAX_FRAMES_IN_FLIGHT; + + // return IS_RUNNING(frame_num); + return true; +} + +GFXRECON_END_NAMESPACE(sparse_resources) +GFXRECON_END_NAMESPACE(test_app) +GFXRECON_END_NAMESPACE(gfxrecon) + +int main(int argc, char* argv[]) +{ + try + { + gfxrecon::test_app::sparse_resources::App app{}; + app.run("sparse_resources"); + return 0; + } + catch (std::exception e) + { + std::cout << e.what() << std::endl; + return -1; + } +} diff --git a/test/test_apps/sparse-resources/shaders/tri.frag.spv b/test/test_apps/sparse-resources/shaders/tri.frag.spv new file mode 100644 index 0000000000..24ecacd4f5 Binary files /dev/null and b/test/test_apps/sparse-resources/shaders/tri.frag.spv differ diff --git a/test/test_apps/sparse-resources/shaders/tri.vert.spv b/test/test_apps/sparse-resources/shaders/tri.vert.spv new file mode 100644 index 0000000000..6d4ac23ce8 Binary files /dev/null and b/test/test_apps/sparse-resources/shaders/tri.vert.spv differ