diff --git a/icd/CMakeLists.txt b/icd/CMakeLists.txt index fe4d51c..9522eca 100644 --- a/icd/CMakeLists.txt +++ b/icd/CMakeLists.txt @@ -1,6 +1,6 @@ # ~~~ -# Copyright (c) 2024-2025 The Khronos Group Inc. -# Copyright (c) 2024-2025 RasterGrid Kft. +# Copyright (c) 2024-2026 The Khronos Group Inc. +# Copyright (c) 2024-2026 RasterGrid Kft. # # SPDX-License-Identifier: Apache-2.0 # ~~~ @@ -54,16 +54,23 @@ target_sources(icd PRIVATE vksc_command_buffer.cpp vksc_command_pool.h vksc_command_pool.cpp + vksc_descriptor_pool.h + vksc_descriptor_pool.cpp + vksc_descriptor_set_layout.h vksc_device.h vksc_device.cpp vksc_display_emulation.h vksc_display_emulation.cpp vksc_global.h vksc_global.cpp + vksc_image.h + vksc_image_view.h vksc_instance.h vksc_instance.cpp vksc_physical_device.h vksc_physical_device.cpp + vksc_pipeline.h + vksc_render_pass.h vksc_queue.h vksc_queue.cpp ) diff --git a/icd/icd_env_helper.h b/icd/icd_env_helper.h index bf8ef59..efed2af 100644 --- a/icd/icd_env_helper.h +++ b/icd/icd_env_helper.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,7 +53,7 @@ class EnvironmentOverride { ~EnvironmentOverride(); private: - std::lock_guard lock_; + std::unique_lock lock_; const EnvironmentHelper& env_; }; diff --git a/icd/icd_fault_handler.cpp b/icd/icd_fault_handler.cpp index 8c9c353..7c03351 100644 --- a/icd/icd_fault_handler.cpp +++ b/icd/icd_fault_handler.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,31 +18,32 @@ FaultHandler::FaultHandler(uint32_t max_fault_count, const VkFaultCallbackInfo* } void FaultHandler::ReportFault(VkFaultLevel level, VkFaultType type) { + std::unique_lock lock(faults_mutex_); + VkFaultData fault_data = {VK_STRUCTURE_TYPE_FAULT_DATA, nullptr, level, type}; if (fault_callback_) { - fault_callback_.value().pfnFaultCallback(unrecorded_faults_.load(), 1, &fault_data); + fault_callback_.value().pfnFaultCallback(unrecorded_faults_, 1, &fault_data); } if (max_fault_count_ != 0) { - std::lock_guard lock{faults_mutex_}; - if (faults_.size() < max_fault_count_) { faults_.push_back(fault_data); } else { - unrecorded_faults_.store(true); + unrecorded_faults_ = true; } } } VkResult FaultHandler::GetFaultData(VkFaultQueryBehavior faultQueryBehavior, VkBool32* pUnrecordedFaults, uint32_t* pFaultCount, VkFaultData* pFaults) { - std::lock_guard lock{faults_mutex_}; + std::unique_lock lock(faults_mutex_); switch (faultQueryBehavior) { case VkFaultQueryBehavior::VK_FAULT_QUERY_BEHAVIOR_GET_AND_CLEAR_ALL_FAULTS: if (pUnrecordedFaults != nullptr) { - *pUnrecordedFaults = unrecorded_faults_.exchange(false); + *pUnrecordedFaults = unrecorded_faults_; + unrecorded_faults_ = false; } if (pFaults == nullptr) { diff --git a/icd/icd_fault_handler.h b/icd/icd_fault_handler.h index 00d36d3..37004d0 100644 --- a/icd/icd_fault_handler.h +++ b/icd/icd_fault_handler.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,6 @@ #include #include #include -#include namespace icd { @@ -42,7 +41,7 @@ class FaultHandler { const std::optional fault_callback_{std::nullopt}; std::mutex faults_mutex_{}; std::vector faults_{}; - std::atomic_bool unrecorded_faults_{false}; + bool unrecorded_faults_{false}; }; } // namespace icd diff --git a/icd/icd_log.h b/icd/icd_log.h index 77dd7a7..aa9dd57 100644 --- a/icd/icd_log.h +++ b/icd/icd_log.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -78,37 +78,37 @@ class Logger { } template - void Fatal(const char* msg_id, const char* format, ARGS... args) const { + void Fatal(const char* msg_id, const char* format, ARGS&&... args) const { Log(true, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, msg_id, format, std::forward(args)...); } template - void Error(const char* msg_id, const char* format, ARGS... args) const { + void Error(const char* msg_id, const char* format, ARGS&&... args) const { Log(false, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, msg_id, format, std::forward(args)...); } template - void Warning(const char* msg_id, const char* format, ARGS... args) const { + void Warning(const char* msg_id, const char* format, ARGS&&... args) const { Log(false, VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, msg_id, format, std::forward(args)...); } template - void Info(const char* msg_id, const char* format, ARGS... args) const { + void Info(const char* msg_id, const char* format, ARGS&&... args) const { Log(false, VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, msg_id, format, std::forward(args)...); } template - void Debug(const char* msg_id, const char* format, ARGS... args) const { + void Debug(const char* msg_id, const char* format, ARGS&&... args) const { Log(false, VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, msg_id, format, std::forward(args)...); } template - void ValidationError(const char* msg_id, const char* format, ARGS... args) const { + void ValidationError(const char* msg_id, const char* format, ARGS&&... args) const { Log(false, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, msg_id, format, std::forward(args)...); } diff --git a/icd/icd_object_tracker.h b/icd/icd_object_tracker.h index 785412c..8de8921 100644 --- a/icd/icd_object_tracker.h +++ b/icd/icd_object_tracker.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,25 +8,69 @@ #pragma once #include "vk_device.h" +#include "icd_log.h" +#include "icd_fault_handler.h" #include #include +#include #include #include +#include +#include namespace icd { +// Utility template to track state related to a particular object type +template +class ObjectStateTracker { + public: + template + void Add(HandleType handle, ARGS&&... args) { + std::unique_lock lock(mutex_); + object_map_[handle] = std::make_unique(handle, std::forward(args)...); + } + + T* Get(HandleType handle) { + std::unique_lock lock(mutex_); + auto it = object_map_.find(handle); + if (it != object_map_.end()) { + return it->second.get(); + } else { + return nullptr; + } + } + + void Remove(HandleType handle) { + std::unique_lock lock(mutex_); + object_map_.erase(handle); + } + + private: + std::mutex mutex_{}; + std::unordered_map> object_map_{}; +}; + +// Placeholder handle type for unused object handles +VK_DEFINE_NON_DISPATCHABLE_HANDLE(UnusedHandleType); + // RAII object reservation template template class ObjectReservation { public: - ObjectReservation(T& tracker, uint32_t count) : tracker_(tracker), count_(tracker.ReserveInternal(count)) {} + ObjectReservation(T& tracker, uint32_t count) : tracker_(tracker), count_(count > 0 ? tracker.ReserveInternal(count) : count) {} ~ObjectReservation() { - if (count_ > 0) tracker_.CancelInternal(); + if (count_ > 0) tracker_.CancelInternal(count_); } + operator bool() const { return count_ > 0; } + + uint32_t Count() const { return count_; } + void Commit(HandleType* handles) { - tracker_.CommitInternal(handles, count_); + if (count_ > 0) { + tracker_.CommitInternal(handles, count_); + } count_ = 0; } @@ -35,6 +79,96 @@ class ObjectReservation { uint32_t count_; }; +// General object limit template +template +class GeneralObjectLimitTracker { + public: + GeneralObjectLimitTracker(vk::Device& device, const VkDeviceCreateInfo& create_info) : reserved_limit_(0) { + const auto* object_reservation_info = vku::FindStructInPNextChain(create_info.pNext); + while (object_reservation_info != nullptr) { + reserved_limit_ = + std::max(reserved_limit_, *reinterpret_cast( + reinterpret_cast(object_reservation_info) + RESERVATION_INFO_OFFSET)); + object_reservation_info = vku::FindStructInPNextChain(object_reservation_info->pNext); + } + } + + RESERVATION_INFO_TYPE Limit() const { return reserved_limit_; } + + private: + RESERVATION_INFO_TYPE reserved_limit_; +}; + +// General object count tracker template +template +class GeneralObjectCountTracker { + public: + using HandleType = HANDLE; + + GeneralObjectCountTracker(vk::Device& device, const VkDeviceCreateInfo& create_info) : reserved_count_(0), allocated_count_(0) { + const auto* object_reservation_info = vku::FindStructInPNextChain(create_info.pNext); + while (object_reservation_info != nullptr) { + reserved_count_ += *reinterpret_cast( + reinterpret_cast(object_reservation_info) + RESERVATION_INFO_OFFSET); + object_reservation_info = vku::FindStructInPNextChain(object_reservation_info->pNext); + } + } + + bool Free(const HandleType* handles, uint32_t count) { + uint32_t free_count = 0; + if (handles == nullptr) { + // No actual handles are used so use the input + free_count = count; + } else { + for (uint32_t i = 0; i < count; ++i) { + if (handles[i] != VK_NULL_HANDLE) { + ++free_count; + } + } + } + return allocated_count_.fetch_sub(free_count) >= count; + } + + private: + uint32_t ReserveInternal(uint32_t count) { + if (allocated_count_.fetch_add(count) + count <= reserved_count_) { + return count; + } else { + // We ran out of the requested number of objects + allocated_count_.fetch_sub(count); + return 0; + } + } + + void CancelInternal(uint32_t count) { + // Rservation was cancelled implicitly + allocated_count_.fetch_sub(count); + } + + void CommitInternal(HandleType* handles, uint32_t count) { + // Nothing to do if this is a case where handles are not actually used + if (handles == nullptr) { + return; + } + + // Free up capacity where handles became VK_NULL_HANDLE + uint32_t null_handle_count = 0; + for (uint32_t i = 0; i < count; ++i) { + if (handles[i] == VK_NULL_HANDLE) { + null_handle_count++; + } + } + allocated_count_.fetch_sub(null_handle_count); + } + + template + friend class ObjectReservation; + + RESERVATION_INFO_TYPE reserved_count_; + std::atomic allocated_count_; +}; + // Implicitly destroyed object tracker template template @@ -76,7 +210,7 @@ class ImplicitlyDestroyedDeviceObjectTracker { } } - void CancelInternal() { + void CancelInternal(uint32_t count) { // Reservation was cancelled implicitly objects_mutex_.unlock(); } @@ -85,7 +219,9 @@ class ImplicitlyDestroyedDeviceObjectTracker { // At this point we should still have enough storage because we locked the container if (objects_.size() + count <= objects_.capacity()) { for (uint32_t i = 0; i < count; ++i) { - objects_.push_back(handles[i]); + if (handles[i] != VK_NULL_HANDLE) { + objects_.push_back(handles[i]); + } } objects_mutex_.unlock(); } @@ -102,43 +238,159 @@ class ImplicitlyDestroyedDeviceObjectTracker { // Main device object tracker class aggregating all object tracking class DeviceObjectTracker { public: - DeviceObjectTracker(vk::Device& device, const VkDeviceCreateInfo& create_info) - : DeviceMemory_tracker_(device, create_info), - CommandPool_tracker_(device, create_info), - DescriptorPool_tracker_(device, create_info), + DeviceObjectTracker(vk::Device& device, icd::Logger& logger, icd::FaultHandler& fault_handler, + const VkDeviceCreateInfo& create_info) + : logger_(logger), + fault_handler_(fault_handler), + Semaphore_tracker_(device, create_info), + CommandBuffer_tracker_(device, create_info), + Fence_tracker_(device, create_info), + DeviceMemory_tracker_(device, create_info), + Buffer_tracker_(device, create_info), + Image_tracker_(device, create_info), + Event_tracker_(device, create_info), QueryPool_tracker_(device, create_info), - SwapchainKHR_tracker_(device, create_info) {} + BufferView_tracker_(device, create_info), + ImageView_tracker_(device, create_info), + LayeredImageView_tracker_(device, create_info), + PipelineCache_tracker_(device, create_info), + PipelineLayout_tracker_(device, create_info), + RenderPass_tracker_(device, create_info), + GraphicsPipeline_tracker_(device, create_info), + ComputePipeline_tracker_(device, create_info), + DescriptorSetLayout_tracker_(device, create_info), + Sampler_tracker_(device, create_info), + DescriptorPool_tracker_(device, create_info), + DescriptorSet_tracker_(device, create_info), + Framebuffer_tracker_(device, create_info), + CommandPool_tracker_(device, create_info), + SamplerYcbcrConversion_tracker_(device, create_info), + SwapchainKHR_tracker_(device, create_info), + SubpassDescription_tracker_(device, create_info), + AttachmentDescription_tracker_(device, create_info), + DescriptorSetLayoutBinding_tracker_(device, create_info), + DescriptorSetLayoutBindingLimit_tracker_(device, create_info), + MaxImageViewMipLevels_tracker_(device, create_info), + MaxImageViewArrayLayers_tracker_(device, create_info), + MaxLayeredImageViewMipLevels_tracker_(device, create_info), + MaxOcclusionQueriesPerPool_tracker_(device, create_info), + MaxPipelineStatisticsQueriesPerPool_tracker_(device, create_info), + MaxTimestampQueriesPerPool_tracker_(device, create_info), + MaxImmutableSamplersPerDescriptorSetLayout_tracker_(device, create_info), + MaxPerformanceQueriesPerPool_tracker_(device, create_info) {} + + private: + icd::Logger& logger_; + icd::FaultHandler& fault_handler_; + +#define ICD_GEN_LIMIT(limit_type, reservation_struct, reservation_member) \ + private: \ + class limit_type##Tracker : public GeneralObjectLimitTracker { \ + public: \ + limit_type##Tracker(vk::Device& device, const VkDeviceCreateInfo& create_info) \ + : GeneralObjectLimitTracker(device, create_info) {} \ + } limit_type##_tracker_; \ + \ + public: \ + auto limit_type() const { return limit_type##_tracker_.Limit(); } + +#define ICD_GEN_COUNT_EX(object_type, handle_type, reservation_struct, reservation_member) \ + private: \ + class object_type##Tracker : public GeneralObjectCountTracker { \ + public: \ + object_type##Tracker(vk::Device& device, const VkDeviceCreateInfo& create_info) \ + : GeneralObjectCountTracker(device, create_info) {} \ + } object_type##_tracker_; \ + \ + public: \ + ObjectReservation Reserve##object_type(uint32_t count = 1) { \ + return ObjectReservation(object_type##_tracker_, count); \ + } \ + void Free##object_type(uint32_t count, const handle_type* handles) { \ + if (!object_type##_tracker_.Free(handles, count)) { \ + fault_handler_.ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); \ + logger_.Fatal("VKSC-EMU-ObjectTracker-Invalid" #object_type "Count", \ + "Object tracker encountered an attempt to free more " #object_type " objects than remaining"); \ + } \ + } + +#define ICD_GEN_COUNT(object_type, reservation_struct, reservation_member) \ + ICD_GEN_COUNT_EX(object_type, UnusedHandleType, reservation_struct, reservation_member) + +#define ICD_GEN_OBJ(object_type, handle_type, reservation_struct, reservation_member) \ + ICD_GEN_COUNT_EX(object_type, handle_type, reservation_struct, reservation_member) -#define ICD_IMPL_DESTR(object_type, reservation_struct, reservation_member, destructor) \ +#define ICD_IMPL_DESTR(object_type, handle_type, reservation_struct, reservation_member, destructor) \ private: \ class object_type##Tracker \ - : public ImplicitlyDestroyedDeviceObjectTracker { \ public: \ object_type##Tracker(vk::Device& device, const VkDeviceCreateInfo& create_info) \ - : ImplicitlyDestroyedDeviceObjectTracker(device, create_info) {} \ - void DestroyObject(Vk##object_type handle) { ParentDevice().destructor(handle, nullptr); } \ + void DestroyObject(handle_type handle) { ParentDevice().destructor(handle, nullptr); } \ } object_type##_tracker_; \ \ public: \ - ObjectReservation Reserve##object_type(uint32_t count = 1) { \ - return ObjectReservation(object_type##_tracker_, count); \ + ObjectReservation Reserve##object_type(uint32_t count = 1) { \ + return ObjectReservation(object_type##_tracker_, count); \ } // clang-format off - // Implicitly destroyed handles that do not have destructors in Vulkan SC - // handle type reservation structure reservation member destructor - ICD_IMPL_DESTR( DeviceMemory, VkDeviceObjectReservationCreateInfo, deviceMemoryRequestCount, FreeMemory); - ICD_IMPL_DESTR( CommandPool, VkDeviceObjectReservationCreateInfo, commandPoolRequestCount, DestroyCommandPool); - ICD_IMPL_DESTR( DescriptorPool, VkDeviceObjectReservationCreateInfo, descriptorPoolRequestCount, DestroyDescriptorPool); - ICD_IMPL_DESTR( QueryPool, VkDeviceObjectReservationCreateInfo, queryPoolRequestCount, DestroyQueryPool); - ICD_IMPL_DESTR( SwapchainKHR, VkDeviceObjectReservationCreateInfo, swapchainRequestCount, DestroySwapchainKHR); + // object type handle type reservation structure reservation member destructor + ICD_GEN_OBJ( Semaphore, VkSemaphore, VkDeviceObjectReservationCreateInfo, semaphoreRequestCount); + ICD_GEN_OBJ( CommandBuffer, VkCommandBuffer, VkDeviceObjectReservationCreateInfo, commandBufferRequestCount); + ICD_GEN_OBJ( Fence, VkFence, VkDeviceObjectReservationCreateInfo, fenceRequestCount); + ICD_IMPL_DESTR( DeviceMemory, VkDeviceMemory, VkDeviceObjectReservationCreateInfo, deviceMemoryRequestCount, FreeMemory); + ICD_GEN_OBJ( Buffer, VkBuffer, VkDeviceObjectReservationCreateInfo, bufferRequestCount); + ICD_GEN_OBJ( Image, VkImage, VkDeviceObjectReservationCreateInfo, imageRequestCount); + ICD_GEN_OBJ( Event, VkEvent, VkDeviceObjectReservationCreateInfo, eventRequestCount); + ICD_IMPL_DESTR( QueryPool, VkQueryPool, VkDeviceObjectReservationCreateInfo, queryPoolRequestCount, DestroyQueryPool); + ICD_GEN_OBJ( BufferView, VkBufferView, VkDeviceObjectReservationCreateInfo, bufferViewRequestCount); + ICD_GEN_OBJ( ImageView, VkImageView, VkDeviceObjectReservationCreateInfo, imageViewRequestCount); + ICD_GEN_OBJ( LayeredImageView, VkImageView, VkDeviceObjectReservationCreateInfo, layeredImageViewRequestCount); + ICD_GEN_OBJ( PipelineCache, VkPipelineCache, VkDeviceObjectReservationCreateInfo, pipelineCacheRequestCount); + ICD_GEN_OBJ( PipelineLayout, VkPipelineLayout, VkDeviceObjectReservationCreateInfo, pipelineLayoutRequestCount); + ICD_GEN_OBJ( RenderPass, VkRenderPass, VkDeviceObjectReservationCreateInfo, renderPassRequestCount); + ICD_GEN_OBJ( GraphicsPipeline, VkPipeline, VkDeviceObjectReservationCreateInfo, graphicsPipelineRequestCount); + ICD_GEN_OBJ( ComputePipeline, VkPipeline, VkDeviceObjectReservationCreateInfo, computePipelineRequestCount); + ICD_GEN_OBJ( DescriptorSetLayout, VkDescriptorSetLayout, VkDeviceObjectReservationCreateInfo, descriptorSetLayoutRequestCount); + ICD_GEN_OBJ( Sampler, VkSampler, VkDeviceObjectReservationCreateInfo, samplerRequestCount); + ICD_IMPL_DESTR( DescriptorPool, VkDescriptorPool, VkDeviceObjectReservationCreateInfo, descriptorPoolRequestCount, DestroyDescriptorPool); + ICD_GEN_OBJ( DescriptorSet, VkDescriptorSet, VkDeviceObjectReservationCreateInfo, descriptorSetRequestCount); + ICD_GEN_OBJ( Framebuffer, VkFramebuffer, VkDeviceObjectReservationCreateInfo, framebufferRequestCount); + ICD_IMPL_DESTR( CommandPool, VkCommandPool, VkDeviceObjectReservationCreateInfo, commandPoolRequestCount, DestroyCommandPool); + ICD_GEN_OBJ( SamplerYcbcrConversion, VkSamplerYcbcrConversion, VkDeviceObjectReservationCreateInfo, samplerYcbcrConversionRequestCount); + ICD_IMPL_DESTR( SwapchainKHR, VkSwapchainKHR, VkDeviceObjectReservationCreateInfo, swapchainRequestCount, DestroySwapchainKHR); + ICD_GEN_COUNT( SubpassDescription, VkDeviceObjectReservationCreateInfo, subpassDescriptionRequestCount); + ICD_GEN_COUNT( AttachmentDescription, VkDeviceObjectReservationCreateInfo, attachmentDescriptionRequestCount); + ICD_GEN_COUNT( DescriptorSetLayoutBinding, VkDeviceObjectReservationCreateInfo, descriptorSetLayoutBindingRequestCount); + ICD_GEN_LIMIT( DescriptorSetLayoutBindingLimit, VkDeviceObjectReservationCreateInfo, descriptorSetLayoutBindingLimit); + ICD_GEN_LIMIT( MaxImageViewMipLevels, VkDeviceObjectReservationCreateInfo, maxImageViewMipLevels); + ICD_GEN_LIMIT( MaxImageViewArrayLayers, VkDeviceObjectReservationCreateInfo, maxImageViewArrayLayers); + ICD_GEN_LIMIT( MaxLayeredImageViewMipLevels, VkDeviceObjectReservationCreateInfo, maxLayeredImageViewMipLevels); + ICD_GEN_LIMIT( MaxOcclusionQueriesPerPool, VkDeviceObjectReservationCreateInfo, maxOcclusionQueriesPerPool); + ICD_GEN_LIMIT( MaxPipelineStatisticsQueriesPerPool, VkDeviceObjectReservationCreateInfo, maxPipelineStatisticsQueriesPerPool); + ICD_GEN_LIMIT( MaxTimestampQueriesPerPool, VkDeviceObjectReservationCreateInfo, maxTimestampQueriesPerPool); + ICD_GEN_LIMIT( MaxImmutableSamplersPerDescriptorSetLayout, VkDeviceObjectReservationCreateInfo, maxImmutableSamplersPerDescriptorSetLayout); + ICD_GEN_LIMIT( MaxPerformanceQueriesPerPool, VkPerformanceQueryReservationInfoKHR, maxPerformanceQueriesPerPool); // clang-format on #undef ICD_IMPL_DESTR +#undef ICD_GEN_OBJ +#undef ICD_GEN_COUNT +#undef ICD_GEN_COUNT_EX +#undef ICD_GEN_LIMIT }; } // namespace icd diff --git a/icd/vksc_command_pool.cpp b/icd/vksc_command_pool.cpp index 7a0ebf6..c21ff48 100644 --- a/icd/vksc_command_pool.cpp +++ b/icd/vksc_command_pool.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -28,28 +28,15 @@ CommandPool::~CommandPool() { bool CommandPool::operator==(const VkCommandPool& rhs) const { return this->handle_ == rhs; } -VkDeviceSize CommandPool::GetReservedMemorySize() const { - const std::lock_guard lock{mutex_}; - return reserved_memory_size_; -} - -VkDeviceSize CommandPool::GetAllocatedMemorySize() const { - const std::lock_guard lock{mutex_}; - return allocated_memory_size_; -} - -uint32_t CommandPool::GetReservedCount() const { - const std::lock_guard lock{mutex_}; - return max_command_buffer_count_; -} - -uint32_t CommandPool::GetAllocatedCount() const { - const std::lock_guard lock{mutex_}; - return static_cast(command_buffers_.size()); +void CommandPool::GetMemoryConsumption(VkCommandPoolMemoryConsumption* pConsumption) const { + std::unique_lock lock(mutex_); + pConsumption->commandPoolReservedSize = reserved_memory_size_; + pConsumption->commandPoolAllocated = allocated_memory_size_; } VkResult CommandPool::AllocateMemory(VkDeviceSize size) { - if (const std::lock_guard lock{mutex_}; allocated_memory_size_ + size <= reserved_memory_size_) { + std::unique_lock lock(mutex_); + if (allocated_memory_size_ + size <= reserved_memory_size_) { allocated_memory_size_ += size; return VK_SUCCESS; } else { @@ -64,7 +51,8 @@ icd::ObjectReservation CommandPool::ReserveCommand } VkResult CommandPool::FreeMemory(VkDeviceSize size) { - if (const std::lock_guard lock{mutex_}; size <= allocated_memory_size_) { + std::unique_lock lock(mutex_); + if (size <= allocated_memory_size_) { allocated_memory_size_ -= size; return VK_SUCCESS; } else { @@ -76,7 +64,7 @@ VkResult CommandPool::FreeMemory(VkDeviceSize size) { } VkResult CommandPool::FreeCommandBuffers(uint32_t count, const VkCommandBuffer* buffers) { - const std::lock_guard lock{mutex_}; + std::unique_lock lock(mutex_); for (uint32_t i = 0; i < count; ++i) { if (buffers[i] == VK_NULL_HANDLE) { @@ -92,11 +80,13 @@ VkResult CommandPool::FreeCommandBuffers(uint32_t count, const VkCommandBuffer* } command_buffers_.erase(it); } + return VK_SUCCESS; } VkResult CommandPool::ResetCommandPool(VkCommandPoolResetFlags) { - const std::lock_guard lock{mutex_}; + std::unique_lock lock(mutex_); + VkResult result = VK_SUCCESS; for (auto& command_buffer : command_buffers_) { @@ -119,7 +109,7 @@ uint32_t CommandPool::ReserveInternal(uint32_t count) { } } -void CommandPool::CancelInternal() { mutex_.unlock(); } +void CommandPool::CancelInternal(uint32_t count) { mutex_.unlock(); } void CommandPool::CommitInternal(VkCommandBuffer* handles, uint32_t count) { for (uint32_t i = 0; i < count; ++i) { diff --git a/icd/vksc_command_pool.h b/icd/vksc_command_pool.h index ea7848f..c3d5fa6 100644 --- a/icd/vksc_command_pool.h +++ b/icd/vksc_command_pool.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,10 +29,7 @@ class CommandPool { Device& GetDevice() const { return device_; } - VkDeviceSize GetReservedMemorySize() const; - VkDeviceSize GetAllocatedMemorySize() const; - uint32_t GetReservedCount() const; - uint32_t GetAllocatedCount() const; + void GetMemoryConsumption(VkCommandPoolMemoryConsumption* pConsumption) const; VkResult AllocateMemory(VkDeviceSize size); icd::ObjectReservation ReserveCommandBuffers(uint32_t count, const VkCommandBuffer* buffers); @@ -47,7 +44,7 @@ class CommandPool { friend class icd::ObjectReservation; uint32_t ReserveInternal(uint32_t count); - void CancelInternal(); + void CancelInternal(uint32_t count); void CommitInternal(VkCommandBuffer* handles, uint32_t count); VkCommandPool handle_; diff --git a/icd/vksc_descriptor_pool.cpp b/icd/vksc_descriptor_pool.cpp new file mode 100644 index 0000000..ff2dc0d --- /dev/null +++ b/icd/vksc_descriptor_pool.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "vksc_descriptor_pool.h" +#include "vksc_device.h" + +namespace vksc { + +uint32_t DescriptorPool::GetAllocatedSetsAndReset() { return allocated_descriptor_sets_.exchange(0); } + +void DescriptorPool::AllocateDescriptorSets(uint32_t count) { + if (allocated_descriptor_sets_.fetch_add(count) + count > max_descriptor_sets_) { + device_.ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + device_.Log().Error("VKSC-EMU-AllocateDescriptorSets-UnexpectedOverflow", + "Unexpectedly exceeded number of descriptor sets in descriptor pool"); + } +} + +void DescriptorPool::FreeDescriptorSets(uint32_t count) { + if (allocated_descriptor_sets_.fetch_sub(count) < count) { + allocated_descriptor_sets_.store(0); + device_.ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + device_.Log().Error("VKSC-EMU-FreeDescriptorSets-UnexpectedUnderflow", + "Unexpectedly attempted to free more descriptor sets than allocated in descriptor pool"); + } +} + +} // namespace vksc diff --git a/icd/vksc_descriptor_pool.h b/icd/vksc_descriptor_pool.h new file mode 100644 index 0000000..ec60b57 --- /dev/null +++ b/icd/vksc_descriptor_pool.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +#include + +namespace vksc { + +class Device; + +class DescriptorPool { + public: + DescriptorPool(VkDescriptorPool handle, Device& device, uint32_t max_descriptor_sets) + : device_(device), max_descriptor_sets_(max_descriptor_sets), allocated_descriptor_sets_(0) {} + + uint32_t GetAllocatedSetsAndReset(); + void AllocateDescriptorSets(uint32_t count); + void FreeDescriptorSets(uint32_t count); + + private: + Device& device_; + const uint32_t max_descriptor_sets_; + std::atomic_uint32_t allocated_descriptor_sets_; +}; + +} // namespace vksc diff --git a/icd/vksc_descriptor_set_layout.h b/icd/vksc_descriptor_set_layout.h new file mode 100644 index 0000000..4514266 --- /dev/null +++ b/icd/vksc_descriptor_set_layout.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +namespace vksc { + +class Device; + +class DescriptorSetLayout { + public: + DescriptorSetLayout(VkDescriptorSetLayout handle, uint32_t binding_count) : binding_count_(binding_count) {} + + uint32_t GetBindingCount() const { return binding_count_; } + + private: + const uint32_t binding_count_; +}; + +} // namespace vksc diff --git a/icd/vksc_device.cpp b/icd/vksc_device.cpp index f6bb3a1..4bb9a4b 100644 --- a/icd/vksc_device.cpp +++ b/icd/vksc_device.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -31,7 +31,7 @@ Device::Device(VkDevice device, PhysicalDevice& physical_device, const VkDeviceC logger_(physical_device.Log(), VK_OBJECT_TYPE_DEVICE, device), fault_handler_(physical_device.GetMaxQueryFaultCount(), vku::FindStructInPNextChain(create_info.pNext)), device_queues_(), - object_tracker_(*this, create_info) { + object_tracker_(*this, logger_, fault_handler_, create_info) { status_ = SetupDevice(create_info); } @@ -75,8 +75,6 @@ VkResult Device::SetupDevice(const VkDeviceCreateInfo& create_info) { reserved_pipeline_pool_entries_map_[pool_size.poolEntrySize] += pool_size.poolEntryCount; } - command_pools_.reserve(object_reservation_info->commandPoolRequestCount); - object_reservation_info = vku::FindStructInPNextChain(object_reservation_info->pNext); } @@ -122,23 +120,20 @@ void Device::GetDeviceQueue2(const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQue } VkResult Device::AllocateCommandBuffers(const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers) { - const std::lock_guard lock{command_pool_mutex_}; - - auto command_pool = command_pools_.find(pAllocateInfo->commandPool); - if (command_pool == command_pools_.end()) { + auto command_pool_state = command_pools_.Get(pAllocateInfo->commandPool); + if (!command_pool_state) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-AllocateCommandBuffers-UnknownCommandPool", - "vkAllocateCommandBuffer called with a VkCommandBufferAllocateInfo holding an unknown commandPool pointer (%p)", - pAllocateInfo->commandPool); - return VK_ERROR_UNKNOWN; + Log().Error("VKSC-EMU-AllocateCommandBuffers-InvalidCommandPool", + "vkAllocateCommandBuffer called with an invalid command pool handle (%p)", pAllocateInfo->commandPool); + return VK_ERROR_VALIDATION_FAILED; } - auto reservation = command_pool->second->ReserveCommandBuffers(pAllocateInfo->commandBufferCount, pCommandBuffers); + auto reservation = command_pool_state->ReserveCommandBuffers(pAllocateInfo->commandBufferCount, pCommandBuffers); if (!reservation) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-AllocateCommandBuffers-OutOfCommandBuffers", "Ran out of command buffers reserved for the command pool (%p)", VkHandle()); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; } VkResult result = NEXT::AllocateCommandBuffers(pAllocateInfo, pCommandBuffers); @@ -147,7 +142,7 @@ VkResult Device::AllocateCommandBuffers(const VkCommandBufferAllocateInfo* pAllo } for (uint32_t i = 0; i < pAllocateInfo->commandBufferCount; ++i) { - auto command_buffer = CommandBuffer::Create(pCommandBuffers[i], *command_pool->second.get()); + auto command_buffer = CommandBuffer::Create(pCommandBuffers[i], *command_pool_state); pCommandBuffers[i] = command_buffer; } reservation.Commit(pCommandBuffers); @@ -156,17 +151,15 @@ VkResult Device::AllocateCommandBuffers(const VkCommandBufferAllocateInfo* pAllo } void Device::FreeCommandBuffers(VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers) { - const std::lock_guard lock{command_pool_mutex_}; - - auto command_pool = command_pools_.find(commandPool); - if (command_pool == command_pools_.end()) { + auto command_pool_state = command_pools_.Get(commandPool); + if (!command_pool_state) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-FreeCommandBuffers-UnknownCommandPool", - "vkFreeCommandBuffers called with an unknown commandPool pointer (%p)", commandPool); + Log().Error("VKSC-EMU-FreeCommandBuffers-InvalidCommandPool", + "vkFreeCommandBuffers called with an invalid command pool handle (%p)", commandPool); return; } - if (command_pool->second->FreeCommandBuffers(commandBufferCount, pCommandBuffers) != VK_SUCCESS) { + if (command_pool_state->FreeCommandBuffers(commandBufferCount, pCommandBuffers) < VK_SUCCESS) { return; } @@ -186,10 +179,19 @@ void Device::FreeCommandBuffers(VkCommandPool commandPool, uint32_t commandBuffe VkResult Device::CreatePipelineCache(const VkPipelineCacheCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache) { + auto reservation = GetObjectTracker().ReservePipelineCache(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreatePipelineCache-OutOfReservedPipelineCacheObjects", + "Ran out of the reserved number of VkPipelineCache objects"); + return VK_ERROR_VALIDATION_FAILED; + } + // We do not create Vulkan pipeline caches here, just take one of the pre-created pipeline cache containers auto it = pipeline_cache_map_.find(pCreateInfo->pInitialData); if (it != pipeline_cache_map_.end()) { *pPipelineCache = it->second.VkSCHandle(); + reservation.Commit(pPipelineCache); return VK_SUCCESS; } else { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); @@ -200,7 +202,10 @@ VkResult Device::CreatePipelineCache(const VkPipelineCacheCreateInfo* pCreateInf } void Device::DestroyPipelineCache(VkPipelineCache pipelineCache, const VkAllocationCallbacks* pAllocator) { - // Nothing to do here as we do not create any dynamic objects for pipeline caches + if (pipelineCache != VK_NULL_HANDLE) { + GetObjectTracker().FreePipelineCache(1, &pipelineCache); + } + // Nothing else to do here as we do not create any dynamic objects for pipeline caches } const icd::Pipeline* Device::GetPipelineFromCache(const icd::PipelineCache& pipeline_cache, @@ -235,6 +240,14 @@ const icd::Pipeline* Device::GetPipelineFromCache(const icd::PipelineCache& pipe VkResult Device::CreateGraphicsPipelines(VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) { + auto reservation = GetObjectTracker().ReserveGraphicsPipeline(createInfoCount); + if (!reservation && createInfoCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateGraphicsPipelines-OutOfReservedGraphicsPipelineObjects", + "Ran out of the reserved number of graphics pipeline objects"); + return VK_ERROR_VALIDATION_FAILED; + } + VkResult result = VK_SUCCESS; auto pipeline_cache = icd::PipelineCache::FromHandle(pipelineCache); for (uint32_t i = 0; i < createInfoCount; ++i) { @@ -294,7 +307,7 @@ VkResult Device::CreateGraphicsPipelines(VkPipelineCache pipelineCache, uint32_t .GetModifiedPNext(); VkResult vk_result = NEXT::CreateGraphicsPipelines(VK_NULL_HANDLE, 1, &vk_create_info, pAllocator, &pPipelines[i]); - if (vk_result != VK_SUCCESS) { + if (vk_result < VK_SUCCESS) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_IMPLEMENTATION); Log().Error("VKSC-EMU-CreatePipeline-CreateGraphicsPipelinesFailed", "Failed to create underlying Vulkan graphics pipeline for pipeline (%s)", @@ -304,18 +317,25 @@ VkResult Device::CreateGraphicsPipelines(VkPipelineCache pipelineCache, uint32_t continue; } - if (RecyclePipelineMemory()) { - // Need to remember the pipeline's pool entry size to recycle it upon destruction - std::unique_lock pipeline_pool_size_map_lock(pipeline_pool_size_map_mutex_); - pipeline_pool_size_map_[pPipelines[i]] = offline_info->poolEntrySize; - } + pipelines_.Add(pPipelines[i], VK_PIPELINE_BIND_POINT_GRAPHICS, offline_info->poolEntrySize); } + + reservation.Commit(pPipelines); + return result; } VkResult Device::CreateComputePipelines(VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) { + auto reservation = GetObjectTracker().ReserveComputePipeline(createInfoCount); + if (!reservation && createInfoCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateComputePipelines-OutOfReservedComputePipelineObjects", + "Ran out of the reserved number of compute pipeline objects"); + return VK_ERROR_VALIDATION_FAILED; + } + VkResult result = VK_SUCCESS; auto pipeline_cache = icd::PipelineCache::FromHandle(pipelineCache); for (uint32_t i = 0; i < createInfoCount; ++i) { @@ -368,7 +388,7 @@ VkResult Device::CreateComputePipelines(VkPipelineCache pipelineCache, uint32_t .GetModifiedPNext(); VkResult vk_result = NEXT::CreateComputePipelines(VK_NULL_HANDLE, 1, &vk_create_info, pAllocator, &pPipelines[i]); - if (vk_result != VK_SUCCESS) { + if (vk_result < VK_SUCCESS) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_IMPLEMENTATION); Log().Error("VKSC-EMU-CreatePipeline-CreateComputePipelinesFailed", "Failed to create underlying Vulkan compute pipeline for pipeline (%s)", pipeline->ID().toString().c_str()); @@ -377,45 +397,61 @@ VkResult Device::CreateComputePipelines(VkPipelineCache pipelineCache, uint32_t continue; } - if (RecyclePipelineMemory()) { - // Need to remember the pipeline's pool entry size to recycle it upon destruction - std::unique_lock pipeline_pool_size_map_lock(pipeline_pool_size_map_mutex_); - pipeline_pool_size_map_[pPipelines[i]] = offline_info->poolEntrySize; - } + pipelines_.Add(pPipelines[i], VK_PIPELINE_BIND_POINT_COMPUTE, offline_info->poolEntrySize); } + + reservation.Commit(pPipelines); + return result; } void Device::DestroyPipeline(VkPipeline pipeline, const VkAllocationCallbacks* pAllocator) { - if (pipeline != VK_NULL_HANDLE && RecyclePipelineMemory()) { - std::unique_lock pipeline_pool_size_map_lock(pipeline_pool_size_map_mutex_); - auto it = pipeline_pool_size_map_.find(pipeline); - if (it != pipeline_pool_size_map_.end()) { - used_pipeline_pool_entries_map_[it->second].fetch_sub(1); - pipeline_pool_size_map_.erase(it); - } else { + if (pipeline != VK_NULL_HANDLE) { + auto pipeline_state = pipelines_.Get(pipeline); + if (!pipeline_state) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-DestroyPipeline-MissingPipelinePoolEntrySize", - "Missing pipeline pool entry size tracking information for pipeline (%p)", pipeline); + Log().Error("VKSC-EMU-DestroyPipeline-InvalidPipeline", "vkDestroyPipeline called with an invalid pipeline handle (%p)", + pipeline); + return; } + + if (RecyclePipelineMemory()) { + used_pipeline_pool_entries_map_[pipeline_state->GetPoolSize()].fetch_sub(1); + } + + switch (pipeline_state->GetBindPoint()) { + case VK_PIPELINE_BIND_POINT_GRAPHICS: + GetObjectTracker().FreeGraphicsPipeline(1, &pipeline); + break; + + case VK_PIPELINE_BIND_POINT_COMPUTE: + GetObjectTracker().FreeComputePipeline(1, &pipeline); + break; + + default: + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_IMPLEMENTATION); + Log().Error("VKSC-EMU-DestroyPipeline-UnexpectedPipelineType", "Unexpected pipeline bind point for pipeline (%p)", + pipeline); + break; + } + + pipelines_.Remove(pipeline); } + NEXT::DestroyPipeline(pipeline, pAllocator); } void Device::GetCommandPoolMemoryConsumption(VkCommandPool commandPool, VkCommandBuffer commandBuffer, VkCommandPoolMemoryConsumption* pConsumption) { - const std::lock_guard lock{command_pool_mutex_}; - - auto command_pool = command_pools_.find(commandPool); - if (command_pool == command_pools_.end()) { + auto command_pool_state = command_pools_.Get(commandPool); + if (!command_pool_state) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-GetCommandPoolMemoryConsumption-UnknownCommandPool", - "vkGetCommandPoolMemoryConsumption called with an unknown commandPool pointer (%p)", commandPool); + Log().Error("VKSC-EMU-GetCommandPoolMemoryConsumption-InvalidCommandPool", + "vkGetCommandPoolMemoryConsumption called with an invalid command pool handle (%p)", commandPool); return; } - pConsumption->commandPoolReservedSize = command_pool->second.get()->GetReservedMemorySize(); - pConsumption->commandPoolAllocated = command_pool->second.get()->GetAllocatedMemorySize(); + command_pool_state->GetMemoryConsumption(pConsumption); if (commandBuffer) { CommandBuffer* command_buffer = CommandBuffer::FromHandle(commandBuffer); @@ -426,17 +462,15 @@ void Device::GetCommandPoolMemoryConsumption(VkCommandPool commandPool, VkComman } VkResult Device::ResetCommandPool(VkCommandPool commandPool, VkCommandPoolResetFlags flags) { - const std::lock_guard lock{command_pool_mutex_}; - - auto command_pool = command_pools_.find(commandPool); - if (command_pool == command_pools_.end()) { + auto command_pool_state = command_pools_.Get(commandPool); + if (!command_pool_state) { ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-ResetCommandPool-UnknownCommandPool", - "vkResetCommandPool called with an unknown commandPool pointer (%p)", commandPool); - return VK_ERROR_OUT_OF_DEVICE_MEMORY; + Log().Error("VKSC-EMU-ResetCommandPool-InvalidCommandPool", + "vkResetCommandPool called with an invalid command pool handle (%p)", commandPool); + return VK_ERROR_VALIDATION_FAILED; } - return command_pool->second->ResetCommandPool(); + return command_pool_state->ResetCommandPool(); } VkResult Device::GetFaultData(VkFaultQueryBehavior faultQueryBehavior, VkBool32* pUnrecordedFaults, uint32_t* pFaultCount, @@ -446,113 +480,270 @@ VkResult Device::GetFaultData(VkFaultQueryBehavior faultQueryBehavior, VkBool32* VkResult Device::AllocateMemory(const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) { - if (auto reservation = GetObjectTracker().ReserveDeviceMemory()) { - VkResult result = NEXT::AllocateMemory(pAllocateInfo, pAllocator, pMemory); - if (result >= VK_SUCCESS) { - reservation.Commit(pMemory); - } - return result; - } else { + auto reservation = GetObjectTracker().ReserveDeviceMemory(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-AllocateMemory-OutOfReservedDeviceMemoryObjects", "Ran out of the reserved number of VkDeviceMemory objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::AllocateMemory(pAllocateInfo, pAllocator, pMemory); + if (result >= VK_SUCCESS) { + reservation.Commit(pMemory); } + return result; } VkResult Device::CreateCommandPool(const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool) { - if (auto reservation = GetObjectTracker().ReserveCommandPool()) { - icd::ShadowStack::Frame stack_frame{}; + auto reservation = GetObjectTracker().ReserveCommandPool(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateCommandPool-OutOfReservedCommandPoolObjects", + "Ran out of the reserved number of VkCommandPool objects"); + return VK_ERROR_VALIDATION_FAILED; + } - auto memory_reservation = vku::FindStructInPNextChain(pCreateInfo->pNext); - if (memory_reservation == nullptr) { - ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); - Log().Error("VKSC-EMU-CreateCommandPool-MissingMemoryReservationInfo", - "Command pool creation called with missing VkCommandPoolMemoryReservationCreateInfo"); - return VK_ERROR_OUT_OF_HOST_MEMORY; - } + auto memory_reservation = vku::FindStructInPNextChain(pCreateInfo->pNext); + if (memory_reservation == nullptr) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateCommandPool-MissingMemoryReservationInfo", + "Command pool creation called with missing VkCommandPoolMemoryReservationCreateInfo"); + return VK_ERROR_OUT_OF_HOST_MEMORY; + } - // Remove VkCommandPoolMemoryReservationCreateInfo from Vulkan create info pNext chain - auto vk_create_info = *pCreateInfo; - vk_create_info.pNext = icd::ModifiablePNextChain(stack_frame, vk_create_info) - .RemoveStructFromChain() - .GetModifiedPNext(); + auto cmdbuf_reservation = GetObjectTracker().ReserveCommandBuffer(memory_reservation->commandPoolMaxCommandBuffers); + if (!cmdbuf_reservation && memory_reservation->commandPoolMaxCommandBuffers > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateCommandPool-OutOfReservedCommandBufferObjects", + "Ran out of the reserved number of VkCommandBuffer objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + icd::ShadowStack::Frame stack_frame{}; - const std::lock_guard lock{command_pool_mutex_}; + // Remove VkCommandPoolMemoryReservationCreateInfo from Vulkan create info pNext chain + auto vk_create_info = *pCreateInfo; + vk_create_info.pNext = icd::ModifiablePNextChain(stack_frame, vk_create_info) + .RemoveStructFromChain() + .GetModifiedPNext(); - VkResult result = NEXT::CreateCommandPool(&vk_create_info, pAllocator, pCommandPool); - if (result >= VK_SUCCESS) { - reservation.Commit(pCommandPool); - command_pools_.emplace(*pCommandPool, - std::make_unique(*pCommandPool, *this, memory_reservation->commandPoolReservedSize, - memory_reservation->commandPoolMaxCommandBuffers)); - } - return result; - } else { - Log().Error("VKSC-EMU-CreateCommandPool-OutOfReservedCommandPoolObjects", - "Ran out of the reserved number of VkCommandPool objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + VkResult result = NEXT::CreateCommandPool(&vk_create_info, pAllocator, pCommandPool); + if (result >= VK_SUCCESS) { + command_pools_.Add(*pCommandPool, *this, memory_reservation->commandPoolReservedSize, cmdbuf_reservation.Count()); + + cmdbuf_reservation.Commit(nullptr /* unused */); + reservation.Commit(pCommandPool); } + return result; } VkResult Device::CreateDescriptorPool(const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool) { - if (auto reservation = GetObjectTracker().ReserveDescriptorPool()) { - VkResult result = NEXT::CreateDescriptorPool(pCreateInfo, pAllocator, pDescriptorPool); - if (result >= VK_SUCCESS) { - reservation.Commit(pDescriptorPool); - } - return result; - } else { + auto reservation = GetObjectTracker().ReserveDescriptorPool(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-CreateDescriptorPool-OutOfReservedDescriptorPoolObjects", "Ran out of the reserved number of VkDescriptorPool objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateDescriptorPool(pCreateInfo, pAllocator, pDescriptorPool); + if (result >= VK_SUCCESS) { + descriptor_pools_.Add(*pDescriptorPool, *this, pCreateInfo->maxSets); + + reservation.Commit(pDescriptorPool); + } + return result; +} + +VkResult Device::ResetDescriptorPool(VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags) { + auto descriptor_pool_state = descriptor_pools_.Get(descriptorPool); + if (!descriptor_pool_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-ResetDescriptorPool-InvalidDescriptorPool", + "vkResetDescriptorPool called with an invalid descriptor pool handle (%p)", descriptorPool); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::ResetDescriptorPool(descriptorPool, flags); + + if (result >= VK_SUCCESS) { + GetObjectTracker().FreeDescriptorSet(descriptor_pool_state->GetAllocatedSetsAndReset(), nullptr /* unused */); + } + + return result; +} + +VkResult Device::AllocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets) { + auto descriptor_pool_state = descriptor_pools_.Get(pAllocateInfo->descriptorPool); + if (!descriptor_pool_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-AllocateDescriptorSets-InvalidDescriptorPool", + "vkAllocateDescriptorSets called with an invalid descriptor pool handle (%p)", pAllocateInfo->descriptorPool); + return VK_ERROR_VALIDATION_FAILED; + } + + auto reservation = GetObjectTracker().ReserveDescriptorSet(pAllocateInfo->descriptorSetCount); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-AllocateDescriptorSets-OutOfReservedDescriptorSetObjects", + "Ran out of the reserved number of VkDescriptorSet objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::AllocateDescriptorSets(pAllocateInfo, pDescriptorSets); + + if (result >= VK_SUCCESS) { + descriptor_pool_state->AllocateDescriptorSets(pAllocateInfo->descriptorSetCount); + + reservation.Commit(nullptr /* unused */); + } + + return result; +} + +VkResult Device::FreeDescriptorSets(VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, + const VkDescriptorSet* pDescriptorSets) { + auto descriptor_pool_state = descriptor_pools_.Get(descriptorPool); + if (!descriptor_pool_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-FreeDescriptorSets-UnknownDescriptorPool", + "vkFreeDescriptorSets called with an invalid descriptor pool handle (%p)", descriptorPool); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::FreeDescriptorSets(descriptorPool, descriptorSetCount, pDescriptorSets); + + if (result >= VK_SUCCESS) { + // Update reserved and allocated descriptor set counts + uint32_t actual_free_count = 0; + for (uint32_t i = 0; i < descriptorSetCount; ++i) { + if (pDescriptorSets[i] != VK_NULL_HANDLE) { + actual_free_count++; + } + } + + GetObjectTracker().FreeDescriptorSet(actual_free_count, nullptr /* unused */); + descriptor_pool_state->FreeDescriptorSets(actual_free_count); } + + return result; } VkResult Device::CreateQueryPool(const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool) { - if (auto reservation = GetObjectTracker().ReserveQueryPool()) { - VkResult result = NEXT::CreateQueryPool(pCreateInfo, pAllocator, pQueryPool); - if (result >= VK_SUCCESS) { - reservation.Commit(pQueryPool); - } - return result; - } else { + switch (pCreateInfo->queryType) { + case VK_QUERY_TYPE_OCCLUSION: + if (pCreateInfo->queryCount > GetObjectTracker().MaxOcclusionQueriesPerPool()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateQueryPool-MaxOcclustionQueriesPerPoolExceeded", + "Query count (%u) exceeds the requested maxOcclusionQueriesPerPool (%u)", pCreateInfo->queryCount, + GetObjectTracker().MaxOcclusionQueriesPerPool()); + return VK_ERROR_VALIDATION_FAILED; + } + break; + + case VK_QUERY_TYPE_PIPELINE_STATISTICS: + if (pCreateInfo->queryCount > GetObjectTracker().MaxPipelineStatisticsQueriesPerPool()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateQueryPool-MaxPipelineStatisticsQueriesPerPoolExceeded", + "Query count (%u) exceeds the requested maxPipelineStatisticsQueriesPerPool (%u)", + pCreateInfo->queryCount, GetObjectTracker().MaxPipelineStatisticsQueriesPerPool()); + return VK_ERROR_VALIDATION_FAILED; + } + break; + + case VK_QUERY_TYPE_TIMESTAMP: + if (pCreateInfo->queryCount > GetObjectTracker().MaxTimestampQueriesPerPool()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateQueryPool-MaxTimestampQueriesPerPoolExceeded", + "Query count (%u) exceeds the requested maxTimestampQueriesPerPool (%u)", pCreateInfo->queryCount, + GetObjectTracker().MaxTimestampQueriesPerPool()); + return VK_ERROR_VALIDATION_FAILED; + } + break; + + case VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR: + if (pCreateInfo->queryCount > GetObjectTracker().MaxPerformanceQueriesPerPool()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateQueryPool-MaxPerformanceQueriesPerPoolExceeded", + "Query count (%u) exceeds the requested maxPerformanceQueriesPerPool (%u)", pCreateInfo->queryCount, + GetObjectTracker().MaxPerformanceQueriesPerPool()); + return VK_ERROR_VALIDATION_FAILED; + } + break; + + default: + break; + } + + auto reservation = GetObjectTracker().ReserveQueryPool(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-CreateQueryPool-OutOfReservedQueryPoolObjects", "Ran out of the reserved number of VkQueryPool objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateQueryPool(pCreateInfo, pAllocator, pQueryPool); + if (result >= VK_SUCCESS) { + reservation.Commit(pQueryPool); } + return result; } VkResult Device::CreateSwapchainKHR(const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - if (auto reservation = GetObjectTracker().ReserveSwapchainKHR()) { - VkResult result = NEXT::CreateSwapchainKHR(pCreateInfo, pAllocator, pSwapchain); - if (result >= VK_SUCCESS) { - reservation.Commit(pSwapchain); - } - return result; - } else { + auto reservation = GetObjectTracker().ReserveSwapchainKHR(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-CreateSwapchainKHR-OutOfReservedSwapchainObjects", "Ran out of the reserved number of VkSwapchainKHR objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateSwapchainKHR(pCreateInfo, pAllocator, pSwapchain); + if (result >= VK_SUCCESS) { + if (!InitSwapchainImageInfo(*pSwapchain, pCreateInfo->imageExtent, pCreateInfo->imageArrayLayers)) { + Log().Error("VKSC-EMU-CreateSwapchainKHR-SwapchainImageInitFailed", + "Failed to initialize tracking information for swapchain images"); + NEXT::DestroySwapchainKHR(*pSwapchain, pAllocator); + return VK_ERROR_INITIALIZATION_FAILED; + } + + reservation.Commit(pSwapchain); } + return result; } VkResult Device::CreateSharedSwapchainsKHR(uint32_t swapchainCount, const VkSwapchainCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchains) { - if (auto reservation = GetObjectTracker().ReserveSwapchainKHR(swapchainCount)) { - VkResult result = NEXT::CreateSharedSwapchainsKHR(swapchainCount, pCreateInfos, pAllocator, pSwapchains); - if (result >= VK_SUCCESS) { - reservation.Commit(pSwapchains); - } - return result; - } else { + auto reservation = GetObjectTracker().ReserveSwapchainKHR(swapchainCount); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); Log().Error("VKSC-EMU-CreateSharedSwapchainsKHR-OutOfReservedSwapchainObjects", "Ran out of the reserved number of VkSwapchainKHR objects"); - return VK_ERROR_OUT_OF_HOST_MEMORY; + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateSharedSwapchainsKHR(swapchainCount, pCreateInfos, pAllocator, pSwapchains); + if (result >= VK_SUCCESS) { + for (uint32_t i = 0; i < swapchainCount; ++i) { + if (!InitSwapchainImageInfo(pSwapchains[i], pCreateInfos[i].imageExtent, pCreateInfos[i].imageArrayLayers)) { + Log().Error("VKSC-EMU-CreateSharedSwapchainKHR-SwapchainImageInitFailed", + "Failed to initialize tracking information for swapchain images"); + for (uint32_t j = 0; j < swapchainCount; ++j) { + NEXT::DestroySwapchainKHR(pSwapchains[j], pAllocator); + } + return VK_ERROR_INITIALIZATION_FAILED; + } + } + + reservation.Commit(pSwapchains); } + return result; } VkResult Device::SetDebugUtilsObjectNameEXT(const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { @@ -573,4 +764,516 @@ VkResult Device::SetDebugUtilsObjectTagEXT(const VkDebugUtilsObjectTagInfoEXT* p return VK_SUCCESS; } +VkResult Device::CreateSemaphore(const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore) { + auto reservation = GetObjectTracker().ReserveSemaphore(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateSemaphore-OutOfReservedSemaphoreObjects", + "Ran out of the reserved number of VkSemaphore objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateSemaphore(pCreateInfo, pAllocator, pSemaphore); + if (result >= VK_SUCCESS) { + reservation.Commit(pSemaphore); + } + return result; +} + +void Device::DestroySemaphore(VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator) { + if (semaphore != VK_NULL_HANDLE) { + GetObjectTracker().FreeSemaphore(1, &semaphore); + } + NEXT::DestroySemaphore(semaphore, pAllocator); +} + +VkResult Device::CreateFence(const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence) { + auto reservation = GetObjectTracker().ReserveFence(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateFence-OutOfReservedFenceObjects", "Ran out of the reserved number of VkFence objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateFence(pCreateInfo, pAllocator, pFence); + if (result >= VK_SUCCESS) { + reservation.Commit(pFence); + } + return result; +} + +void Device::DestroyFence(VkFence fence, const VkAllocationCallbacks* pAllocator) { + if (fence != VK_NULL_HANDLE) { + GetObjectTracker().FreeFence(1, &fence); + } + NEXT::DestroyFence(fence, pAllocator); +} + +VkResult Device::CreateEvent(const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent) { + auto reservation = GetObjectTracker().ReserveEvent(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateEvent-OutOfReservedEventObjects", "Ran out of the reserved number of VkEvent objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateEvent(pCreateInfo, pAllocator, pEvent); + if (result >= VK_SUCCESS) { + reservation.Commit(pEvent); + } + return result; +} + +void Device::DestroyEvent(VkEvent event, const VkAllocationCallbacks* pAllocator) { + if (event != VK_NULL_HANDLE) { + GetObjectTracker().FreeEvent(1, &event); + } + NEXT::DestroyEvent(event, pAllocator); +} + +VkResult Device::CreateBuffer(const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) { + auto reservation = GetObjectTracker().ReserveBuffer(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateBuffer-OutOfReservedBufferObjects", "Ran out of the reserved number of VkBuffer objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateBuffer(pCreateInfo, pAllocator, pBuffer); + if (result >= VK_SUCCESS) { + reservation.Commit(pBuffer); + } + return result; +} + +void Device::DestroyBuffer(VkBuffer buffer, const VkAllocationCallbacks* pAllocator) { + if (buffer != VK_NULL_HANDLE) { + GetObjectTracker().FreeBuffer(1, &buffer); + } + NEXT::DestroyBuffer(buffer, pAllocator); +} + +VkResult Device::CreateBufferView(const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkBufferView* pView) { + auto reservation = GetObjectTracker().ReserveBufferView(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateBufferView-OutOfReservedBufferViewObjects", + "Ran out of the reserved number of VkBufferView objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateBufferView(pCreateInfo, pAllocator, pView); + if (result >= VK_SUCCESS) { + reservation.Commit(pView); + } + return result; +} + +void Device::DestroyBufferView(VkBufferView bufferView, const VkAllocationCallbacks* pAllocator) { + if (bufferView != VK_NULL_HANDLE) { + GetObjectTracker().FreeBufferView(1, &bufferView); + } + NEXT::DestroyBufferView(bufferView, pAllocator); +} + +VkResult Device::CreateImage(const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) { + auto reservation = GetObjectTracker().ReserveImage(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImage-OutOfReservedImageObjects", "Ran out of the reserved number of VkImage objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateImage(pCreateInfo, pAllocator, pImage); + if (result >= VK_SUCCESS) { + reservation.Commit(pImage); + + images_.Add(*pImage, pCreateInfo->imageType, pCreateInfo->extent, pCreateInfo->mipLevels, pCreateInfo->arrayLayers); + } + return result; +} + +void Device::DestroyImage(VkImage image, const VkAllocationCallbacks* pAllocator) { + if (image != VK_NULL_HANDLE) { + GetObjectTracker().FreeImage(1, &image); + images_.Remove(image); + } + NEXT::DestroyImage(image, pAllocator); +} + +VkResult Device::CreateImageView(const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkImageView* pView) { + auto image_state = images_.Get(pCreateInfo->image); + if (!image_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-InvalidImage", "vkCreateImageView called with an invalid image handle (%p)", + pCreateInfo->image); + return VK_ERROR_VALIDATION_FAILED; + } + + const uint32_t level_count = image_state->GetImageViewLevelCount(*pCreateInfo); + const uint32_t layer_count = image_state->GetImageViewLayerCount(*pCreateInfo); + const bool is_layered = layer_count > 1; + + if (level_count > GetObjectTracker().MaxImageViewMipLevels()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-MaxImageViewMipLevelsExceeded", + "Level count (%u) exceeds the requested maxImageViewMipLevels (%u)", level_count, + GetObjectTracker().MaxImageViewMipLevels()); + return VK_ERROR_VALIDATION_FAILED; + } + + if (is_layered && level_count > GetObjectTracker().MaxLayeredImageViewMipLevels()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-MaxLayeredImageViewMipLevelsExceeded", + "Level count (%u) exceeds the requested maxLayeredImageViewMipLevels (%u)", level_count, + GetObjectTracker().MaxLayeredImageViewMipLevels()); + return VK_ERROR_VALIDATION_FAILED; + } + + if (layer_count > GetObjectTracker().MaxImageViewArrayLayers()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-MaxImageViewArrayLayersExceeded", + "Layer count (%u) exceeds the requested maxImageViewArrayLayers (%u)", layer_count, + GetObjectTracker().MaxImageViewArrayLayers()); + return VK_ERROR_VALIDATION_FAILED; + } + + auto reservation = GetObjectTracker().ReserveImageView(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-OutOfReservedImageViewObjects", + "Ran out of the reserved number of VkImageView objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto layered_reservation = GetObjectTracker().ReserveLayeredImageView(is_layered ? 1 : 0); + if (is_layered && !layered_reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateImageView-OutOfReservedLayeredImageViewObjects", + "Ran out of the reserved number of layered image view objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateImageView(pCreateInfo, pAllocator, pView); + if (result >= VK_SUCCESS) { + image_views_.Add(*pView, is_layered); + + if (is_layered) { + layered_reservation.Commit(pView); + } + reservation.Commit(pView); + } + return result; +} + +void Device::DestroyImageView(VkImageView imageView, const VkAllocationCallbacks* pAllocator) { + if (imageView != VK_NULL_HANDLE) { + auto image_view_state = image_views_.Get(imageView); + if (!image_view_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-DestroyImageView-InvalidImageView", + "vkDestroyImageView called with an invalid image view handle (%p)", imageView); + return; + } + + if (image_view_state->IsLayered()) { + GetObjectTracker().FreeLayeredImageView(1, &imageView); + } + GetObjectTracker().FreeImageView(1, &imageView); + + image_views_.Remove(imageView); + } + NEXT::DestroyImageView(imageView, pAllocator); +} + +VkResult Device::CreateSampler(const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkSampler* pSampler) { + auto reservation = GetObjectTracker().ReserveSampler(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateSampler-OutOfReservedSamplerObjects", "Ran out of the reserved number of VkSampler objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateSampler(pCreateInfo, pAllocator, pSampler); + if (result >= VK_SUCCESS) { + reservation.Commit(pSampler); + } + return result; +} + +void Device::DestroySampler(VkSampler sampler, const VkAllocationCallbacks* pAllocator) { + if (sampler != VK_NULL_HANDLE) { + GetObjectTracker().FreeSampler(1, &sampler); + } + NEXT::DestroySampler(sampler, pAllocator); +} + +VkResult Device::CreateSamplerYcbcrConversion(const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion) { + auto reservation = GetObjectTracker().ReserveSamplerYcbcrConversion(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateSamplerYcbcrConversion-OutOfReservedSamplerYcbcrConversionObjects", + "Ran out of the reserved number of VkSamplerYcbcrConversion objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateSamplerYcbcrConversion(pCreateInfo, pAllocator, pYcbcrConversion); + if (result >= VK_SUCCESS) { + reservation.Commit(pYcbcrConversion); + } + return result; +} + +void Device::DestroySamplerYcbcrConversion(VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator) { + if (ycbcrConversion != VK_NULL_HANDLE) { + GetObjectTracker().FreeSamplerYcbcrConversion(1, &ycbcrConversion); + } + NEXT::DestroySamplerYcbcrConversion(ycbcrConversion, pAllocator); +} + +VkResult Device::CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout) { + uint32_t requested_immutable_samplers = 0; + for (uint32_t i = 0; i < pCreateInfo->bindingCount; ++i) { + const auto& binding = pCreateInfo->pBindings[i]; + + if (binding.binding >= GetObjectTracker().DescriptorSetLayoutBindingLimit()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateDescriptorSetLayout-DescriptorSetLayoutBindingLimitExceeded", + "Binding (%u) for index #%u exceeds the requested descriptorSetLayoutBindingLimit (%u)", binding.binding, i, + GetObjectTracker().DescriptorSetLayoutBindingLimit()); + return VK_ERROR_VALIDATION_FAILED; + } + + if ((binding.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || + binding.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) && + binding.pImmutableSamplers != nullptr) { + requested_immutable_samplers += binding.descriptorCount; + } + } + if (requested_immutable_samplers > GetObjectTracker().MaxImmutableSamplersPerDescriptorSetLayout()) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateDescriptorSetLayout-MaxImmutableSamplersPerDescriptorSetLayoutExceeded", + "The number of immutable samplers (%u) exceeds the requested maxImmutableSamplersPerDescriptorSetLayout (%u)", + requested_immutable_samplers, GetObjectTracker().MaxImmutableSamplersPerDescriptorSetLayout()); + return VK_ERROR_VALIDATION_FAILED; + } + + auto reservation = GetObjectTracker().ReserveDescriptorSetLayout(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateDescriptorSetLayout-OutOfReservedDescriptorSetLayoutObjects", + "Ran out of the reserved number of VkDescriptorSetLayout objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto binding_reservation = GetObjectTracker().ReserveDescriptorSetLayoutBinding(pCreateInfo->bindingCount); + if (!binding_reservation && pCreateInfo->bindingCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateDescriptorSetLayout-OutOfReservedDescriptorSetLayoutBindings", + "Ran out of the reserved number of descriptor set layout bindings"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateDescriptorSetLayout(pCreateInfo, pAllocator, pSetLayout); + if (result >= VK_SUCCESS) { + descriptor_set_layouts_.Add(*pSetLayout, binding_reservation.Count()); + + binding_reservation.Commit(nullptr /* unused */); + reservation.Commit(pSetLayout); + } + return result; +} + +void Device::DestroyDescriptorSetLayout(VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator) { + if (descriptorSetLayout != VK_NULL_HANDLE) { + auto descriptor_set_layout_state = descriptor_set_layouts_.Get(descriptorSetLayout); + if (!descriptor_set_layout_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-DestroyDescriptorSetLayout-InvalidDescriptorSetLayout", + "vkDestroyDescriptorSetLayout called with an invalid descriptor set layout handle (%p)", + descriptorSetLayout); + return; + } + + GetObjectTracker().FreeDescriptorSetLayoutBinding(descriptor_set_layout_state->GetBindingCount(), nullptr /* unused */); + GetObjectTracker().FreeDescriptorSetLayout(1, &descriptorSetLayout); + + descriptor_set_layouts_.Remove(descriptorSetLayout); + } + + NEXT::DestroyDescriptorSetLayout(descriptorSetLayout, pAllocator); +} + +VkResult Device::CreatePipelineLayout(const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkPipelineLayout* pPipelineLayout) { + auto reservation = GetObjectTracker().ReservePipelineLayout(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreatePipelineLayout-OutOfReservedPipelineLayoutObjects", + "Ran out of the reserved number of VkPipelineLayout objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreatePipelineLayout(pCreateInfo, pAllocator, pPipelineLayout); + if (result >= VK_SUCCESS) { + reservation.Commit(pPipelineLayout); + } + return result; +} + +void Device::DestroyPipelineLayout(VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator) { + if (pipelineLayout != VK_NULL_HANDLE) { + GetObjectTracker().FreePipelineLayout(1, &pipelineLayout); + } + NEXT::DestroyPipelineLayout(pipelineLayout, pAllocator); +} + +VkResult Device::CreateRenderPass(const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass) { + auto reservation = GetObjectTracker().ReserveRenderPass(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass-OutOfReservedRenderPassObjects", + "Ran out of the reserved number of VkRenderPass objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto subpass_desc_reservation = GetObjectTracker().ReserveSubpassDescription(pCreateInfo->subpassCount); + if (!subpass_desc_reservation && pCreateInfo->subpassCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass-OutOfReservedSubpassDescriptions", + "Ran out of the reserved number of subpass descriptions"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto attachment_desc_reservation = GetObjectTracker().ReserveAttachmentDescription(pCreateInfo->attachmentCount); + if (!attachment_desc_reservation && pCreateInfo->attachmentCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass-OutOfReservedAttachmentDescriptions", + "Ran out of the reserved number of attachment descriptions"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateRenderPass(pCreateInfo, pAllocator, pRenderPass); + + if (result >= VK_SUCCESS) { + render_passes_.Add(*pRenderPass, subpass_desc_reservation.Count(), attachment_desc_reservation.Count()); + + subpass_desc_reservation.Commit(nullptr /* unused */); + attachment_desc_reservation.Commit(nullptr /* unused */); + reservation.Commit(pRenderPass); + } + + return result; +} + +VkResult Device::CreateRenderPass2(const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass) { + auto reservation = GetObjectTracker().ReserveRenderPass(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass2-OutOfReservedRenderPassObjects", + "Ran out of the reserved number of VkRenderPass objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto subpass_desc_reservation = GetObjectTracker().ReserveSubpassDescription(pCreateInfo->subpassCount); + if (!subpass_desc_reservation && pCreateInfo->subpassCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass2-OutOfReservedSubpassDescriptions", + "Ran out of the reserved number of subpass descriptions"); + return VK_ERROR_VALIDATION_FAILED; + } + + auto attachment_desc_reservation = GetObjectTracker().ReserveAttachmentDescription(pCreateInfo->attachmentCount); + if (!attachment_desc_reservation && pCreateInfo->attachmentCount > 0) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateRenderPass2-OutOfReservedAttachmentDescriptions", + "Ran out of the reserved number of attachment descriptions"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateRenderPass2(pCreateInfo, pAllocator, pRenderPass); + + if (result >= VK_SUCCESS) { + render_passes_.Add(*pRenderPass, pCreateInfo->subpassCount, pCreateInfo->attachmentCount); + + subpass_desc_reservation.Commit(nullptr /* unused */); + attachment_desc_reservation.Commit(nullptr /* unused */); + reservation.Commit(pRenderPass); + } + + return result; +} + +void Device::DestroyRenderPass(VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator) { + if (renderPass != VK_NULL_HANDLE) { + auto render_pass_state = render_passes_.Get(renderPass); + if (!render_pass_state) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-DestroyRenderPass-InvalidRenderPass", + "vkDestroyRenderPass called with an invalid descriptor pool handle (%p)", renderPass); + return; + } + + GetObjectTracker().FreeSubpassDescription(render_pass_state->GetSubpassDescriptionCount(), nullptr /* unused */); + GetObjectTracker().FreeAttachmentDescription(render_pass_state->GetAttachmentDescriptionCount(), nullptr /* unused */); + GetObjectTracker().FreeRenderPass(1, &renderPass); + + render_passes_.Remove(renderPass); + } + + NEXT::DestroyRenderPass(renderPass, pAllocator); +} + +VkResult Device::CreateFramebuffer(const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkFramebuffer* pFramebuffer) { + auto reservation = GetObjectTracker().ReserveFramebuffer(); + if (!reservation) { + ReportFault(VK_FAULT_LEVEL_CRITICAL, VK_FAULT_TYPE_INVALID_API_USAGE); + Log().Error("VKSC-EMU-CreateFramebuffer-OutOfReservedFramebufferObjects", + "Ran out of the reserved number of VkFramebuffer objects"); + return VK_ERROR_VALIDATION_FAILED; + } + + VkResult result = NEXT::CreateFramebuffer(pCreateInfo, pAllocator, pFramebuffer); + if (result >= VK_SUCCESS) { + reservation.Commit(pFramebuffer); + } + return result; +} + +void Device::DestroyFramebuffer(VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator) { + if (framebuffer != VK_NULL_HANDLE) { + GetObjectTracker().FreeFramebuffer(1, &framebuffer); + } + NEXT::DestroyFramebuffer(framebuffer, pAllocator); +} + +bool Device::InitSwapchainImageInfo(VkSwapchainKHR swapchain, VkExtent2D extent, uint32_t array_layers) { + uint32_t image_count = 0; + VkResult result = NEXT::GetSwapchainImagesKHR(swapchain, &image_count, nullptr); + if (result < VK_SUCCESS) { + return false; + } + + icd::ShadowStack::Frame stack_frame{}; + auto images = stack_frame.Alloc(image_count); + result = NEXT::GetSwapchainImagesKHR(swapchain, &image_count, images); + if (result < VK_SUCCESS) { + return false; + } + + for (uint32_t i = 0; i < image_count; ++i) { + images_.Add(images[i], VK_IMAGE_TYPE_2D, VkExtent3D{extent.width, extent.height, 1}, 1, array_layers); + } + return false; +} + } // namespace vksc diff --git a/icd/vksc_device.h b/icd/vksc_device.h index 00fb3dd..e67606d 100644 --- a/icd/vksc_device.h +++ b/icd/vksc_device.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,12 @@ #pragma once #include "vksc_command_pool.h" +#include "vksc_descriptor_pool.h" +#include "vksc_pipeline.h" +#include "vksc_render_pass.h" +#include "vksc_image.h" +#include "vksc_image_view.h" +#include "vksc_descriptor_set_layout.h" #include "vksc_dispatchable.h" #include "vksc_physical_device.h" #include "vksc_extension_helper.h" @@ -98,10 +104,64 @@ class Device : public Dispatchable, public vk::Device { VkResult SetDebugUtilsObjectNameEXT(const VkDebugUtilsObjectNameInfoEXT* pNameInfo); VkResult SetDebugUtilsObjectTagEXT(const VkDebugUtilsObjectTagInfoEXT* pTagInfo); + VkResult CreateSemaphore(const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore); + void DestroySemaphore(VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator); + + VkResult CreateFence(const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence); + void DestroyFence(VkFence fence, const VkAllocationCallbacks* pAllocator); + + VkResult CreateEvent(const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent); + void DestroyEvent(VkEvent event, const VkAllocationCallbacks* pAllocator); + + VkResult CreateBuffer(const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer); + void DestroyBuffer(VkBuffer buffer, const VkAllocationCallbacks* pAllocator); + + VkResult CreateBufferView(const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkBufferView* pView); + void DestroyBufferView(VkBufferView bufferView, const VkAllocationCallbacks* pAllocator); + + VkResult CreateImage(const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage); + void DestroyImage(VkImage image, const VkAllocationCallbacks* pAllocator); + + VkResult CreateImageView(const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImageView* pView); + void DestroyImageView(VkImageView imageView, const VkAllocationCallbacks* pAllocator); + + VkResult CreateSampler(const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSampler* pSampler); + void DestroySampler(VkSampler sampler, const VkAllocationCallbacks* pAllocator); + + VkResult CreateSamplerYcbcrConversion(const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion); + void DestroySamplerYcbcrConversion(VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator); + + VkResult ResetDescriptorPool(VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags); + VkResult AllocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets); + VkResult FreeDescriptorSets(VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, + const VkDescriptorSet* pDescriptorSets); + + VkResult CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkDescriptorSetLayout* pSetLayout); + void DestroyDescriptorSetLayout(VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator); + + VkResult CreatePipelineLayout(const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkPipelineLayout* pPipelineLayout); + void DestroyPipelineLayout(VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator); + + VkResult CreateRenderPass(const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + VkResult CreateRenderPass2(const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + void DestroyRenderPass(VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator); + + VkResult CreateFramebuffer(const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkFramebuffer* pFramebuffer); + void DestroyFramebuffer(VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator); + private: VkResult SetupDevice(const VkDeviceCreateInfo& create_info); const icd::Pipeline* GetPipelineFromCache(const icd::PipelineCache& pipeline_cache, const VkPipelineOfflineCreateInfo* offline_info, VkResult& out_result); + bool InitSwapchainImageInfo(VkSwapchainKHR, VkExtent2D extent, uint32_t array_layers); icd::DeviceObjectTracker& GetObjectTracker() { return object_tracker_; } @@ -124,12 +184,14 @@ class Device : public Dispatchable, public vk::Device { std::unordered_map reserved_pipeline_pool_entries_map_{}; std::unordered_map used_pipeline_pool_entries_map_{}; - // Map of pipelines and corresponding pool entry sizes (used only when pipeline pool entry recycling is enabled) - std::mutex pipeline_pool_size_map_mutex_{}; - std::unordered_map pipeline_pool_size_map_{}; - - std::mutex command_pool_mutex_{}; - std::unordered_map> command_pools_{}; + // Object state trackers + icd::ObjectStateTracker descriptor_set_layouts_{}; + icd::ObjectStateTracker pipelines_{}; + icd::ObjectStateTracker command_pools_{}; + icd::ObjectStateTracker descriptor_pools_{}; + icd::ObjectStateTracker render_passes_{}; + icd::ObjectStateTracker images_{}; + icd::ObjectStateTracker image_views_{}; }; } // namespace vksc diff --git a/icd/vksc_image.h b/icd/vksc_image.h new file mode 100644 index 0000000..36b3838 --- /dev/null +++ b/icd/vksc_image.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +namespace vksc { + +class Image { + public: + Image(VkImage handle, VkImageType image_type, VkExtent3D extent, uint32_t mip_levels, uint32_t array_layers) + : image_type_(image_type), extent_(extent), mip_levels_(mip_levels), array_layers_(array_layers) {} + + uint32_t GetImageViewLevelCount(const VkImageViewCreateInfo& create_info) const { + return GetSubresourceRangeLevelCount(create_info.subresourceRange); + } + + uint32_t GetImageViewLayerCount(const VkImageViewCreateInfo& create_info) const { + if (image_type_ == VK_IMAGE_TYPE_3D && create_info.viewType != VK_IMAGE_VIEW_TYPE_3D) { + return create_info.subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS + ? extent_.depth - create_info.subresourceRange.baseArrayLayer + : create_info.subresourceRange.layerCount; + } else { + return GetSubresourceRangeLayerCount(create_info.subresourceRange); + } + } + + private: + uint32_t GetSubresourceRangeLevelCount(const VkImageSubresourceRange& range) const { + return range.levelCount == VK_REMAINING_MIP_LEVELS ? mip_levels_ - range.baseMipLevel : range.levelCount; + } + + uint32_t GetSubresourceRangeLayerCount(const VkImageSubresourceRange& range) const { + return range.layerCount == VK_REMAINING_ARRAY_LAYERS ? array_layers_ - range.baseArrayLayer : range.layerCount; + } + + const VkImageType image_type_; + const VkExtent3D extent_; + const uint32_t mip_levels_; + const uint32_t array_layers_; +}; + +} // namespace vksc diff --git a/icd/vksc_image_view.h b/icd/vksc_image_view.h new file mode 100644 index 0000000..3cb0a1e --- /dev/null +++ b/icd/vksc_image_view.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +namespace vksc { + +class ImageView { + public: + ImageView(VkImageView handle, bool layered) : layered_(layered) {} + + bool IsLayered() const { return layered_; } + + private: + const bool layered_; +}; + +} // namespace vksc diff --git a/icd/vksc_physical_device.h b/icd/vksc_physical_device.h index b0b2371..d305195 100644 --- a/icd/vksc_physical_device.h +++ b/icd/vksc_physical_device.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024-2025 The Khronos Group Inc. - * Copyright (c) 2024-2025 RasterGrid Kft. + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,7 @@ #include #include #include +#include namespace vksc { diff --git a/icd/vksc_pipeline.h b/icd/vksc_pipeline.h new file mode 100644 index 0000000..fb2d56a --- /dev/null +++ b/icd/vksc_pipeline.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +namespace vksc { + +class Pipeline { + public: + Pipeline(VkPipeline handle, VkPipelineBindPoint bind_point, uint64_t pool_size) + : bind_point_(bind_point), pool_size_(pool_size) {} + + VkPipelineBindPoint GetBindPoint() const { return bind_point_; } + uint64_t GetPoolSize() const { return pool_size_; } + + private: + const VkPipelineBindPoint bind_point_; + const uint64_t pool_size_; +}; + +} // namespace vksc diff --git a/icd/vksc_render_pass.h b/icd/vksc_render_pass.h new file mode 100644 index 0000000..108cc5c --- /dev/null +++ b/icd/vksc_render_pass.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "icd_defs.h" + +namespace vksc { + +class RenderPass { + public: + RenderPass(VkRenderPass handle, uint32_t subpass_desc_count, uint32_t attachment_desc_count) + : subpass_desc_count_(subpass_desc_count), attachment_desc_count_(attachment_desc_count) {} + + uint32_t GetSubpassDescriptionCount() const { return subpass_desc_count_; } + uint32_t GetAttachmentDescriptionCount() const { return attachment_desc_count_; } + + private: + const uint32_t subpass_desc_count_; + const uint32_t attachment_desc_count_; +}; + +} // namespace vksc diff --git a/tests/icd/CMakeLists.txt b/tests/icd/CMakeLists.txt index 9c865f2..41594ca 100644 --- a/tests/icd/CMakeLists.txt +++ b/tests/icd/CMakeLists.txt @@ -82,6 +82,7 @@ target_sources(icd_tests PRIVATE test_infrastructure.cpp test_memory_consumption.cpp test_pipeline_cache.cpp + test_object_reservation.cpp test_output_struct_sanitizer.cpp ) diff --git a/tests/icd/icd_test_framework.cpp b/tests/icd/icd_test_framework.cpp index 5013aec..1108a51 100644 --- a/tests/icd/icd_test_framework.cpp +++ b/tests/icd/icd_test_framework.cpp @@ -22,15 +22,13 @@ } while (0) static void InitDefaultMockHandlers(IcdTest *test_case = nullptr) { + static VkMockNonDispatchableObjectGenerator mock_gen{}; static VkMockObject mock_instance{}; static VkMockObject mock_physical_device{}; static VkMockObject mock_device{}; static VkMockObject mock_queue{}; - static VkMockObject mock_command_pool{}; static std::vector> mock_command_buffers{}; - static VkMockObject mock_buffer{}; static VkDeviceSize mock_buffer_size = 0; - static VkMockObject mock_memory{}; vkmock::Reset(); @@ -154,12 +152,28 @@ static void InitDefaultMockHandlers(IcdTest *test_case = nullptr) { vkmock::GetBufferMemoryRequirements2 = [&](auto device, auto pInfo, auto pMemoryRequirements) { vkmock::GetBufferMemoryRequirements(device, pInfo->buffer, &pMemoryRequirements->memoryRequirements); }; + vkmock::CreateSampler = [&](auto, auto, auto, auto pSampler) { + *pSampler = mock_gen.Alloc(); + return VK_SUCCESS; + }; vkmock::CreateBuffer = [&](auto, auto, auto, auto pBuffer) { - *pBuffer = mock_buffer; + *pBuffer = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateImage = [&](auto, auto, auto, auto pImage) { + *pImage = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateImageView = [&](auto, auto, auto, auto pView) { + *pView = mock_gen.Alloc(); return VK_SUCCESS; }; vkmock::CreateCommandPool = [&](auto, const auto pCreateInfo, const auto, auto pCommandPool) { - *pCommandPool = mock_command_pool; + *pCommandPool = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateQueryPool = [&](auto, const auto pCreateInfo, const auto, auto pQueryPool) { + *pQueryPool = mock_gen.Alloc(); return VK_SUCCESS; }; vkmock::AllocateCommandBuffers = [&](auto, auto pCreateInfo, auto pCommandBuffers) { @@ -170,16 +184,20 @@ static void InitDefaultMockHandlers(IcdTest *test_case = nullptr) { return VK_SUCCESS; }; vkmock::AllocateMemory = [&](auto, auto, auto, auto pMemory) { - *pMemory = mock_memory; + *pMemory = mock_gen.Alloc(); return VK_SUCCESS; }; vkmock::FreeMemory = [&](auto, auto, auto) {}; + vkmock::DestroySampler = [&](auto, auto, auto) {}; vkmock::DestroyBuffer = [&](auto, auto, auto) {}; + vkmock::DestroyImage = [&](auto, auto, auto) {}; + vkmock::DestroyImageView = [&](auto, auto, auto) {}; vkmock::FreeCommandBuffers = [&](auto, auto, auto, auto) {}; vkmock::BindBufferMemory2 = [&](auto, auto, auto) { return VK_SUCCESS; }; vkmock::BeginCommandBuffer = [&](auto, auto) { return VK_SUCCESS; }; vkmock::EndCommandBuffer = [&](auto) { return VK_SUCCESS; }; vkmock::DestroyCommandPool = [&](auto, auto, auto) {}; + vkmock::DestroyQueryPool = [&](auto, auto, auto) {}; vkmock::CreateInstance = [&](auto, auto, auto pInstance) { *pInstance = mock_instance; return VK_SUCCESS; @@ -190,6 +208,26 @@ static void InitDefaultMockHandlers(IcdTest *test_case = nullptr) { return VK_SUCCESS; }; vkmock::DestroyDevice = [&](auto, auto) {}; + vkmock::CreateDescriptorSetLayout = [&](auto, auto, auto, auto pSetLayout) { + *pSetLayout = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyDescriptorSetLayout = [&](auto, auto, auto) {}; + vkmock::CreatePipelineLayout = [&](auto, auto, auto, auto pPipelineLayout) { + *pPipelineLayout = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyPipelineLayout = [&](auto, auto, auto) {}; + vkmock::CreateShaderModule = [&](auto, auto, auto, auto pShaderModule) { + *pShaderModule = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyShaderModule = [&](auto, auto, auto) {}; + vkmock::CreateRenderPass = [&](auto, auto, auto, auto pRenderPass) { + *pRenderPass = mock_gen.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyRenderPass = [&](auto, auto, auto) {}; } void Framework::SetUp() { diff --git a/tests/icd/icd_test_framework.h b/tests/icd/icd_test_framework.h index feafe59..f104463 100644 --- a/tests/icd/icd_test_framework.h +++ b/tests/icd/icd_test_framework.h @@ -10,7 +10,10 @@ #include #include #include +#include #include +#include +#include #include #include @@ -40,14 +43,71 @@ class Framework : public ::testing::Environment { template class VkMockObject { public: - VkMockObject() { set_loader_magic_value(this); } - T handle() { return (T)this; } - operator T() { return (T)this; } + VkMockObject() {} + T handle() { + set_loader_magic_value(this); + return (T)this; + } + operator T() { + set_loader_magic_value(this); + return (T)this; + } private: VK_LOADER_DATA loader_data_; }; +template +class VkMockObjects { + public: + void Reset(uint32_t count) { + std::unique_lock lock(mutex_); + mock_objects_.clear(); + unused_mock_objects_.clear(); + mock_objects_.resize(count); + unused_mock_objects_.reserve(count); + for (auto& mock_object : mock_objects_) { + unused_mock_objects_.push_back(mock_object); + } + } + + T Alloc() { + std::unique_lock lock(mutex_); + if (!unused_mock_objects_.empty()) { + auto handle = unused_mock_objects_.back(); + unused_mock_objects_.pop_back(); + // Need to reset loader magic value on reuse + set_loader_magic_value(handle); + return handle; + } else { + return VK_NULL_HANDLE; + } + } + + void Free(T handle) { + if (handle != VK_NULL_HANDLE) { + std::unique_lock lock(mutex_); + unused_mock_objects_.push_back(handle); + } + } + + private: + std::mutex mutex_{}; + std::vector> mock_objects_{}; + std::vector unused_mock_objects_{}; +}; + +class VkMockNonDispatchableObjectGenerator { + public: + template + T Alloc() { + return reinterpret_cast(next_mock_handle_.fetch_add(1)); + } + + private: + std::atomic_uint64_t next_mock_handle_{42}; +}; + class IcdTest : public ::testing::Test { public: IcdTest(); diff --git a/tests/icd/test_object_reservation.cpp b/tests/icd/test_object_reservation.cpp new file mode 100644 index 0000000..ebf8c41 --- /dev/null +++ b/tests/icd/test_object_reservation.cpp @@ -0,0 +1,2629 @@ +/* + * Copyright (c) 2024-2026 The Khronos Group Inc. + * Copyright (c) 2024-2026 RasterGrid Kft. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "icd_test_framework.h" +#include "icd_test_pipeline_cache_utils.h" + +#include +#include +#include + +class ObjectReservationTest : public IcdTest { + public: + inline static uint64_t kPipelinePoolEntrySize = 65536; + inline static const char* kGraphicsPipelineUUID = "1265a236-e369-11ed-b5ea-0242ac120002"; + inline static const char* kComputePipelineUUID = "b23d0e5c-70a0-4d67-8781-99ec3798ed31"; + + void InitPipelineCaches() { + // clang-format off + pipeline_caches_.in_pipeline_pools = { + PipelinePoolDesc{kPipelinePoolEntrySize, 2} + }; + pipeline_caches_.in_pipeline_caches = { + PipelineCacheDesc{ + { + PipelineDesc{ + kGraphicsPipelineUUID, + 49658, + kSampleGraphicsPipelineJson, + { + CompileSPV(kSampleVertexShaderSpv), + CompileSPV(kSampleFragmentShaderSpv) + } + }, + PipelineDesc{ + kComputePipelineUUID, + 6512, + kSampleComputePipelineJson, + { + CompileSPV(kSampleComputeShaderSpv), + } + } + } + } + }; + // clang-format on + + BuildPipelineCaches(pipeline_caches_); + } + + VkPipelinePoolSize GetPipelinePoolSize() const { + assert(!pipeline_caches_.out_pipeline_pool_sizes.empty()); + return pipeline_caches_.out_pipeline_pool_sizes[0]; + } + + VkPipelineCacheCreateInfo GetPipelineCacheCreateInfo() const { + assert(!pipeline_caches_.out_pipeline_cache_create_info.empty()); + return pipeline_caches_.out_pipeline_cache_create_info[0]; + } + + VkPipelineOfflineCreateInfo GetGraphicsPipelineOfflineCreateInfo() const { + auto offline_info = vku::InitStruct(); + utils::UUID(kGraphicsPipelineUUID).CopyToArray(offline_info.pipelineIdentifier); + offline_info.poolEntrySize = kPipelinePoolEntrySize; + return offline_info; + } + + VkPipelineOfflineCreateInfo GetComputePipelineOfflineCreateInfo() const { + auto offline_info = vku::InitStruct(); + utils::UUID(kComputePipelineUUID).CopyToArray(offline_info.pipelineIdentifier); + offline_info.poolEntrySize = kPipelinePoolEntrySize; + return offline_info; + } + + VkPhysicalDeviceVulkanSC10Properties GetVulkanSC10Properties() { + auto physical_device = GetPhysicalDevice(); + auto sc_10_props = vku::InitStruct(); + auto props2 = vku::InitStruct(&sc_10_props); + vksc::GetPhysicalDeviceProperties2(physical_device, &props2); + return sc_10_props; + } + + using caps_func_t = std::function; + using setup_func_t = std::function; + using create_func_t = std::function; + using destroy_func_t = std::function; + using teardown_func_t = std::function; + + VkDevice InitDeviceWithCustomObjectReservation(void* object_reservation_info) { + const float queue_priority = 1.f; + auto queue_info = vku::InitStruct(); + queue_info.queueCount = 1; + queue_info.pQueuePriorities = &queue_priority; + + auto device_reservation_info = vku::InitStruct(object_reservation_info); + device_reservation_info.pipelineCacheCreateInfoCount = + static_cast(pipeline_caches_.out_pipeline_cache_create_info.size()); + device_reservation_info.pPipelineCacheCreateInfos = pipeline_caches_.out_pipeline_cache_create_info.data(); + device_reservation_info.pipelinePoolSizeCount = static_cast(pipeline_caches_.out_pipeline_pool_sizes.size()); + device_reservation_info.pPipelinePoolSizes = pipeline_caches_.out_pipeline_pool_sizes.data(); + + auto phys_dev_sc_features = vku::InitStruct(&device_reservation_info); + auto create_info = vku::InitStruct(&phys_dev_sc_features); + create_info.queueCreateInfoCount = 1; + create_info.pQueueCreateInfos = &queue_info; + + return InitDevice(&create_info); + } + + void TestObjectReservationLimit(uint32_t max_create_count, bool can_destroy, bool has_parent, caps_func_t caps_func, + setup_func_t setup_func, create_func_t create_func, destroy_func_t destroy_func, + teardown_func_t teardown_func) { + auto sc_10_features = vku::InitStruct(); + auto object_reservation_info = vku::InitStruct(&sc_10_features); + + const std::vector tested_limits{0, 1}; //, 7, 13, 42, 111, 499}; + for (auto tested_limit : tested_limits) { + const uint32_t over_limit = 5; + + if (!caps_func(object_reservation_info, tested_limit)) { + continue; + } + + const float queue_priority = 1.f; + auto queue_info = vku::InitStruct(); + queue_info.queueCount = 1; + queue_info.pQueuePriorities = &queue_priority; + + auto device_reservation_info = vku::InitStruct(&object_reservation_info); + device_reservation_info.pipelineCacheCreateInfoCount = + static_cast(pipeline_caches_.out_pipeline_cache_create_info.size()); + device_reservation_info.pPipelineCacheCreateInfos = pipeline_caches_.out_pipeline_cache_create_info.data(); + device_reservation_info.pipelinePoolSizeCount = static_cast(pipeline_caches_.out_pipeline_pool_sizes.size()); + device_reservation_info.pPipelinePoolSizes = pipeline_caches_.out_pipeline_pool_sizes.data(); + + auto phys_dev_sc_features = vku::InitStruct(&device_reservation_info); + auto create_info = vku::InitStruct(&phys_dev_sc_features); + create_info.queueCreateInfoCount = 1; + create_info.pQueueCreateInfos = &queue_info; + + auto device = InitDevice(&create_info); + + if (setup_func) { + if (!setup_func(device)) { + if (teardown_func) { + teardown_func(device); + } + continue; + } + } + + if (max_create_count == 0) { + // Commands can only create a single object at a time + + // Create up to the desired limit + for (uint32_t i = 0; i < tested_limit; ++i) { + create_func(device, i, 1, false); + } + + // Expect additional creates to fail + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, 1, true); + } + + if (can_destroy) { + // Destroy some objects + for (uint32_t i = 0; i < tested_limit; i += 5) { + destroy_func(device, i, 1); + } + + // Expect that we can create new objects instead of the destroyed ones + for (uint32_t i = 0; i < tested_limit; i += 5) { + create_func(device, i, 1, false); + } + + // Expect additional creates to fail once again + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, 1, true); + } + + if (tested_limit > 0) { + // Destroy the first object multiple times (later destroys will be ignored) + for (uint32_t i = 0; i < 5; ++i) { + destroy_func(device, 0, 1); + } + + // Expect to be able to create an object instead of it + create_func(device, 0, 1, false); + + // Expect additional attempts to fail once again + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, 1, true); + } + } + + // Destroy all objects + for (uint32_t i = 0; i < tested_limit; ++i) { + destroy_func(device, i, 1); + } + } + } else if (!has_parent) { + // Commands can create multiple objects at a time, and they're not part of a parent object + // (e.g. like pipelines) + + // Create up to the desired limit + uint32_t already_created_count = 0; + while (already_created_count < tested_limit) { + uint32_t create_count = std::min(tested_limit - already_created_count, max_create_count); + create_func(device, already_created_count, create_count, false); + already_created_count += create_count; + } + + // Expect additional creates to fail + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, over_limit - i, true); + } + + if (can_destroy) { + // Destroy some objects + uint32_t destroy_count = tested_limit / 4; + for (uint32_t i = 0; i < destroy_count; ++i) { + destroy_func(device, i, 1); + destroy_func(device, i + tested_limit / 2, 1); + } + + // Expect that we can create new objects instead of the destroyed ones + for (uint32_t i = 0; i < destroy_count; ++i) { + create_func(device, i, 1, false); + create_func(device, i + tested_limit / 2, 1, false); + } + + // Expect additional creates to fail again + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, over_limit - i, true); + } + + if (tested_limit > 0) { + // Destroy the first object multiple times (later destroys will be ignored) + for (uint32_t i = 0; i < 5; ++i) { + destroy_func(device, 0, 1); + } + + // Expect to be able to create an object instead of it + create_func(device, 0, 1, false); + + // Expect additional attempts to fail once again + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, 1, true); + } + } + + // Destroy all objects + for (uint32_t i = 0; i < tested_limit; ++i) { + destroy_func(device, i, 1); + } + } + } else { + // Commands can create multiple objects at a time, and they're part of a parent object + // (e.g. like render pass attachment descriptions) + + // Create up to the desired limit minus max_create_count - 1 + uint32_t object_count = 0; + uint32_t left_to_create = (tested_limit >= max_create_count) ? tested_limit + 1 - max_create_count : 0; + while (left_to_create >= max_create_count) { + create_func(device, object_count++, max_create_count, false); + left_to_create -= max_create_count; + } + + if (left_to_create > 0) { + create_func(device, object_count++, left_to_create, false); + } + + // Trying to create another max_create_count number should fail + create_func(device, object_count, max_create_count, true); + + // Now allocate one by one and try to reserve one more than remaining + uint32_t remaining_count = std::min(max_create_count - 1, tested_limit); + for (uint32_t i = 0; i < remaining_count; ++i) { + create_func(device, object_count++, 1, false); + create_func(device, object_count, remaining_count - i + 1, true); + } + + if (can_destroy) { + // Destroy one by one the allocations done in the previous step + for (uint32_t i = 0; i < remaining_count; ++i) { + destroy_func(device, --object_count, 0); + } + + if (tested_limit > 0) { + if (max_create_count > 1) { + // Expect to be able to create a single object instead of it with max_create_count - 1 + create_func(device, object_count++, std::min(max_create_count - 1, tested_limit), false); + } + + // After that any attempts to create should fail + create_func(device, object_count, 1, true); + + // Destroy the first object multiple times (later destroys will be ignored) + for (uint32_t i = 0; i < 5; ++i) { + destroy_func(device, 0, 0); + } + + // Expect to be able to create an object instead of it + create_func(device, 0, std::min(max_create_count, tested_limit), false); + + // Expect additional attempts to fail once again + for (uint32_t i = 0; i < over_limit; ++i) { + create_func(device, tested_limit + i, 1, true); + } + } + + // Destroy all objects + for (uint32_t i = 0; i < object_count; ++i) { + destroy_func(device, i, 0); + } + } + } + + if (teardown_func) { + teardown_func(device); + } + + DestroyDevice(); + } + } + + private: + PipelineCacheInfo pipeline_caches_; +}; + +TEST_F(ObjectReservationTest, CommandPoolRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::commandPoolRequestCount"); + + struct { + std::vector cmd_pools{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = false; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.commandPoolRequestCount = tested_limit; + object_reservation_info.commandBufferRequestCount = tested_limit + 1; + + data.cmd_pools.clear(); + data.cmd_pools.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateCommandPool = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyCommandPool = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkCommandPool cmd_pool = VK_NULL_HANDLE; + + auto mem_reservation_info = vku::InitStruct(); + mem_reservation_info.commandPoolReservedSize = 64 * 1024; + mem_reservation_info.commandPoolMaxCommandBuffers = 1; + + auto create_info = vku::InitStruct(&mem_reservation_info); + + if (should_fail) { + EXPECT_EQ(vksc::CreateCommandPool(device, &create_info, nullptr, &cmd_pool), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.cmd_pools.size()); + EXPECT_EQ(vksc::CreateCommandPool(device, &create_info, nullptr, &data.cmd_pools[index]), VK_SUCCESS); + } + }, + // Destroy objects + nullptr, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, CommandBufferRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::commandBufferRequestCount"); + + struct { + std::vector cmd_pools{}; + } data; + + const uint32_t max_create_count = std::min(GetVulkanSC10Properties().maxCommandPoolCommandBuffers, 16u); + const bool can_destroy = false; + const bool has_parent = true; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.commandPoolRequestCount = tested_limit + 1; + object_reservation_info.commandBufferRequestCount = tested_limit; + + data.cmd_pools.clear(); + data.cmd_pools.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + 1); + vkmock::CreateCommandPool = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyCommandPool = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkCommandPool cmd_pool = VK_NULL_HANDLE; + + auto mem_reservation_info = vku::InitStruct(); + mem_reservation_info.commandPoolReservedSize = 1024 * 1024; + mem_reservation_info.commandPoolMaxCommandBuffers = create_count; + + auto create_info = vku::InitStruct(&mem_reservation_info); + + if (should_fail) { + EXPECT_EQ(vksc::CreateCommandPool(device, &create_info, nullptr, &cmd_pool), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.cmd_pools.size()); + EXPECT_EQ(vksc::CreateCommandPool(device, &create_info, nullptr, &data.cmd_pools[index]), VK_SUCCESS); + } + }, + // Destroy objects + nullptr, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, DescriptorSetLayoutRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::descriptorSetLayoutRequestCount"); + + struct { + std::vector descriptor_set_layouts{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.descriptorSetLayoutRequestCount = tested_limit; + object_reservation_info.descriptorSetLayoutBindingRequestCount = tested_limit + 1; + object_reservation_info.descriptorSetLayoutBindingLimit = 1; + + data.descriptor_set_layouts.clear(); + data.descriptor_set_layouts.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateDescriptorSetLayout = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyDescriptorSetLayout = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE; + + VkDescriptorSetLayoutBinding binding{}; + binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.descriptorCount = 1; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + auto create_info = vku::InitStruct(); + create_info.bindingCount = 1; + create_info.pBindings = &binding; + + if (should_fail) { + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &descriptor_set_layout), + VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.descriptor_set_layouts.size()); + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &data.descriptor_set_layouts[index]), + VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.descriptor_set_layouts.size()); + assert(destroy_count == 1); + + vksc::DestroyDescriptorSetLayout(device, data.descriptor_set_layouts[index], nullptr); + data.descriptor_set_layouts[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, DescriptorSetLayoutBindingRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::descriptorSetLayoutBindingRequestCount"); + + struct { + std::vector descriptor_set_layouts{}; + } data; + + const uint32_t max_create_count = 4; + const bool can_destroy = true; + const bool has_parent = true; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.descriptorSetLayoutRequestCount = tested_limit + 1; + object_reservation_info.descriptorSetLayoutBindingRequestCount = tested_limit; + object_reservation_info.descriptorSetLayoutBindingLimit = 4; + + data.descriptor_set_layouts.clear(); + data.descriptor_set_layouts.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + 1); + vkmock::CreateDescriptorSetLayout = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyDescriptorSetLayout = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE; + + uint32_t binding_count = 0; + std::vector bindings(create_count, VkDescriptorSetLayoutBinding{}); + for (auto& binding : bindings) { + binding.binding = binding_count++; + binding.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + binding.descriptorCount = 2; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + auto create_info = vku::InitStruct(); + create_info.bindingCount = binding_count; + create_info.pBindings = bindings.data(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &descriptor_set_layout), + VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.descriptor_set_layouts.size()); + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &data.descriptor_set_layouts[index]), + VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.descriptor_set_layouts.size()); + assert(destroy_count == 0); + + vksc::DestroyDescriptorSetLayout(device, data.descriptor_set_layouts[index], nullptr); + data.descriptor_set_layouts[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, PipelineLayoutRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::pipelineLayoutRequestCount"); + + struct { + VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE}; + std::vector pipeline_layouts{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.pipelineLayoutRequestCount = tested_limit; + object_reservation_info.descriptorSetLayoutRequestCount = 1; + object_reservation_info.descriptorSetLayoutBindingRequestCount = 1; + object_reservation_info.descriptorSetLayoutBindingLimit = 1; + + data.pipeline_layouts.clear(); + data.pipeline_layouts.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreatePipelineLayout = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyPipelineLayout = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + VkDescriptorSetLayoutBinding binding{}; + binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.descriptorCount = 1; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + auto create_info = vku::InitStruct(); + create_info.bindingCount = 1; + create_info.pBindings = &binding; + + result = vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &data.descriptor_set_layout); + if (result != VK_SUCCESS) return false; + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.setLayoutCount = 1; + create_info.pSetLayouts = &data.descriptor_set_layout; + + if (should_fail) { + EXPECT_EQ(vksc::CreatePipelineLayout(device, &create_info, nullptr, &pipeline_layout), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.pipeline_layouts.size()); + EXPECT_EQ(vksc::CreatePipelineLayout(device, &create_info, nullptr, &data.pipeline_layouts[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.pipeline_layouts.size()); + assert(destroy_count == 1); + + vksc::DestroyPipelineLayout(device, data.pipeline_layouts[index], nullptr); + data.pipeline_layouts[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { vksc::DestroyDescriptorSetLayout(device, data.descriptor_set_layout, nullptr); }); +} + +TEST_F(ObjectReservationTest, DescriptorPoolRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::descriptorPoolRequestCount"); + + struct { + std::vector descriptor_pools{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = false; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.descriptorPoolRequestCount = tested_limit; + object_reservation_info.descriptorSetRequestCount = tested_limit + 1; + + data.descriptor_pools.clear(); + data.descriptor_pools.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateDescriptorPool = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyDescriptorPool = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; + + VkDescriptorPoolSize pool_size{}; + pool_size.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + pool_size.descriptorCount = 4; + + auto create_info = vku::InitStruct(); + create_info.maxSets = 1; + create_info.poolSizeCount = 1; + create_info.pPoolSizes = &pool_size; + + if (should_fail) { + EXPECT_EQ(vksc::CreateDescriptorPool(device, &create_info, nullptr, &descriptor_pool), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.descriptor_pools.size()); + EXPECT_EQ(vksc::CreateDescriptorPool(device, &create_info, nullptr, &data.descriptor_pools[index]), VK_SUCCESS); + } + }, + // Destroy objects + nullptr, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, DescriptorSetRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::descriptorSetRequestCount"); + + struct { + const uint32_t max_descriptor_sets_per_pool = 16; + VkDescriptorSetLayout descriptor_set_layout{}; + VkDescriptorPool descriptor_pool_to_use_on_fail{}; + std::vector descriptor_pools{}; + } data; + + const uint32_t max_create_count = data.max_descriptor_sets_per_pool; + const bool can_destroy = true; + const bool has_parent = true; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.descriptorPoolRequestCount = tested_limit + 1; + object_reservation_info.descriptorSetRequestCount = tested_limit; + object_reservation_info.descriptorSetLayoutRequestCount = 1; + object_reservation_info.descriptorSetLayoutBindingRequestCount = 1; + object_reservation_info.descriptorSetLayoutBindingLimit = 1; + + data.descriptor_set_layout = VK_NULL_HANDLE; + data.descriptor_pool_to_use_on_fail = VK_NULL_HANDLE; + data.descriptor_pools.clear(); + data.descriptor_pools.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + 1); + vkmock::CreateDescriptorPool = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyDescriptorPool = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + vkmock::ResetDescriptorPool = [&](auto, auto, auto) { return VK_SUCCESS; }; + vkmock::AllocateDescriptorSets = [&](auto, auto, auto) { return VK_SUCCESS; }; + vkmock::FreeDescriptorSets = [&](auto, auto, auto, auto) { return VK_SUCCESS; }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + VkDescriptorSetLayoutBinding binding{}; + binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.descriptorCount = 2; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + auto create_info = vku::InitStruct(); + create_info.bindingCount = 1; + create_info.pBindings = &binding; + + result = vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &data.descriptor_set_layout); + if (result != VK_SUCCESS) return false; + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + std::vector descriptor_sets(create_count, VK_NULL_HANDLE); + std::vector set_layouts(create_count, data.descriptor_set_layout); + + auto alloc_info = vku::InitStruct(); + alloc_info.descriptorSetCount = create_count; + alloc_info.pSetLayouts = set_layouts.data(); + + // Create descriptor pool first if needed + { + VkDescriptorPoolSize pool_size{}; + pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + pool_size.descriptorCount = 50; + + auto create_info = vku::InitStruct(); + create_info.maxSets = data.max_descriptor_sets_per_pool; + create_info.poolSizeCount = 1; + create_info.pPoolSizes = &pool_size; + + if (should_fail) { + if (data.descriptor_pool_to_use_on_fail == VK_NULL_HANDLE) { + vksc::CreateDescriptorPool(device, &create_info, nullptr, &data.descriptor_pool_to_use_on_fail); + } else { + vksc::ResetDescriptorPool(device, data.descriptor_pool_to_use_on_fail, 0); + } + alloc_info.descriptorPool = data.descriptor_pool_to_use_on_fail; + } else { + assert(index < data.descriptor_pools.size()); + if (data.descriptor_pools[index] == VK_NULL_HANDLE) { + vksc::CreateDescriptorPool(device, &create_info, nullptr, &data.descriptor_pools[index]); + } + alloc_info.descriptorPool = data.descriptor_pools[index]; + } + } + + if (alloc_info.descriptorPool != VK_NULL_HANDLE) { + if (should_fail) { + EXPECT_EQ(vksc::AllocateDescriptorSets(device, &alloc_info, descriptor_sets.data()), + VK_ERROR_VALIDATION_FAILED); + } else { + EXPECT_EQ(vksc::AllocateDescriptorSets(device, &alloc_info, descriptor_sets.data()), VK_SUCCESS); + } + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.descriptor_pools.size()); + + if (data.descriptor_pools[index] != VK_NULL_HANDLE) { + vksc::ResetDescriptorPool(device, data.descriptor_pools[index], 0); + } + }, + // Teardown common device objects + [&](VkDevice device) { vksc::DestroyDescriptorSetLayout(device, data.descriptor_set_layout, nullptr); }); +} + +TEST_F(ObjectReservationTest, DeviceMemoryRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::deviceMemoryRequestCount"); + + struct { + std::vector device_memories{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = false; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.deviceMemoryRequestCount = tested_limit; + + data.device_memories.clear(); + data.device_memories.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::AllocateMemory = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::FreeMemory = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkDeviceMemory device_memory = VK_NULL_HANDLE; + + auto alloc_info = vku::InitStruct(); + alloc_info.allocationSize = 1024; + alloc_info.memoryTypeIndex = 0; + + if (should_fail) { + EXPECT_EQ(vksc::AllocateMemory(device, &alloc_info, nullptr, &device_memory), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.device_memories.size()); + EXPECT_EQ(vksc::AllocateMemory(device, &alloc_info, nullptr, &data.device_memories[index]), VK_SUCCESS); + } + }, + // Destroy objects + nullptr, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, PipelineCacheRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::pipelineCacheRequestCount"); + + InitPipelineCaches(); + + struct { + std::vector pipeline_caches{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.pipelineCacheRequestCount = tested_limit; + + data.pipeline_caches.clear(); + data.pipeline_caches.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreatePipelineCache = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyPipelineCache = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkPipelineCache pipeline_cache = VK_NULL_HANDLE; + + auto create_info = GetPipelineCacheCreateInfo(); + + if (should_fail) { + EXPECT_EQ(vksc::CreatePipelineCache(device, &create_info, nullptr, &pipeline_cache), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.pipeline_caches.size()); + EXPECT_EQ(vksc::CreatePipelineCache(device, &create_info, nullptr, &data.pipeline_caches[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.pipeline_caches.size()); + assert(destroy_count == 1); + + vksc::DestroyPipelineCache(device, data.pipeline_caches[index], nullptr); + data.pipeline_caches[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, ComputePipelineRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::computePipelineRequestCount"); + + InitPipelineCaches(); + + struct { + VkPipelinePoolSize pipeline_pool_size{}; + VkPipelineLayout pipeline_layout{VK_NULL_HANDLE}; + VkPipelineCache pipeline_cache{VK_NULL_HANDLE}; + std::vector pipelines{}; + } data; + + const uint32_t max_create_count = 16; + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + data.pipeline_pool_size = GetPipelinePoolSize(); + data.pipeline_pool_size.poolEntryCount = 100 + tested_limit * 3; + + object_reservation_info.pipelinePoolSizeCount = 1; + object_reservation_info.pPipelinePoolSizes = &data.pipeline_pool_size; + + object_reservation_info.pipelineCacheRequestCount = 1; + object_reservation_info.pipelineLayoutRequestCount = 1; + object_reservation_info.computePipelineRequestCount = tested_limit; + + data.pipeline_layout = VK_NULL_HANDLE; + data.pipeline_cache = VK_NULL_HANDLE; + data.pipelines.clear(); + data.pipelines.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateComputePipelines = [&](auto, auto, auto count, auto, auto, auto pHandles) { + for (uint32_t i = 0; i < count; ++i) { + pHandles[i] = mock_objects.Alloc(); + } + return VK_SUCCESS; + }; + vkmock::DestroyPipeline = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + { + auto create_info = GetPipelineCacheCreateInfo(); + result = vksc::CreatePipelineCache(device, &create_info, nullptr, &data.pipeline_cache); + if (result != VK_SUCCESS) return false; + } + + { + auto create_info = vku::InitStruct(); + result = vksc::CreatePipelineLayout(device, &create_info, nullptr, &data.pipeline_layout); + if (result != VK_SUCCESS) return false; + } + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + std::vector pipelines(create_count, VK_NULL_HANDLE); + + auto offline_info = GetComputePipelineOfflineCreateInfo(); + std::vector create_info(create_count, + vku::InitStruct(&offline_info)); + for (uint32_t i = 0; i < create_count; ++i) { + create_info[i].stage = vku::InitStruct(); + create_info[i].stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + create_info[i].stage.pName = "main"; + create_info[i].layout = data.pipeline_layout; + } + + if (should_fail) { + EXPECT_EQ(vksc::CreateComputePipelines(device, data.pipeline_cache, create_count, create_info.data(), nullptr, + pipelines.data()), + VK_ERROR_VALIDATION_FAILED); + } else { + assert(index + create_count <= data.pipelines.size()); + EXPECT_EQ(vksc::CreateComputePipelines(device, data.pipeline_cache, create_count, create_info.data(), nullptr, + &data.pipelines[index]), + VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.pipelines.size()); + assert(destroy_count == 1); + + vksc::DestroyPipeline(device, data.pipelines[index], nullptr); + data.pipelines[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { + vksc::DestroyPipelineCache(device, data.pipeline_cache, nullptr); + vksc::DestroyPipelineLayout(device, data.pipeline_layout, nullptr); + }); +} + +TEST_F(ObjectReservationTest, GraphicsPipelineRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::graphicsPipelineRequestCount"); + + InitPipelineCaches(); + + struct { + VkPipelinePoolSize pipeline_pool_size{}; + VkRenderPass render_pass{VK_NULL_HANDLE}; + VkPipelineLayout pipeline_layout{VK_NULL_HANDLE}; + VkPipelineCache pipeline_cache{VK_NULL_HANDLE}; + std::vector pipelines{}; + } data; + + const uint32_t max_create_count = 16; + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + data.pipeline_pool_size = GetPipelinePoolSize(); + data.pipeline_pool_size.poolEntryCount = 100 + tested_limit * 3; + + object_reservation_info.pipelinePoolSizeCount = 1; + object_reservation_info.pPipelinePoolSizes = &data.pipeline_pool_size; + + object_reservation_info.renderPassRequestCount = 1; + object_reservation_info.subpassDescriptionRequestCount = 1; + object_reservation_info.pipelineCacheRequestCount = 1; + object_reservation_info.pipelineLayoutRequestCount = 1; + object_reservation_info.graphicsPipelineRequestCount = tested_limit; + + data.render_pass = VK_NULL_HANDLE; + data.pipeline_layout = VK_NULL_HANDLE; + data.pipeline_cache = VK_NULL_HANDLE; + data.pipelines.clear(); + data.pipelines.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateGraphicsPipelines = [&](auto, auto, auto count, auto, auto, auto pHandles) { + for (uint32_t i = 0; i < count; ++i) { + pHandles[i] = mock_objects.Alloc(); + } + return VK_SUCCESS; + }; + vkmock::DestroyPipeline = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + { + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + result = vksc::CreateRenderPass(device, &create_info, nullptr, &data.render_pass); + if (result != VK_SUCCESS) return false; + } + + { + auto create_info = GetPipelineCacheCreateInfo(); + result = vksc::CreatePipelineCache(device, &create_info, nullptr, &data.pipeline_cache); + if (result != VK_SUCCESS) return false; + } + + { + auto create_info = vku::InitStruct(); + result = vksc::CreatePipelineLayout(device, &create_info, nullptr, &data.pipeline_layout); + if (result != VK_SUCCESS) return false; + } + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + std::vector pipelines(create_count, VK_NULL_HANDLE); + + auto stage_info = vku::InitStruct(); + stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + stage_info.pName = "main"; + + auto vi_state = vku::InitStruct(); + auto ia_state = vku::InitStruct(); + auto rs_state = vku::InitStruct(); + rs_state.rasterizerDiscardEnable = VK_TRUE; + rs_state.lineWidth = 1.f; + + auto offline_info = GetGraphicsPipelineOfflineCreateInfo(); + std::vector create_info(create_count, + vku::InitStruct(&offline_info)); + for (uint32_t i = 0; i < create_count; ++i) { + create_info[i].stageCount = 1; + create_info[i].pStages = &stage_info; + create_info[i].pVertexInputState = &vi_state; + create_info[i].pInputAssemblyState = &ia_state; + create_info[i].pRasterizationState = &rs_state; + create_info[i].layout = data.pipeline_layout; + create_info[i].renderPass = data.render_pass; + } + + if (should_fail) { + EXPECT_EQ(vksc::CreateGraphicsPipelines(device, data.pipeline_cache, create_count, create_info.data(), nullptr, + pipelines.data()), + VK_ERROR_VALIDATION_FAILED); + } else { + assert(index + create_count <= data.pipelines.size()); + EXPECT_EQ(vksc::CreateGraphicsPipelines(device, data.pipeline_cache, create_count, create_info.data(), nullptr, + &data.pipelines[index]), + VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.pipelines.size()); + assert(destroy_count == 1); + + vksc::DestroyPipeline(device, data.pipelines[index], nullptr); + data.pipelines[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { + vksc::DestroyRenderPass(device, data.render_pass, nullptr); + vksc::DestroyPipelineCache(device, data.pipeline_cache, nullptr); + vksc::DestroyPipelineLayout(device, data.pipeline_layout, nullptr); + }); +} + +TEST_F(ObjectReservationTest, QueryPoolRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::queryPoolRequestCount"); + + struct { + std::vector query_pools{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = false; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.queryPoolRequestCount = tested_limit; + object_reservation_info.maxOcclusionQueriesPerPool = 8; + + data.query_pools.clear(); + data.query_pools.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateQueryPool = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyQueryPool = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkQueryPool query_pool = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.queryType = VK_QUERY_TYPE_OCCLUSION; + create_info.queryCount = 8; + + if (should_fail) { + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.query_pools.size()); + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &data.query_pools[index]), VK_SUCCESS); + } + }, + // Destroy objects + nullptr, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, RenderPassRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::renderPassRequestCount"); + + struct { + bool use_create_render_pass2{}; + std::vector render_passes{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.renderPassRequestCount = tested_limit; + object_reservation_info.subpassDescriptionRequestCount = tested_limit; + + data.render_passes.clear(); + data.render_passes.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateRenderPass = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateRenderPass2 = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyRenderPass = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkRenderPass render_pass = VK_NULL_HANDLE; + + // Use CreateRenderPass2 for every second create call + data.use_create_render_pass2 = !data.use_create_render_pass2; + if (data.use_create_render_pass2) { + auto subpass = vku::InitStruct(); + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } else { + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.render_passes.size()); + assert(destroy_count == 1); + + vksc::DestroyRenderPass(device, data.render_passes[index], nullptr); + data.render_passes[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, SubpassDescriptionRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::subpassDescriptionRequestCount"); + + struct { + bool use_create_render_pass2{}; + std::vector render_passes{}; + } data; + + const uint32_t max_create_count = std::min(GetVulkanSC10Properties().maxRenderPassSubpasses, 4u); + const bool can_destroy = true; + const bool has_parent = true; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.renderPassRequestCount = tested_limit + 1; + object_reservation_info.subpassDescriptionRequestCount = tested_limit; + + data.render_passes.clear(); + data.render_passes.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + 1); + vkmock::CreateRenderPass = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateRenderPass2 = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyRenderPass = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkRenderPass render_pass = VK_NULL_HANDLE; + + // Use CreateRenderPass2 for every second create call + data.use_create_render_pass2 = !data.use_create_render_pass2; + if (data.use_create_render_pass2) { + std::vector subpasses(create_count, vku::InitStruct()); + for (auto& subpass : subpasses) { + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + } + + auto create_info = vku::InitStruct(); + create_info.subpassCount = create_count; + create_info.pSubpasses = subpasses.data(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } else { + std::vector subpasses(create_count, VkSubpassDescription{}); + for (auto& subpass : subpasses) { + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + } + + auto create_info = vku::InitStruct(); + create_info.subpassCount = create_count; + create_info.pSubpasses = subpasses.data(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.render_passes.size()); + assert(destroy_count == 0); + + vksc::DestroyRenderPass(device, data.render_passes[index], nullptr); + data.render_passes[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, AttachmentDescriptionRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::attachmentDescriptionRequestCount"); + + struct { + bool use_create_render_pass2{}; + std::vector render_passes{}; + } data; + + const uint32_t max_create_count = 4; + const bool can_destroy = true; + const bool has_parent = true; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.renderPassRequestCount = tested_limit + 1; + object_reservation_info.subpassDescriptionRequestCount = tested_limit + 1; + object_reservation_info.attachmentDescriptionRequestCount = tested_limit; + + data.render_passes.clear(); + data.render_passes.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + 1); + vkmock::CreateRenderPass = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::CreateRenderPass2 = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyRenderPass = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkRenderPass render_pass = VK_NULL_HANDLE; + + // Use CreateRenderPass2 for every second create call + data.use_create_render_pass2 = !data.use_create_render_pass2; + if (data.use_create_render_pass2) { + std::vector attachments(create_count, vku::InitStruct()); + for (auto& attachment : attachments) { + attachment.format = VK_FORMAT_R8G8B8A8_UNORM; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + + auto subpass = vku::InitStruct(); + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.attachmentCount = create_count; + create_info.pAttachments = attachments.data(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass2(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } else { + std::vector attachments(create_count, VkAttachmentDescription{}); + for (auto& attachment : attachments) { + attachment.format = VK_FORMAT_R8G8B8A8_UNORM; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.attachmentCount = create_count; + create_info.pAttachments = attachments.data(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + if (should_fail) { + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &render_pass), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.render_passes.size()); + EXPECT_EQ(vksc::CreateRenderPass(device, &create_info, nullptr, &data.render_passes[index]), VK_SUCCESS); + } + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.render_passes.size()); + assert(destroy_count == 0); + + vksc::DestroyRenderPass(device, data.render_passes[index], nullptr); + data.render_passes[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, FramebufferRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::framebufferRequestCount"); + + struct { + VkRenderPass render_pass{VK_NULL_HANDLE}; + std::vector framebuffers{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.renderPassRequestCount = 1; + object_reservation_info.subpassDescriptionRequestCount = 1; + object_reservation_info.framebufferRequestCount = tested_limit; + + data.render_pass = VK_NULL_HANDLE; + data.framebuffers.clear(); + data.framebuffers.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateFramebuffer = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyFramebuffer = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + auto create_info = vku::InitStruct(); + create_info.subpassCount = 1; + create_info.pSubpasses = &subpass; + + result = vksc::CreateRenderPass(device, &create_info, nullptr, &data.render_pass); + if (result != VK_SUCCESS) return false; + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkFramebuffer framebuffer = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.renderPass = data.render_pass; + create_info.width = 128; + create_info.height = 128; + create_info.layers = 1; + + if (should_fail) { + EXPECT_EQ(vksc::CreateFramebuffer(device, &create_info, nullptr, &framebuffer), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.framebuffers.size()); + EXPECT_EQ(vksc::CreateFramebuffer(device, &create_info, nullptr, &data.framebuffers[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.framebuffers.size()); + assert(destroy_count == 1); + + vksc::DestroyFramebuffer(device, data.framebuffers[index], nullptr); + data.framebuffers[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { vksc::DestroyRenderPass(device, data.render_pass, nullptr); }); +} + +TEST_F(ObjectReservationTest, BufferRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::bufferRequestCount"); + + struct { + std::vector buffers{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.bufferRequestCount = tested_limit; + + data.buffers.clear(); + data.buffers.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateBuffer = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyBuffer = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkBuffer buffer = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.size = 1024; + create_info.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; + + if (should_fail) { + EXPECT_EQ(vksc::CreateBuffer(device, &create_info, nullptr, &buffer), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.buffers.size()); + EXPECT_EQ(vksc::CreateBuffer(device, &create_info, nullptr, &data.buffers[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.buffers.size()); + assert(destroy_count == 1); + + vksc::DestroyBuffer(device, data.buffers[index], nullptr); + data.buffers[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, BufferViewRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::bufferViewRequestCount"); + + struct { + VkBuffer buffer{}; + std::vector buffer_views{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.deviceMemoryRequestCount = 1; + object_reservation_info.bufferRequestCount = 1; + object_reservation_info.bufferViewRequestCount = tested_limit; + + data.buffer_views.clear(); + data.buffer_views.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateBufferView = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyBufferView = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + auto create_info = vku::InitStruct(); + create_info.size = 1024; + create_info.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; + + VkResult result = vksc::CreateBuffer(device, &create_info, nullptr, &data.buffer); + if (result != VK_SUCCESS) return false; + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkBufferView buffer_view = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.buffer = data.buffer; + create_info.format = VK_FORMAT_R32_UINT; + create_info.range = 256; + + if (should_fail) { + EXPECT_EQ(vksc::CreateBufferView(device, &create_info, nullptr, &buffer_view), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.buffer_views.size()); + EXPECT_EQ(vksc::CreateBufferView(device, &create_info, nullptr, &data.buffer_views[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.buffer_views.size()); + assert(destroy_count == 1); + + vksc::DestroyBufferView(device, data.buffer_views[index], nullptr); + data.buffer_views[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { + vksc::DestroyBuffer(device, data.buffer, nullptr); + data.buffer = VK_NULL_HANDLE; + }); +} + +TEST_F(ObjectReservationTest, ImageRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::imageRequestCount"); + + struct { + std::vector images{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.imageRequestCount = tested_limit; + + data.images.clear(); + data.images.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateImage = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyImage = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkImage image = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.imageType = VK_IMAGE_TYPE_2D; + create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + create_info.extent = {16, 16, 1}; + create_info.mipLevels = 1; + create_info.arrayLayers = 1; + create_info.samples = VK_SAMPLE_COUNT_1_BIT; + create_info.tiling = VK_IMAGE_TILING_OPTIMAL; + create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + if (should_fail) { + EXPECT_EQ(vksc::CreateImage(device, &create_info, nullptr, &image), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.images.size()); + EXPECT_EQ(vksc::CreateImage(device, &create_info, nullptr, &data.images[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.images.size()); + assert(destroy_count == 1); + + vksc::DestroyImage(device, data.images[index], nullptr); + data.images[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, ImageViewRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::imageViewRequestCount"); + + struct { + VkImage image{}; + std::vector image_views{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.deviceMemoryRequestCount = 1; + object_reservation_info.imageRequestCount = 1; + object_reservation_info.imageViewRequestCount = tested_limit; + object_reservation_info.maxImageViewMipLevels = 1; + object_reservation_info.maxImageViewArrayLayers = 1; + + data.image_views.clear(); + data.image_views.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateImageView = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyImageView = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + auto create_info = vku::InitStruct(); + create_info.imageType = VK_IMAGE_TYPE_2D; + create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + create_info.extent = {16, 16, 1}; + create_info.mipLevels = 1; + create_info.arrayLayers = 1; + create_info.samples = VK_SAMPLE_COUNT_1_BIT; + create_info.tiling = VK_IMAGE_TILING_OPTIMAL; + create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + VkResult result = vksc::CreateImage(device, &create_info, nullptr, &data.image); + if (result != VK_SUCCESS) return false; + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkImageView image_view = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.image = data.image; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + create_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + if (should_fail) { + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.image_views.size()); + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &data.image_views[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.image_views.size()); + assert(destroy_count == 1); + + vksc::DestroyImageView(device, data.image_views[index], nullptr); + data.image_views[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { + vksc::DestroyImage(device, data.image, nullptr); + data.image = VK_NULL_HANDLE; + }); +} + +TEST_F(ObjectReservationTest, LayeredImageViewRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::layeredImageViewRequestCount"); + + struct { + const uint32_t layer_count = 32; + VkImage image{}; + std::vector non_layered_image_views{}; + std::vector layered_image_views{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.deviceMemoryRequestCount = 1; + object_reservation_info.imageRequestCount = 1; + object_reservation_info.imageViewRequestCount = tested_limit + data.layer_count + 1; + object_reservation_info.layeredImageViewRequestCount = tested_limit; + object_reservation_info.maxImageViewMipLevels = 1; + object_reservation_info.maxImageViewArrayLayers = data.layer_count; + object_reservation_info.maxLayeredImageViewMipLevels = 1; + + data.non_layered_image_views.clear(); + data.non_layered_image_views.resize(data.layer_count, VK_NULL_HANDLE); + data.layered_image_views.clear(); + data.layered_image_views.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit + data.layer_count + 1); + vkmock::CreateImageView = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyImageView = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + [&](VkDevice device) { + VkResult result = VK_SUCCESS; + + auto create_info = vku::InitStruct(); + create_info.imageType = VK_IMAGE_TYPE_2D; + create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + create_info.extent = {16, 16, 1}; + create_info.mipLevels = 1; + create_info.arrayLayers = data.layer_count; + create_info.samples = VK_SAMPLE_COUNT_1_BIT; + create_info.tiling = VK_IMAGE_TILING_OPTIMAL; + create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + result = vksc::CreateImage(device, &create_info, nullptr, &data.image); + if (result != VK_SUCCESS) return false; + + for (uint32_t i = 0; i < data.layer_count; ++i) { + auto view_create_info = vku::InitStruct(); + view_create_info.image = data.image; + view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + view_create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + view_create_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, i, 1}; + + result = vksc::CreateImageView(device, &view_create_info, nullptr, &data.non_layered_image_views[i]); + if (result != VK_SUCCESS) return false; + } + + return true; + }, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkImageView image_view = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.image = data.image; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + create_info.format = VK_FORMAT_R8G8B8A8_UNORM; + create_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1}; + create_info.subresourceRange.baseArrayLayer = index % (data.layer_count / 2); + create_info.subresourceRange.layerCount = (index % 2 == 0) ? data.layer_count / 2 : VK_REMAINING_ARRAY_LAYERS; + + assert(create_info.subresourceRange.layerCount > 1); + + if (should_fail) { + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.layered_image_views.size()); + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &data.layered_image_views[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.layered_image_views.size()); + assert(destroy_count == 1); + + vksc::DestroyImageView(device, data.layered_image_views[index], nullptr); + data.layered_image_views[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + [&](VkDevice device) { + for (uint32_t i = 0; i < data.layer_count; ++i) { + vksc::DestroyImageView(device, data.non_layered_image_views[i], nullptr); + } + vksc::DestroyImage(device, data.image, nullptr); + data.image = VK_NULL_HANDLE; + }); +} + +TEST_F(ObjectReservationTest, SamplerRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::samplerRequestCount"); + + struct { + std::vector samplers{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.samplerRequestCount = tested_limit; + + data.samplers.clear(); + data.samplers.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateSampler = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroySampler = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkSampler sampler = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.magFilter = VK_FILTER_LINEAR; + create_info.minFilter = VK_FILTER_LINEAR; + + if (should_fail) { + EXPECT_EQ(vksc::CreateSampler(device, &create_info, nullptr, &sampler), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.samplers.size()); + EXPECT_EQ(vksc::CreateSampler(device, &create_info, nullptr, &data.samplers[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.samplers.size()); + assert(destroy_count == 1); + + vksc::DestroySampler(device, data.samplers[index], nullptr); + data.samplers[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, SamplerYcbcrConversionRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::samplerYcbcrConversionRequestCount"); + + struct { + VkFormat ycbcr_format = VK_FORMAT_UNDEFINED; + std::vector sampler_ycbcr_conversions{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.samplerYcbcrConversionRequestCount = tested_limit; + + data.sampler_ycbcr_conversions.clear(); + data.sampler_ycbcr_conversions.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateSamplerYcbcrConversion = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroySamplerYcbcrConversion = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkSamplerYcbcrConversion sampler_ycbcr_conversion = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.format = data.ycbcr_format; + + if (should_fail) { + EXPECT_EQ(vksc::CreateSamplerYcbcrConversion(device, &create_info, nullptr, &sampler_ycbcr_conversion), + VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.sampler_ycbcr_conversions.size()); + EXPECT_EQ(vksc::CreateSamplerYcbcrConversion(device, &create_info, nullptr, &data.sampler_ycbcr_conversions[index]), + VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.sampler_ycbcr_conversions.size()); + assert(destroy_count == 1); + + vksc::DestroySamplerYcbcrConversion(device, data.sampler_ycbcr_conversions[index], nullptr); + data.sampler_ycbcr_conversions[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, FenceRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::fenceRequestCount"); + + struct { + std::vector fences{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.fenceRequestCount = tested_limit; + + data.fences.clear(); + data.fences.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateFence = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyFence = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkFence fence = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateFence(device, &create_info, nullptr, &fence), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.fences.size()); + EXPECT_EQ(vksc::CreateFence(device, &create_info, nullptr, &data.fences[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.fences.size()); + assert(destroy_count == 1); + + vksc::DestroyFence(device, data.fences[index], nullptr); + data.fences[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, SemaphoreRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::semaphoreRequestCount"); + + struct { + std::vector semaphores{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.semaphoreRequestCount = tested_limit; + + data.semaphores.clear(); + data.semaphores.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateSemaphore = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroySemaphore = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkSemaphore semaphore = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateSemaphore(device, &create_info, nullptr, &semaphore), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.semaphores.size()); + EXPECT_EQ(vksc::CreateSemaphore(device, &create_info, nullptr, &data.semaphores[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.semaphores.size()); + assert(destroy_count == 1); + + vksc::DestroySemaphore(device, data.semaphores[index], nullptr); + data.semaphores[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, EventRequestCount) { + TEST_DESCRIPTION("Test VkDeviceObjectReservationCreateInfo::eventRequestCount"); + + struct { + std::vector events{}; + } data; + + const uint32_t max_create_count = 0; // Not a multi-create API + const bool can_destroy = true; + const bool has_parent = false; + + TestObjectReservationLimit( + max_create_count, can_destroy, has_parent, + // Init object reservation info + [&](VkDeviceObjectReservationCreateInfo& object_reservation_info, uint32_t tested_limit) { + object_reservation_info.eventRequestCount = tested_limit; + + data.events.clear(); + data.events.resize(tested_limit, VK_NULL_HANDLE); + + static VkMockObjects mock_objects{}; + mock_objects.Reset(tested_limit); + vkmock::CreateEvent = [&](auto, auto, auto, auto pHandle) { + *pHandle = mock_objects.Alloc(); + return VK_SUCCESS; + }; + vkmock::DestroyEvent = [&](auto, auto handle, auto) { mock_objects.Free(handle); }; + + return true; + }, + // Setup common device objects + nullptr, + // Create objects + [&](VkDevice device, uint32_t index, uint32_t create_count, bool should_fail) { + VkEvent event = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + + if (should_fail) { + EXPECT_EQ(vksc::CreateEvent(device, &create_info, nullptr, &event), VK_ERROR_VALIDATION_FAILED); + } else { + assert(index < data.events.size()); + EXPECT_EQ(vksc::CreateEvent(device, &create_info, nullptr, &data.events[index]), VK_SUCCESS); + } + }, + // Destroy objects + [&](VkDevice device, uint32_t index, uint32_t destroy_count) { + assert(index < data.events.size()); + assert(destroy_count == 1); + + vksc::DestroyEvent(device, data.events[index], nullptr); + data.events[index] = VK_NULL_HANDLE; + }, + // Teardown common device objects + nullptr); +} + +TEST_F(ObjectReservationTest, DescriptorSetLayoutBindingLimit) { + TEST_DESCRIPTION("vkCreateDescriptorSetLayout - descriptor binding index must be below descriptorSetLayoutBindingLimit"); + + auto object_reservation_info1 = vku::InitStruct(); + auto object_reservation_info2 = vku::InitStruct(&object_reservation_info1); + auto object_reservation_info3 = vku::InitStruct(&object_reservation_info2); + + object_reservation_info1.descriptorSetLayoutRequestCount = 2; + + object_reservation_info1.descriptorSetLayoutBindingRequestCount = 20; + + object_reservation_info1.descriptorSetLayoutBindingLimit = 1; + object_reservation_info2.descriptorSetLayoutBindingLimit = 4; + object_reservation_info3.descriptorSetLayoutBindingLimit = 3; + + auto device = InitDeviceWithCustomObjectReservation(&object_reservation_info3); + + VkDescriptorSetLayoutBinding bindings[] = { + {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + }; + + auto create_info = vku::InitStruct(); + create_info.bindingCount = sizeof(bindings) / sizeof(bindings[0]); + create_info.pBindings = &bindings[0]; + + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + + // Should fail with all bindings + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &layout), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyDescriptorSetLayout(device, layout, nullptr); + layout = VK_NULL_HANDLE; + + // Should succeed if we exclude the first two and the last bindings + create_info.bindingCount -= 3; + create_info.pBindings += 2; + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &layout), VK_SUCCESS); + vksc::DestroyDescriptorSetLayout(device, layout, nullptr); + layout = VK_NULL_HANDLE; +} + +TEST_F(ObjectReservationTest, MaxImageViewMipLevels) { + TEST_DESCRIPTION("vkCreateImageView - levelCount cannot exceed max[Layered]ImageViewMipLevels"); + + auto object_reservation_info1 = vku::InitStruct(); + auto object_reservation_info2 = vku::InitStruct(&object_reservation_info1); + auto object_reservation_info3 = vku::InitStruct(&object_reservation_info2); + + object_reservation_info1.imageRequestCount = 1; + object_reservation_info1.imageViewRequestCount = 2; + object_reservation_info1.layeredImageViewRequestCount = 2; + + object_reservation_info1.maxImageViewArrayLayers = 4; + + object_reservation_info1.maxImageViewMipLevels = 1; + object_reservation_info2.maxImageViewMipLevels = 3; + object_reservation_info3.maxImageViewMipLevels = 2; + + object_reservation_info1.maxLayeredImageViewMipLevels = 2; + object_reservation_info2.maxLayeredImageViewMipLevels = 0; + object_reservation_info3.maxLayeredImageViewMipLevels = 1; + + auto device = InitDeviceWithCustomObjectReservation(&object_reservation_info3); + + auto image_info = vku::InitStruct(); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.extent = {128, 128, 1}; + image_info.mipLevels = 6; + image_info.arrayLayers = 4; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + + VkImage image = VK_NULL_HANDLE; + ASSERT_EQ(vksc::CreateImage(device, &image_info, nullptr, &image), VK_SUCCESS); + + auto create_info = vku::InitStruct(); + create_info.image = image; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + create_info.format = image_info.format; + create_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 2, 3, 2, 1}; + + VkImageView image_view = VK_NULL_HANDLE; + + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_SUCCESS); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.levelCount++; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.levelCount--; + create_info.subresourceRange.layerCount++; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.layerCount--; + create_info.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.baseMipLevel = 4; + create_info.subresourceRange.baseArrayLayer = 3; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_SUCCESS); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; +} + +TEST_F(ObjectReservationTest, MaxImageViewArrayLayers) { + TEST_DESCRIPTION("vkCreateImageView - levelCount cannot exceed maxImageViewArrayLayers"); + + auto object_reservation_info1 = vku::InitStruct(); + auto object_reservation_info2 = vku::InitStruct(&object_reservation_info1); + auto object_reservation_info3 = vku::InitStruct(&object_reservation_info2); + + object_reservation_info1.imageRequestCount = 1; + object_reservation_info1.imageViewRequestCount = 2; + object_reservation_info1.layeredImageViewRequestCount = 2; + + object_reservation_info1.maxImageViewMipLevels = 6; + object_reservation_info1.maxLayeredImageViewMipLevels = 6; + + object_reservation_info1.maxImageViewArrayLayers = 3; + object_reservation_info2.maxImageViewArrayLayers = 1; + object_reservation_info3.maxImageViewArrayLayers = 5; + + auto device = InitDeviceWithCustomObjectReservation(&object_reservation_info3); + + auto image_info = vku::InitStruct(); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.extent = {128, 128, 1}; + image_info.mipLevels = 6; + image_info.arrayLayers = 8; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + + VkImage image = VK_NULL_HANDLE; + ASSERT_EQ(vksc::CreateImage(device, &image_info, nullptr, &image), VK_SUCCESS); + + auto create_info = vku::InitStruct(); + create_info.image = image; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + create_info.format = image_info.format; + create_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 1, 3, 2, 5}; + + VkImageView image_view = VK_NULL_HANDLE; + + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_SUCCESS); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.layerCount++; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; + + create_info.subresourceRange.baseArrayLayer = 5; + EXPECT_EQ(vksc::CreateImageView(device, &create_info, nullptr, &image_view), VK_SUCCESS); + vksc::DestroyImageView(device, image_view, nullptr); + image_view = VK_NULL_HANDLE; +} + +TEST_F(ObjectReservationTest, MaxQueriesPerPool) { + TEST_DESCRIPTION("vkCreateQueryPool - queryCount cannot exceed max*QueriesPerPool for the corresponding query type"); + + auto object_reservation_info1 = vku::InitStruct(); + auto object_reservation_info2 = vku::InitStruct(&object_reservation_info1); + auto object_reservation_info3 = vku::InitStruct(&object_reservation_info2); + + object_reservation_info1.queryPoolRequestCount = 9; + + object_reservation_info1.maxOcclusionQueriesPerPool = 16; + object_reservation_info2.maxOcclusionQueriesPerPool = 0; + object_reservation_info3.maxOcclusionQueriesPerPool = 4; + + object_reservation_info1.maxPipelineStatisticsQueriesPerPool = 6; + object_reservation_info2.maxPipelineStatisticsQueriesPerPool = 8; + object_reservation_info3.maxPipelineStatisticsQueriesPerPool = 2; + + object_reservation_info1.maxTimestampQueriesPerPool = 0; + object_reservation_info2.maxTimestampQueriesPerPool = 10; + object_reservation_info3.maxTimestampQueriesPerPool = 20; + + auto perf_query_reservation_info1 = vku::InitStruct(&object_reservation_info3); + auto perf_query_reservation_info2 = vku::InitStruct(&perf_query_reservation_info1); + + perf_query_reservation_info1.maxPerformanceQueriesPerPool = 1; + perf_query_reservation_info2.maxPerformanceQueriesPerPool = 3; + + auto device = InitDeviceWithCustomObjectReservation(&perf_query_reservation_info2); + + // Test occlusion queries + { + VkQueryPool query_pool = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.queryType = VK_QUERY_TYPE_OCCLUSION; + create_info.queryCount = 16; + + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_SUCCESS); + + create_info.queryCount++; + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_ERROR_VALIDATION_FAILED); + } + + // Test pipeline statistics queries + { + VkQueryPool query_pool = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS; + create_info.queryCount = 8; + create_info.pipelineStatistics = VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT; + + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_SUCCESS); + + create_info.queryCount++; + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_ERROR_VALIDATION_FAILED); + } + + // Test timestamp queries + { + VkQueryPool query_pool = VK_NULL_HANDLE; + + auto create_info = vku::InitStruct(); + create_info.queryType = VK_QUERY_TYPE_TIMESTAMP; + create_info.queryCount = 20; + + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_SUCCESS); + + create_info.queryCount++; + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_ERROR_VALIDATION_FAILED); + } + + // Test performance queries + { + VkQueryPool query_pool = VK_NULL_HANDLE; + + uint32_t perf_counter_index = 0; + auto perf_info = vku::InitStruct(); + perf_info.counterIndexCount = 1; + perf_info.pCounterIndices = &perf_counter_index; + + auto create_info = vku::InitStruct(&perf_info); + create_info.queryType = VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR; + create_info.queryCount = 3; + + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_SUCCESS); + + create_info.queryCount++; + EXPECT_EQ(vksc::CreateQueryPool(device, &create_info, nullptr, &query_pool), VK_ERROR_VALIDATION_FAILED); + } +} + +TEST_F(ObjectReservationTest, MaxImmutableSamplersPerDescriptorSetLayout) { + TEST_DESCRIPTION( + "vkCreateDescriptorSetLayout - immutable sampler limit cannot exceed maxImmutableSamplersPerDescriptorSetLayout"); + + auto object_reservation_info1 = vku::InitStruct(); + auto object_reservation_info2 = vku::InitStruct(&object_reservation_info1); + auto object_reservation_info3 = vku::InitStruct(&object_reservation_info2); + + object_reservation_info1.samplerRequestCount = 1; + object_reservation_info1.descriptorSetLayoutRequestCount = 2; + + object_reservation_info1.descriptorSetLayoutBindingRequestCount = 20; + object_reservation_info1.descriptorSetLayoutBindingLimit = 10; + + object_reservation_info1.maxImmutableSamplersPerDescriptorSetLayout = 0; + object_reservation_info2.maxImmutableSamplersPerDescriptorSetLayout = 7; + object_reservation_info3.maxImmutableSamplersPerDescriptorSetLayout = 5; + + auto device = InitDeviceWithCustomObjectReservation(&object_reservation_info3); + + auto sampler_ci = vku::InitStruct(); + VkSampler sampler = VK_NULL_HANDLE; + ASSERT_EQ(vksc::CreateSampler(device, &sampler_ci, nullptr, &sampler), VK_SUCCESS); + + std::vector samplers(10, sampler); + + VkDescriptorSetLayoutBinding bindings[] = { + {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + {1, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + {2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + {3, VK_DESCRIPTOR_TYPE_SAMPLER, 3, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + // The bindings below will cause the limit to be exceeded + {5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + {6, VK_DESCRIPTOR_TYPE_SAMPLER, 2, VK_SHADER_STAGE_FRAGMENT_BIT, samplers.data()}, + }; + + auto create_info = vku::InitStruct(); + create_info.bindingCount = sizeof(bindings) / sizeof(bindings[0]); + create_info.pBindings = &bindings[0]; + + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + + // Should fail with all bindings + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &layout), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyDescriptorSetLayout(device, layout, nullptr); + layout = VK_NULL_HANDLE; + + // Should still fail if we exclude the last binding + create_info.bindingCount--; + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &layout), VK_ERROR_VALIDATION_FAILED); + vksc::DestroyDescriptorSetLayout(device, layout, nullptr); + layout = VK_NULL_HANDLE; + + // Should succeed if we exclude the last two bindings + create_info.bindingCount--; + EXPECT_EQ(vksc::CreateDescriptorSetLayout(device, &create_info, nullptr, &layout), VK_SUCCESS); + vksc::DestroyDescriptorSetLayout(device, layout, nullptr); + layout = VK_NULL_HANDLE; +}