| /* Copyright (c) 2015-2026 The Khronos Group Inc. |
| * Copyright (c) 2015-2026 Valve Corporation |
| * Copyright (c) 2015-2026 LunarG, Inc. |
| * Copyright (C) 2015-2026 Google Inc. |
| * Copyright (c) 2025 Arm Limited. |
| * Modifications Copyright (C) 2020,2025-2026 Advanced Micro Devices, Inc. All rights reserved. |
| * Modifications Copyright (C) 2022 RasterGrid Kft. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "state_tracker/cmd_buffer_state.h" |
| #include <vulkan/vulkan_core.h> |
| #include <vulkan/utility/vk_format_utils.h> |
| #include "error_message/error_location.h" |
| #include "generated/command_validation.h" |
| #include "state_tracker/descriptor_mode.h" |
| #include "state_tracker/descriptor_sets.h" |
| #include "state_tracker/last_bound_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/image_state.h" |
| #include "state_tracker/queue_state.h" |
| #include "state_tracker/vertex_index_buffer_state.h" |
| #include "utils/assert_utils.h" |
| #include "utils/image_utils.h" |
| #include "containers/container_utils.h" |
| |
| // Submit time validation helper. Since CmdBeginRendering is itself an action command, |
| // if the first detected action or sync command is CmdBeginRendering, it means there are |
| // no other action commands before it. |
| bool HasActionOrSyncCommandBeforeBeginRendering(vvl::Func first_action_or_sync_command) { |
| return first_action_or_sync_command != vvl::Func::Empty && |
| !IsValueIn(first_action_or_sync_command, {vvl::Func::vkCmdBeginRendering, vvl::Func::vkCmdBeginRenderingKHR}); |
| } |
| |
| using RangeGenerator = subresource_adapter::RangeGenerator; |
| |
| // Dynamic Rendering we know it is depth only, but for VkRenderPass, we need to check incase it is a stencil only attachment |
| bool AttachmentInfo::IsDepth() const { |
| return type == Type::Depth || |
| (type == Type::DepthStencil && image_view && vkuFormatHasDepth(image_view->image_state->create_info.format)); |
| } |
| |
| bool AttachmentInfo::IsStencil() const { |
| return type == Type::Stencil || |
| (type == Type::DepthStencil && image_view && vkuFormatHasStencil(image_view->image_state->create_info.format)); |
| } |
| |
| // For Traditional RenderPasses, the index is simply the index into the VkRenderPassCreateInfo::pAttachments, |
| // but for dynamic rendering, there is no "standard" way to map the index, instead we have our own custom indexing and it is not |
| // obvious at all to the user where it came from |
| std::string AttachmentInfo::Describe(const vvl::CommandBuffer &cb_state, uint32_t rp_index) const { |
| std::ostringstream ss; |
| if (cb_state.attachment_source == AttachmentSource::DynamicRendering) { |
| ss << "VkRenderingInfo::"; |
| if (type == Type::Color) { |
| ss << "pColorAttachments[" << rp_index << "].imageView"; |
| } else if (type == Type::ColorResolve) { |
| // This assumes the caller calculated the correct index with GetDynamicRenderingColorResolveAttachmentIndex |
| ss << "pColorAttachments[" << rp_index << "].resolveImageView"; |
| } else if (type == Type::Depth) { |
| ss << "pDepthAttachment->imageView"; |
| } else if (type == Type::DepthResolve) { |
| ss << "pStencilAttachment->resolveImageView"; |
| } else if (type == Type::Stencil) { |
| ss << "pStencilAttachment->imageView"; |
| } else if (type == Type::StencilResolve) { |
| ss << "pStencilAttachment->resolveImageView"; |
| } else if (type == Type::FragmentDensityMap) { |
| ss << "pNext<VkRenderingFragmentDensityMapAttachmentInfoEXT>.imageView"; |
| } else if (type == Type::FragmentShadingRate) { |
| ss << "pNext<VkRenderingFragmentShadingRateAttachmentInfoKHR>.imageView"; |
| } |
| } else { |
| // if the user has a [color, depth, color] the last color would have |
| // rp_index == 2 |
| // index == 1 |
| ss << "VkRenderPassCreateInfo::pAttachments[" << rp_index << "] (Subpass " << cb_state.GetActiveSubpass() << ", "; |
| |
| if (type == Type::Empty) { |
| ss << "VK_ATTACHMENT_UNUSED"; |
| } else if (type == Type::Input) { |
| ss << "VkSubpassDescription::pInputAttachments[" << type_index << "]"; |
| } else if (type == Type::Color) { |
| ss << "VkSubpassDescription::pColorAttachments[" << type_index << "]"; |
| } else if (type == Type::ColorResolve) { |
| ss << "VkSubpassDescription::pResolveAttachments[" << type_index << "]"; |
| } else if (type == Type::DepthStencil) { |
| ss << "VkSubpassDescription::pDepthStencilAttachment"; |
| } else if (type == Type::FragmentDensityMap) { |
| ss << "VkRenderPassFragmentDensityMapCreateInfoEXT::fragmentDensityMapAttachment"; |
| } else if (type == Type::FragmentShadingRate) { |
| ss << "VkFragmentShadingRateAttachmentInfoKHR::pFragmentShadingRateAttachment"; |
| } else { |
| ss << "Unknown Type"; |
| } |
| |
| ss << ")"; |
| } |
| return ss.str(); |
| } |
| |
| #ifdef VK_USE_PLATFORM_METAL_EXT |
| static bool GetMetalExport(const VkEventCreateInfo *info) { |
| bool retval = false; |
| auto export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(info->pNext); |
| while (export_metal_object_info) { |
| if (export_metal_object_info->exportObjectType == VK_EXPORT_METAL_OBJECT_TYPE_METAL_SHARED_EVENT_BIT_EXT) { |
| retval = true; |
| break; |
| } |
| export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(export_metal_object_info->pNext); |
| } |
| return retval; |
| } |
| #endif // VK_USE_PLATFORM_METAL_EXT |
| |
| namespace vvl { |
| |
| Event::Event(VkEvent handle, const VkEventCreateInfo *create_info) |
| : StateObject(handle, kVulkanObjectTypeEvent), |
| flags(create_info->flags) |
| #ifdef VK_USE_PLATFORM_METAL_EXT |
| , |
| metal_event_export(GetMetalExport(create_info)) |
| #endif // VK_USE_PLATFORM_METAL_EXT |
| { |
| } |
| |
| CommandPool::CommandPool(DeviceState &dev, VkCommandPool handle, const VkCommandPoolCreateInfo *create_info, VkQueueFlags flags) |
| : StateObject(handle, kVulkanObjectTypeCommandPool), |
| dev_data(dev), |
| createFlags(create_info->flags), |
| queueFamilyIndex(create_info->queueFamilyIndex), |
| queue_flags(flags), |
| unprotected((create_info->flags & VK_COMMAND_POOL_CREATE_PROTECTED_BIT) == 0) {} |
| |
| void CommandPool::Allocate(const VkCommandBufferAllocateInfo *allocate_info, const VkCommandBuffer *command_buffers) { |
| for (uint32_t i = 0; i < allocate_info->commandBufferCount; i++) { |
| auto new_cb = dev_data.CreateCmdBufferState(command_buffers[i], allocate_info, this); |
| commandBuffers.emplace(command_buffers[i], new_cb.get()); |
| dev_data.Add(std::move(new_cb)); |
| } |
| } |
| |
| void CommandPool::Free(uint32_t count, const VkCommandBuffer *command_buffers) { |
| for (uint32_t i = 0; i < count; i++) { |
| auto iter = commandBuffers.find(command_buffers[i]); |
| if (iter != commandBuffers.end()) { |
| dev_data.Destroy<CommandBuffer>(iter->first); |
| commandBuffers.erase(iter); |
| } |
| } |
| } |
| |
| void CommandPool::Reset(const Location &loc) { |
| for (auto &entry : commandBuffers) { |
| auto guard = entry.second->WriteLock(); |
| entry.second->Reset(loc); |
| } |
| } |
| |
| void CommandPool::Destroy() { |
| for (auto &entry : commandBuffers) { |
| dev_data.Destroy<CommandBuffer>(entry.first); |
| } |
| commandBuffers.clear(); |
| StateObject::Destroy(); |
| } |
| |
| void CommandBuffer::SetActiveSubpass(uint32_t subpass) { |
| active_subpass_ = subpass; |
| // Always reset stored rasterization samples count |
| active_subpass_sample_count_ = std::nullopt; |
| } |
| |
| // Put here, instead of vvl::RenderPass for ease of access |
| const char *CommandBuffer::DescribeActiveColorAttachment() const { |
| if (!active_render_pass) { |
| return ""; |
| } else if (active_render_pass->UsesDynamicRendering()) { |
| return "Active color attachments are those where VkRenderingInfo::pColorAttachments[i].imageView != VK_NULL_HANDLE"; |
| } else { |
| return "Active color attachments are those where pSubpasses[i].pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED"; |
| } |
| } |
| |
| CommandBuffer::CommandBuffer(DeviceState &dev, VkCommandBuffer handle, const VkCommandBufferAllocateInfo *allocate_info, |
| const vvl::CommandPool *pool) |
| : RefcountedStateObject(handle, kVulkanObjectTypeCommandBuffer), |
| allocate_info(*allocate_info), |
| command_pool(pool), |
| dev_data(dev), |
| unprotected(pool->unprotected), |
| lastBound({{{*this, VK_PIPELINE_BIND_POINT_GRAPHICS}, |
| {*this, VK_PIPELINE_BIND_POINT_COMPUTE}, |
| {*this, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR}, |
| {*this, VK_PIPELINE_BIND_POINT_DATA_GRAPH_ARM}}}) { |
| ResetCBState(); |
| } |
| |
| // Get the image viewstate for a given framebuffer attachment |
| vvl::ImageView *CommandBuffer::GetActiveAttachmentImageViewState(uint32_t index) { |
| assert(!active_attachments.empty() && index != VK_ATTACHMENT_UNUSED && (index < active_attachments.size())); |
| return active_attachments[index].image_view; |
| } |
| |
| // Get the image viewstate for a given framebuffer attachment |
| const vvl::ImageView *CommandBuffer::GetActiveAttachmentImageViewState(uint32_t index) const { |
| if (active_attachments.empty() || index == VK_ATTACHMENT_UNUSED || (index >= active_attachments.size())) { |
| return nullptr; |
| } |
| return active_attachments[index].image_view; |
| } |
| |
| void CommandBuffer::AddChild(std::shared_ptr<StateObject> &child_node) { |
| assert(child_node); |
| if (child_node->AddParent(this)) { |
| object_bindings.insert(child_node); |
| } |
| } |
| |
| void CommandBuffer::RemoveChild(std::shared_ptr<StateObject> &child_node) { |
| assert(child_node); |
| child_node->RemoveParent(this); |
| object_bindings.erase(child_node); |
| } |
| |
| // This tracking is awful, in practice people are using only a single descriptor mode per command buffer |
| // But in theory you |COULD| do some crazy stuff, like |
| // - Set graphics to use classic descriptor |
| // - Set compute to use Descriptor buffer |
| // - Override both using Heaps |
| // To "properly" do this, we woudl need to really devide LastBound state into 3 structs for each |
| // For the practical future, we will try and get away just assuming these crazy cases are not happening |
| void CommandBuffer::SetDescriptorMode(vvl::DescriptorMode new_mode, vvl::Func function) { |
| // 99% of time, all LastBound will be the same mode |
| bool reset_heap = false; |
| bool reset_buffer = false; |
| |
| for (uint32_t i = 0; i < vvl::BindPointCount; i++) { |
| const vvl::DescriptorMode old_mode = lastBound[i].GetDescriptorMode(); |
| if (old_mode == new_mode) { |
| continue; // common case, nothing to do/change |
| } |
| |
| if (old_mode == vvl::DescriptorMode::DescriptorModeHeap && !reset_heap) { |
| // mappings in device state object to this command buffer state |
| dev_data.RemoveCommandBufferHeapReservedAddressMap(this); |
| |
| descriptor_heap.Reset(); |
| reset_heap = true; |
| } else if (old_mode == vvl::DescriptorMode::DescriptorModeBuffer && !reset_buffer) { |
| descriptor_buffer.Reset(); |
| reset_buffer = true; |
| } |
| |
| lastBound[i].SetDescriptorMode(new_mode, function); |
| } |
| } |
| |
| // Some functions like vkCmdPushConstant are valid in Classic/Buffer, but invalid in Heap |
| // So calling it doesn't "set" a mode, but instead only "invalidates" |
| void CommandBuffer::InvalidateDescriptorMode(vvl::DescriptorMode invalidate_mode, vvl::Func function) { |
| for (uint32_t i = 0; i < vvl::BindPointCount; i++) { |
| const vvl::DescriptorMode current_mode = lastBound[i].GetDescriptorMode(); |
| if (current_mode == invalidate_mode) { |
| // This will happen calling vkCmdPushConstant when in heap, go to "Classic" as a fallback |
| SetDescriptorMode(vvl::DescriptorModeClassic, function); |
| } |
| } |
| } |
| |
| // Reset the command buffer state |
| // Maintain the createInfo and set state to CB_NEW, but clear all other state |
| void CommandBuffer::ResetCBState() { |
| // Remove object bindings |
| for (const auto &obj : object_bindings) { |
| obj->RemoveParent(this); |
| } |
| object_bindings.clear(); |
| broken_bindings.clear(); |
| |
| begin_info_flags = 0; |
| has_inheritance = false; |
| |
| state = CbState::New; |
| command_count = 0; |
| submit_count = 0; |
| image_layout_change_count = 1; // Start at 1. 0 is insert value for validation cache versions, s.t. new == dirty |
| |
| dynamic_state_status.cb.reset(); |
| dynamic_state_status.pipeline.reset(); |
| dynamic_state_status.history.reset(); |
| dynamic_state_status.rtx_stack_size_cb = false; |
| dynamic_state_status.rtx_stack_size_pipeline = false; |
| CBDynamicFlags all; |
| dynamic_state_value.reset(all.set()); |
| memset(&invalidated_state_pipe, 0, sizeof(VkPipeline) * CB_DYNAMIC_STATE_STATUS_NUM); |
| |
| dirty_static_state = false; |
| |
| has_render_pass_instance = false; |
| resumes_render_pass_instance = false; |
| last_suspend_state = SuspendState::Empty; |
| first_action_or_sync_command = Func::Empty; |
| first_rendering_info = {}; |
| first_rendering_info_loc.reset(); |
| last_rendering_info = {}; |
| active_render_pass = nullptr; |
| sample_locations_begin_info = {}; |
| attachment_source = AttachmentSource::Empty; |
| active_attachments.clear(); |
| active_subpasses.clear(); |
| active_color_attachments_index.clear(); |
| has_render_pass_striped = false; |
| striped_count = 0; |
| active_subpass_contents = VK_SUBPASS_CONTENTS_INLINE; |
| SetActiveSubpass(0); |
| rendering_attachments.Reset(); |
| waited_events.clear(); |
| events.clear(); |
| write_events_before_wait.clear(); |
| active_queries.clear(); |
| started_queries.clear(); |
| render_pass_queries.clear(); |
| image_layout_registry.clear(); |
| aliased_image_layout_map.clear(); |
| current_vertex_buffer_binding_info.clear(); |
| primary_command_buffer = VK_NULL_HANDLE; |
| linked_command_buffers.clear(); |
| bound_tile_memory = nullptr; |
| |
| for (auto &item : lastBound) { |
| item.Reset(); |
| } |
| active_framebuffer = VK_NULL_HANDLE; |
| index_buffer_binding = {}; |
| |
| tensor_barriers.clear(); |
| |
| // Clean up video specific states |
| bound_video_session = nullptr; |
| bound_video_session_parameters = nullptr; |
| bound_video_picture_resources.clear(); |
| video_encode_quality_level.reset(); |
| video_session_updates.clear(); |
| |
| // Clean up the label data |
| label_stack_depth_ = 0; |
| label_commands_.clear(); |
| |
| push_constant_ranges_layout.reset(); |
| |
| transform_feedback_active = false; |
| transform_feedback_buffers_bound = 0; |
| |
| SetDescriptorMode(vvl::DescriptorModeUnknown, vvl::Func::Empty); |
| // Need to reset on initalization |
| descriptor_heap.Reset(); |
| descriptor_buffer.Reset(); |
| |
| // Clean up the label data |
| dev_data.debug_report->ResetCmdDebugUtilsLabel(VkHandle()); |
| } |
| |
| void CommandBuffer::Reset(const Location &loc) { |
| ResetCBState(); |
| // Remove reverse command buffer links. |
| Invalidate(true); |
| for (auto &item : sub_states_) { |
| item.second->Reset(loc); |
| } |
| } |
| |
| void CommandBuffer::Destroy() { |
| // Remove the cb debug labels |
| dev_data.debug_report->EraseCmdDebugUtilsLabel(VkHandle()); |
| { |
| auto guard = WriteLock(); |
| ResetCBState(); |
| } |
| for (auto &item : sub_states_) { |
| item.second->Destroy(); |
| } |
| sub_states_.clear(); |
| StateObject::Destroy(); |
| } |
| |
| void CommandBuffer::NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) { |
| { |
| auto guard = WriteLock(); |
| assert(!invalid_nodes.empty()); |
| // Save all of the vulkan handles between the command buffer and the now invalid node |
| LogObjectList log_list; |
| for (auto &obj : invalid_nodes) { |
| log_list.add(obj->Handle()); |
| } |
| |
| bool found_invalid = false; |
| for (auto &obj : invalid_nodes) { |
| // Only record a broken binding if one of the nodes in the invalid chain is still |
| // being tracked by the command buffer. This is to try to avoid race conditions |
| // caused by separate CommandBuffer and StateObject::parent_nodes locking. |
| if (object_bindings.erase(obj)) { |
| obj->RemoveParent(this); |
| found_invalid = true; |
| } |
| switch (obj->Type()) { |
| case kVulkanObjectTypeCommandBuffer: |
| if (unlink) { |
| linked_command_buffers.erase(static_cast<CommandBuffer *>(obj.get())); |
| } |
| break; |
| case kVulkanObjectTypeImage: |
| if (unlink) { |
| image_layout_registry.erase(obj->Handle().Cast<VkImage>()); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| if (found_invalid) { |
| if (state == CbState::Recording) { |
| state = CbState::InvalidIncomplete; |
| } else if (state == CbState::Recorded) { |
| state = CbState::InvalidComplete; |
| } |
| broken_bindings.emplace(invalid_nodes[0]->Handle(), log_list); |
| } |
| } |
| for (auto &item : sub_states_) { |
| item.second->NotifyInvalidate(invalid_nodes, unlink); |
| } |
| StateObject::NotifyInvalidate(invalid_nodes, unlink); |
| } |
| |
| // The const variant only need the image as it is the key for the map |
| std::shared_ptr<const CommandBufferImageLayoutMap> CommandBuffer::GetImageLayoutMap(VkImage image) const { |
| auto it = image_layout_registry.find(image); |
| if (it == image_layout_registry.cend()) { |
| return nullptr; |
| } |
| return it->second; |
| } |
| |
| // The non-const variant only needs the image state, as the factory requires it to construct a new entry |
| std::shared_ptr<CommandBufferImageLayoutMap> CommandBuffer::GetOrCreateImageLayoutMap(const vvl::Image &image_state) { |
| // Make sure we don't create a nullptr keyed entry for a zombie Image |
| if (image_state.Destroyed() || !image_state.layout_map) { |
| return nullptr; |
| } |
| auto iter = image_layout_registry.find(image_state.VkHandle()); |
| if (iter != image_layout_registry.end() && iter->second && image_state.GetId() == iter->second->image_id) { |
| return iter->second; |
| } |
| std::shared_ptr<CommandBufferImageLayoutMap> image_layout_map; |
| if (image_state.CanAlias()) { |
| // Aliasing images need to share the same local layout map. |
| // Since they use the same global layout state, use it as a key |
| // for the local state. We don't need a lock on the global range |
| // map to do a lookup based on its pointer. |
| const auto *p_global_layout_map = image_state.layout_map.get(); |
| auto alias_iter = aliased_image_layout_map.find(p_global_layout_map); |
| if (alias_iter != aliased_image_layout_map.end()) { |
| image_layout_map = alias_iter->second; |
| } else { |
| image_layout_map = std::make_shared<CommandBufferImageLayoutMap>(image_state.subresource_encoder.SubresourceCount(), |
| image_state.GetId()); |
| // Save the local layout map for the next aliased image. |
| // The global layout map pointer is only used as a key into the local lookup |
| // table so it doesn't need to be locked. |
| aliased_image_layout_map.emplace(p_global_layout_map, image_layout_map); |
| } |
| } else { |
| image_layout_map = |
| std::make_shared<CommandBufferImageLayoutMap>(image_state.subresource_encoder.SubresourceCount(), image_state.GetId()); |
| } |
| if (iter != image_layout_registry.end()) { |
| // overwrite the stale entry |
| iter->second = image_layout_map; |
| } else { |
| // add a new entry |
| image_layout_registry.insert({image_state.VkHandle(), image_layout_map}); |
| } |
| return image_layout_map; |
| } |
| |
| void CommandBuffer::RecordCommand(const Location &loc) { |
| command_count++; |
| |
| if (first_action_or_sync_command == Func::Empty) { |
| const CommandValidationInfo &info = GetCommandValidationInfo(loc.function); |
| if (info.action || info.synchronization) { |
| first_action_or_sync_command = loc.function; |
| } |
| } |
| } |
| |
| void CommandBuffer::RecordBeginQuery(const QueryObject &query_obj, const Location &loc) { |
| active_queries.insert(query_obj); |
| started_queries.insert(query_obj); |
| |
| updated_queries.insert(query_obj); |
| if (query_obj.inside_render_pass) { |
| render_pass_queries.insert(query_obj); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordBeginQuery(query_obj, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordEndQuery(const QueryObject &query_obj, const Location &loc) { |
| active_queries.erase(query_obj); |
| updated_queries.insert(query_obj); |
| if (query_obj.inside_render_pass) { |
| render_pass_queries.erase(query_obj); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordEndQuery(query_obj, loc); |
| } |
| } |
| |
| bool CommandBuffer::UpdatesQuery(const QueryObject &query_obj) const { |
| // Clear out the perf_pass from the caller because it isn't known when the command buffer is recorded. |
| auto key = query_obj; |
| key.perf_pass = 0; |
| for (auto *sub_cb : linked_command_buffers) { |
| if (sub_cb->updated_queries.find(key) != sub_cb->updated_queries.end()) { |
| return true; |
| } |
| } |
| return updated_queries.find(key) != updated_queries.end(); |
| } |
| |
| void CommandBuffer::RecordEndQueries(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount) { |
| for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) { |
| QueryObject query_obj = {queryPool, slot}; |
| active_queries.erase(query_obj); |
| updated_queries.insert(query_obj); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordEndQueries(queryPool, firstQuery, queryCount); |
| } |
| } |
| |
| void CommandBuffer::RecordWriteTimestamp(VkQueryPool queryPool, uint32_t slot, const Location &loc) { |
| RecordCommand(loc); |
| if (dev_data.disabled[query_validation]) { |
| return; |
| } |
| |
| if (!dev_data.disabled[command_buffer_state]) { |
| auto pool_state = dev_data.Get<vvl::QueryPool>(queryPool); |
| AddChild(pool_state); |
| } |
| |
| QueryObject query_obj = {queryPool, slot}; |
| for (auto &item : sub_states_) { |
| item.second->RecordWriteTimestamp(query_obj, loc); |
| } |
| |
| // Acts like an end query |
| active_queries.erase(query_obj); |
| updated_queries.insert(query_obj); |
| if (query_obj.inside_render_pass) { |
| render_pass_queries.erase(query_obj); |
| } |
| } |
| |
| void CommandBuffer::RecordResetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, const Location &loc) { |
| RecordCommand(loc); |
| if (dev_data.disabled[query_validation]) { |
| return; |
| } |
| |
| auto pool_state = dev_data.Get<QueryPool>(queryPool); |
| ASSERT_AND_RETURN(pool_state); |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(pool_state); |
| } |
| |
| for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) { |
| QueryObject query_obj = {queryPool, slot}; |
| updated_queries.insert(query_obj); |
| } |
| |
| const bool is_perf_query = pool_state->create_info.queryType == VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR; |
| for (auto &item : sub_states_) { |
| item.second->RecordResetQueryPool(queryPool, firstQuery, queryCount, is_perf_query, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyQueryPoolResults(VkQueryPool queryPool, VkBuffer dstBuffer, uint32_t firstQuery, uint32_t queryCount, |
| VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags, |
| const Location &loc) { |
| RecordCommand(loc); |
| if (dev_data.disabled[query_validation]) { |
| return; |
| } |
| |
| auto buffer_state = dev_data.Get<Buffer>(dstBuffer); |
| auto pool_state = dev_data.Get<QueryPool>(queryPool); |
| ASSERT_AND_RETURN(buffer_state && pool_state); |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(buffer_state); |
| AddChild(pool_state); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyQueryPoolResults(*pool_state, *buffer_state, firstQuery, queryCount, dstOffset, stride, flags, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordWriteAccelerationStructuresProperties(VkQueryPool queryPool, uint32_t firstQuery, |
| uint32_t accelerationStructureCount, const Location &loc) { |
| RecordCommand(loc); |
| if (dev_data.disabled[query_validation]) { |
| return; |
| } |
| |
| if (!dev_data.disabled[command_buffer_state]) { |
| auto pool_state = dev_data.Get<QueryPool>(queryPool); |
| AddChild(pool_state); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordWriteAccelerationStructuresProperties(queryPool, firstQuery, accelerationStructureCount, loc); |
| } |
| |
| // Same idea as RecordEndQueries |
| for (uint32_t slot = firstQuery; slot < (firstQuery + accelerationStructureCount); slot++) { |
| QueryObject query_obj = {queryPool, slot}; |
| active_queries.erase(query_obj); |
| updated_queries.insert(query_obj); |
| } |
| } |
| |
| void CommandBuffer::UpdateSubpassAttachments() { |
| ASSERT_AND_RETURN(active_render_pass); |
| const auto &subpass = active_render_pass->create_info.pSubpasses[GetActiveSubpass()]; |
| assert(active_subpasses.size() == active_attachments.size()); |
| |
| for (size_t i = 0; i < active_attachments.size(); ++i) { |
| active_attachments[i].type = AttachmentInfo::Type::Empty; |
| } |
| |
| for (uint32_t index = 0; index < subpass.inputAttachmentCount; ++index) { |
| const uint32_t attachment_index = subpass.pInputAttachments[index].attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index].type = AttachmentInfo::Type::Input; |
| active_attachments[attachment_index].layout = subpass.pInputAttachments[index].layout; |
| active_attachments[attachment_index].type_index = index; |
| active_subpasses[attachment_index].used = true; |
| active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; |
| active_subpasses[attachment_index].aspectMask = subpass.pInputAttachments[index].aspectMask; |
| } |
| } |
| |
| for (uint32_t index = 0; index < subpass.colorAttachmentCount; ++index) { |
| const uint32_t attachment_index = subpass.pColorAttachments[index].attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index].type = AttachmentInfo::Type::Color; |
| active_attachments[attachment_index].layout = subpass.pColorAttachments[index].layout; |
| active_attachments[attachment_index].type_index = index; |
| active_color_attachments_index.insert(index); |
| active_subpasses[attachment_index].used = true; |
| active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| active_subpasses[attachment_index].aspectMask = subpass.pColorAttachments[index].aspectMask; |
| } |
| if (subpass.pResolveAttachments) { |
| const uint32_t attachment_index2 = subpass.pResolveAttachments[index].attachment; |
| if (attachment_index2 != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index2].type = AttachmentInfo::Type::ColorResolve; |
| active_attachments[attachment_index2].layout = subpass.pResolveAttachments[index].layout; |
| active_attachments[attachment_index2].type_index = index; |
| active_subpasses[attachment_index2].used = true; |
| active_subpasses[attachment_index2].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| active_subpasses[attachment_index2].aspectMask = subpass.pResolveAttachments[index].aspectMask; |
| } |
| } |
| } |
| |
| if (subpass.pDepthStencilAttachment) { |
| const uint32_t attachment_index = subpass.pDepthStencilAttachment->attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index].type = AttachmentInfo::Type::DepthStencil; |
| active_attachments[attachment_index].layout = subpass.pDepthStencilAttachment->layout; |
| // Look for potential dedicated stencil layout |
| if (const auto *stencil_layout = |
| vku::FindStructInPNextChain<VkAttachmentReferenceStencilLayout>(subpass.pDepthStencilAttachment->pNext)) { |
| active_attachments[attachment_index].separate_stencil_layout = stencil_layout->stencilLayout; |
| } |
| active_subpasses[attachment_index].used = true; |
| active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; |
| active_subpasses[attachment_index].aspectMask = subpass.pDepthStencilAttachment->aspectMask; |
| } |
| } |
| |
| if (auto rdm_ci = |
| vku::FindStructInPNextChain<VkRenderPassFragmentDensityMapCreateInfoEXT>(active_render_pass->create_info.pNext)) { |
| const uint32_t attachment_index = rdm_ci->fragmentDensityMapAttachment.attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index].type = AttachmentInfo::Type::FragmentDensityMap; |
| active_attachments[attachment_index].layout = rdm_ci->fragmentDensityMapAttachment.layout; |
| active_subpasses[attachment_index].used = true; |
| active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT; |
| active_subpasses[attachment_index].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
| } |
| } |
| |
| if (auto rdr_attachment_ci = vku::FindStructInPNextChain<VkFragmentShadingRateAttachmentInfoKHR>(subpass.pNext)) { |
| if (rdr_attachment_ci->pFragmentShadingRateAttachment) { |
| const uint32_t attachment_index = rdr_attachment_ci->pFragmentShadingRateAttachment->attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| active_attachments[attachment_index].type = AttachmentInfo::Type::FragmentShadingRate; |
| active_attachments[attachment_index].layout = rdr_attachment_ci->pFragmentShadingRateAttachment->layout; |
| active_subpasses[attachment_index].used = true; |
| active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR; |
| active_subpasses[attachment_index].aspectMask = rdr_attachment_ci->pFragmentShadingRateAttachment->aspectMask; |
| } |
| } |
| } |
| } |
| |
| // For non Dynamic Renderpass we update the attachments |
| void CommandBuffer::UpdateAttachmentsView(const VkRenderPassBeginInfo *pRenderPassBegin) { |
| const bool imageless = (active_framebuffer->create_info.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) != 0; |
| const VkRenderPassAttachmentBeginInfo *attachment_info_struct = nullptr; |
| if (pRenderPassBegin) attachment_info_struct = vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext); |
| |
| for (uint32_t i = 0; i < active_attachments.size(); ++i) { |
| if (imageless) { |
| if (attachment_info_struct && i < attachment_info_struct->attachmentCount) { |
| active_attachments[i].image_view = dev_data.Get<vvl::ImageView>(attachment_info_struct->pAttachments[i]).get(); |
| } |
| } else { |
| active_attachments[i].image_view = active_framebuffer->attachments_view_state[i].get(); |
| } |
| } |
| |
| // While updating the subpass we will set the active_attachments type |
| UpdateSubpassAttachments(); |
| } |
| |
| void CommandBuffer::RecordBeginRenderPass(const VkRenderPassBeginInfo &render_pass_begin, |
| const VkSubpassBeginInfo &subpass_begin_info, const Location &loc) { |
| RecordCommand(loc); |
| active_framebuffer = dev_data.Get<vvl::Framebuffer>(render_pass_begin.framebuffer); |
| active_render_pass = dev_data.Get<vvl::RenderPass>(render_pass_begin.renderPass); |
| render_area = render_pass_begin.renderArea; |
| SetActiveSubpass(0); |
| active_subpass_contents = subpass_begin_info.contents; |
| render_pass_queries.clear(); |
| |
| // Connect this RP to cmdBuffer |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(active_render_pass); |
| } |
| |
| if (auto sample_locations_begin = |
| vku::FindStructInPNextChain<VkRenderPassSampleLocationsBeginInfoEXT>(render_pass_begin.pNext)) { |
| sample_locations_begin_info = sample_locations_begin; |
| } |
| |
| if (auto rp_striped_begin = vku::FindStructInPNextChain<VkRenderPassStripeBeginInfoARM>(render_pass_begin.pNext)) { |
| has_render_pass_striped = true; |
| striped_count += rp_striped_begin->stripeInfoCount; |
| } |
| |
| // Spec states that after BeginRenderPass all resources should be rebound |
| if (active_render_pass->has_multiview_enabled) { |
| UnbindResources(); |
| } |
| |
| auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupRenderPassBeginInfo>(render_pass_begin.pNext); |
| render_pass_device_mask = chained_device_group_struct ? chained_device_group_struct->deviceMask : initial_device_mask; |
| |
| attachment_source = AttachmentSource::RenderPass; |
| active_subpasses.clear(); |
| active_attachments.clear(); |
| |
| if (active_framebuffer) { |
| active_subpasses.resize(active_framebuffer->create_info.attachmentCount); |
| active_attachments.resize(active_framebuffer->create_info.attachmentCount); |
| UpdateAttachmentsView(&render_pass_begin); |
| |
| // Connect this framebuffer and its children to this cmdBuffer |
| AddChild(active_framebuffer); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordBeginRenderPass(render_pass_begin, subpass_begin_info, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordNextSubpass(const VkSubpassBeginInfo &subpass_begin_info, const VkSubpassEndInfo *subpass_end_info, |
| const Location &loc) { |
| RecordCommand(loc); |
| SetActiveSubpass(GetActiveSubpass() + 1); |
| active_subpass_contents = subpass_begin_info.contents; |
| ASSERT_AND_RETURN(active_render_pass); |
| |
| if (active_framebuffer) { |
| active_subpasses.clear(); |
| active_subpasses.resize(active_framebuffer->create_info.attachmentCount); |
| |
| if (GetActiveSubpass() < active_render_pass->create_info.subpassCount) { |
| UpdateSubpassAttachments(); |
| } |
| } |
| |
| // Spec states that after NextSubpass all resources should be rebound |
| if (active_render_pass->has_multiview_enabled) { |
| UnbindResources(); |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordNextSubpass(subpass_begin_info, subpass_end_info, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordEndRenderPass(const VkSubpassEndInfo *subpass_end_info, const Location &loc) { |
| // Call first so SubState can use render pass object before we destroy it |
| for (auto &item : sub_states_) { |
| item.second->RecordEndRenderPass(subpass_end_info, loc); |
| } |
| |
| RecordCommand(loc); |
| active_render_pass = nullptr; |
| attachment_source = AttachmentSource::Empty; |
| active_attachments.clear(); |
| active_subpasses.clear(); |
| active_color_attachments_index.clear(); |
| SetActiveSubpass(0); |
| active_framebuffer = VK_NULL_HANDLE; |
| sample_locations_begin_info = {}; |
| } |
| |
| static void InitDefaultRenderingAttachments(CommandBuffer::RenderingAttachment &attachments, uint32_t count) { |
| attachments.color_locations.resize(count); |
| attachments.color_indexes.resize(count); |
| attachments.depth_index = nullptr; |
| attachments.stencil_index = nullptr; |
| attachments.set_color_locations = false; |
| attachments.set_color_indexes = false; |
| for (uint32_t i = 0; i < count; i++) { |
| // Default from spec |
| attachments.color_locations[i] = i; |
| attachments.color_indexes[i] = i; |
| } |
| } |
| |
| void CommandBuffer::RecordBeginRendering(const VkRenderingInfo &rendering_info, const Location &loc) { |
| RecordCommand(loc); |
| active_render_pass = std::make_shared<vvl::RenderPass>(rendering_info); |
| render_area = rendering_info.renderArea; |
| render_pass_queries.clear(); |
| |
| InitDefaultRenderingAttachments(rendering_attachments, rendering_info.colorAttachmentCount); |
| |
| auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupRenderPassBeginInfo>(rendering_info.pNext); |
| render_pass_device_mask = chained_device_group_struct ? chained_device_group_struct->deviceMask : initial_device_mask; |
| |
| auto rp_striped_begin = vku::FindStructInPNextChain<VkRenderPassStripeBeginInfoARM>(rendering_info.pNext); |
| if (rp_striped_begin) { |
| has_render_pass_striped = true; |
| striped_count += rp_striped_begin->stripeInfoCount; |
| } |
| |
| active_subpass_contents = ((rendering_info.flags & VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT) |
| ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS |
| : VK_SUBPASS_CONTENTS_INLINE); |
| |
| // Track if the first render pass instance does resume |
| if (!has_render_pass_instance && (rendering_info.flags & VK_RENDERING_RESUMING_BIT)) { |
| resumes_render_pass_instance = true; |
| } |
| // Track the last suspension state. Notice that both RESUMING/SUSPENDING flags can be specified. |
| // The ordering is that suspension action goes after resuming action. |
| if (rendering_info.flags & VK_RENDERING_RESUMING_BIT) { |
| last_suspend_state = SuspendState::Resumed; |
| } |
| if (rendering_info.flags & VK_RENDERING_SUSPENDING_BIT) { |
| last_suspend_state = SuspendState::Suspended; |
| } |
| if (!first_rendering_info.has_value()) { |
| first_rendering_info = &rendering_info; |
| first_rendering_info_loc = std::make_unique<LocationCapture>(loc.dot(vvl::Field::pRenderingInfo)); |
| } |
| last_rendering_info = &rendering_info; |
| |
| has_render_pass_instance = true; |
| |
| attachment_source = AttachmentSource::DynamicRendering; |
| active_attachments.clear(); |
| // add 2 for the Depth and Stencil |
| // multiple by 2 because every attachment might have a resolve |
| uint32_t attachment_count = (rendering_info.colorAttachmentCount + 2) * 2; |
| attachment_count += 1; // FragmentDensityMap (doesn't need a resolve) |
| attachment_count += 1; // FragmentShadingRate (doesn't need a resolve) |
| |
| // Currently reserve the maximum possible size for |active_attachments| so when looping, we NEED to check for null |
| active_attachments.resize(attachment_count); |
| |
| for (uint32_t i = 0; i < rendering_info.colorAttachmentCount; ++i) { |
| const auto &rendering_attachment = rendering_info.pColorAttachments[i]; |
| if (rendering_attachment.imageView != VK_NULL_HANDLE) { |
| auto &color_attachment = active_attachments[GetDynamicRenderingColorAttachmentIndex(i)]; |
| color_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_attachment.imageView).get(); |
| color_attachment.type = AttachmentInfo::Type::Color; |
| color_attachment.layout = rendering_attachment.imageLayout; |
| color_attachment.type_index = i; |
| active_color_attachments_index.insert(i); |
| if (color_attachment.image_view) { |
| TrackImageViewFirstLayout(*color_attachment.image_view, rendering_attachment.imageLayout, |
| "VUID-vkCmdBeginRendering-pRenderingInfo-09592"); |
| } |
| if (rendering_attachment.resolveMode != VK_RESOLVE_MODE_NONE && |
| rendering_attachment.resolveImageView != VK_NULL_HANDLE) { |
| auto &resolve_attachment = active_attachments[GetDynamicRenderingColorResolveAttachmentIndex(i)]; |
| resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_attachment.resolveImageView).get(); |
| resolve_attachment.type = AttachmentInfo::Type::ColorResolve; |
| resolve_attachment.layout = rendering_attachment.resolveImageLayout; |
| resolve_attachment.type_index = i; |
| } |
| } |
| } |
| |
| if (rendering_info.pDepthAttachment && rendering_info.pDepthAttachment->imageView != VK_NULL_HANDLE) { |
| auto &depth_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Depth)]; |
| depth_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pDepthAttachment->imageView).get(); |
| depth_attachment.type = AttachmentInfo::Type::Depth; |
| depth_attachment.layout = rendering_info.pDepthAttachment->imageLayout; |
| if (depth_attachment.image_view) { |
| TrackDepthAttachmentFirstLayout(*depth_attachment.image_view, rendering_info.pDepthAttachment->imageLayout, |
| "VUID-vkCmdBeginRendering-pRenderingInfo-09588"); |
| } |
| if (rendering_info.pDepthAttachment->resolveMode != VK_RESOLVE_MODE_NONE && |
| rendering_info.pDepthAttachment->resolveImageView != VK_NULL_HANDLE) { |
| auto &resolve_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::DepthResolve)]; |
| resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pDepthAttachment->resolveImageView).get(); |
| resolve_attachment.type = AttachmentInfo::Type::DepthResolve; |
| resolve_attachment.layout = rendering_info.pDepthAttachment->resolveImageLayout; |
| } |
| } |
| |
| if (rendering_info.pStencilAttachment && rendering_info.pStencilAttachment->imageView != VK_NULL_HANDLE) { |
| auto &stencil_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Stencil)]; |
| stencil_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pStencilAttachment->imageView).get(); |
| stencil_attachment.type = AttachmentInfo::Type::Stencil; |
| stencil_attachment.layout = rendering_info.pStencilAttachment->imageLayout; |
| if (stencil_attachment.image_view) { |
| TrackStencilAttachmentFirstLayout(*stencil_attachment.image_view, rendering_info.pStencilAttachment->imageLayout, |
| "VUID-vkCmdBeginRendering-pRenderingInfo-09590"); |
| } |
| if (rendering_info.pStencilAttachment->resolveMode != VK_RESOLVE_MODE_NONE && |
| rendering_info.pStencilAttachment->resolveImageView != VK_NULL_HANDLE) { |
| auto &resolve_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::StencilResolve)]; |
| resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pStencilAttachment->resolveImageView).get(); |
| resolve_attachment.type = AttachmentInfo::Type::StencilResolve; |
| resolve_attachment.layout = rendering_info.pStencilAttachment->resolveImageLayout; |
| } |
| } |
| |
| if (auto fragment_density_map_info = |
| vku::FindStructInPNextChain<VkRenderingFragmentDensityMapAttachmentInfoEXT>(rendering_info.pNext)) { |
| auto &fdm_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::FragmentDensityMap)]; |
| fdm_attachment.image_view = dev_data.Get<vvl::ImageView>(fragment_density_map_info->imageView).get(); |
| fdm_attachment.type = AttachmentInfo::Type::FragmentDensityMap; |
| fdm_attachment.layout = fragment_density_map_info->imageLayout; |
| if (fdm_attachment.image_view) { |
| TrackImageViewFirstLayout(*fdm_attachment.image_view, fdm_attachment.layout, |
| "VUID-vkCmdBeginRendering-imageView-12276"); |
| } |
| } |
| |
| if (auto fsr_attachment_info = |
| vku::FindStructInPNextChain<VkRenderingFragmentShadingRateAttachmentInfoKHR>(rendering_info.pNext)) { |
| auto &fsr_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::FragmentShadingRate)]; |
| fsr_attachment.image_view = dev_data.Get<vvl::ImageView>(fsr_attachment_info->imageView).get(); |
| fsr_attachment.type = AttachmentInfo::Type::FragmentShadingRate; |
| fsr_attachment.layout = fsr_attachment_info->imageLayout; |
| if (fsr_attachment.image_view) { |
| TrackImageViewFirstLayout(*fsr_attachment.image_view, fsr_attachment.layout, |
| "VUID-vkCmdBeginRendering-imageView-12277"); |
| } |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordBeginRendering(rendering_info, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordEndRendering(const VkRenderingEndInfoEXT *pRenderingEndInfo, const Location &loc) { |
| // Call first so SubState can use render pass object before we destroy it |
| for (auto &item : sub_states_) { |
| item.second->RecordEndRendering(pRenderingEndInfo); |
| } |
| |
| RecordCommand(loc); |
| active_render_pass = nullptr; |
| active_color_attachments_index.clear(); |
| } |
| |
| void CommandBuffer::RecordBeginCustomResolve(const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordBeginCustomResolve(); |
| } |
| } |
| |
| void CommandBuffer::RecordBeginVideoCoding(const VkVideoBeginCodingInfoKHR &begin_info, const Location &loc) { |
| RecordCommand(loc); |
| bound_video_session = dev_data.Get<vvl::VideoSession>(begin_info.videoSession); |
| ASSERT_AND_RETURN(bound_video_session); |
| bound_video_session_parameters = dev_data.Get<vvl::VideoSessionParameters>(begin_info.videoSessionParameters); |
| |
| // Connect this video session to cmdBuffer |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(bound_video_session); |
| } |
| |
| if (bound_video_session_parameters) { |
| // Connect this video session parameters object to cmdBuffer |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(bound_video_session_parameters); |
| } |
| } |
| |
| // Need to record substate first |
| for (auto &item : sub_states_) { |
| item.second->RecordBeginVideoCoding(*bound_video_session, begin_info, loc); |
| } |
| |
| if (bound_video_session->IsEncode()) { |
| video_encode_rate_control_state = VideoEncodeRateControlState(bound_video_session->GetCodecOp(), &begin_info); |
| video_encode_quality_level.reset(); |
| } |
| |
| if (begin_info.referenceSlotCount > 0) { |
| size_t deactivated_slot_count = 0; |
| |
| for (uint32_t i = 0; i < begin_info.referenceSlotCount; ++i) { |
| // Initialize the set of bound video picture resources |
| if (begin_info.pReferenceSlots[i].pPictureResource != nullptr) { |
| int32_t slot_index = begin_info.pReferenceSlots[i].slotIndex; |
| vvl::VideoPictureResource res(dev_data, *begin_info.pReferenceSlots[i].pPictureResource); |
| bound_video_picture_resources.emplace(std::make_pair(res, slot_index)); |
| } |
| |
| if (begin_info.pReferenceSlots[i].slotIndex >= 0 && begin_info.pReferenceSlots[i].pPictureResource == nullptr) { |
| deactivated_slot_count++; |
| } |
| } |
| |
| if (deactivated_slot_count > 0) { |
| std::vector<int32_t> deactivated_slots{}; |
| deactivated_slots.reserve(deactivated_slot_count); |
| for (uint32_t i = 0; i < begin_info.referenceSlotCount; ++i) { |
| if (begin_info.pReferenceSlots[i].slotIndex >= 0 && begin_info.pReferenceSlots[i].pPictureResource == nullptr) { |
| deactivated_slots.emplace_back(begin_info.pReferenceSlots[i].slotIndex); |
| } |
| } |
| |
| // Enqueue submission time DPB slot deactivation |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [deactivated_slots](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, bool do_validate) { |
| for (const auto &slot_index : deactivated_slots) { |
| dev_state.Deactivate(slot_index); |
| } |
| return false; |
| }); |
| } |
| } |
| } |
| |
| void CommandBuffer::RecordEndVideoCoding(const Location &loc) { |
| RecordCommand(loc); |
| bound_video_session = nullptr; |
| bound_video_session_parameters = nullptr; |
| bound_video_picture_resources.clear(); |
| video_encode_quality_level.reset(); |
| } |
| |
| void CommandBuffer::RecordControlVideoCoding(const VkVideoCodingControlInfoKHR &control_info, const Location &loc) { |
| RecordCommand(loc); |
| if (!bound_video_session) { |
| return; |
| } |
| |
| // Need to record substate first |
| for (auto &item : sub_states_) { |
| item.second->RecordControlVideoCoding(*bound_video_session, control_info, loc); |
| } |
| |
| if (control_info.flags & VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR) { |
| // Remove DPB slot index association for bound video picture resources |
| for (auto &binding : bound_video_picture_resources) { |
| binding.second = -1; |
| } |
| |
| // Enqueue submission time video session state reset/initialization |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, bool do_validate) { |
| dev_state.Reset(); |
| return false; |
| }); |
| } |
| |
| if (bound_video_session->IsEncode() && control_info.flags & VK_VIDEO_CODING_CONTROL_ENCODE_RATE_CONTROL_BIT_KHR) { |
| auto state = VideoEncodeRateControlState(bound_video_session->GetCodecOp(), &control_info); |
| if (state) { |
| video_encode_rate_control_state = state; |
| |
| // Enqueue rate control specific device state changes |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [state](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, bool do_validate) { |
| dev_state.SetRateControlState(state); |
| return false; |
| }); |
| } |
| } |
| |
| if (bound_video_session->IsEncode() && control_info.flags & VK_VIDEO_CODING_CONTROL_ENCODE_QUALITY_LEVEL_BIT_KHR) { |
| auto quality_level_info = vku::FindStructInPNextChain<VkVideoEncodeQualityLevelInfoKHR>(control_info.pNext); |
| if (quality_level_info != nullptr) { |
| uint32_t quality_level = quality_level_info->qualityLevel; |
| video_encode_quality_level = quality_level; |
| |
| // Enqueue encode quality level device state change |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [quality_level](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, bool do_validate) { |
| dev_state.SetEncodeQualityLevel(quality_level); |
| return false; |
| }); |
| } |
| } |
| } |
| |
| void vvl::CommandBuffer::RecordVideoInlineQueries(const VkVideoInlineQueryInfoKHR &query_info) { |
| for (auto &item : sub_states_) { |
| item.second->RecordVideoInlineQueries(query_info); |
| } |
| |
| for (uint32_t i = 0; i < query_info.queryCount; i++) { |
| updated_queries.insert(QueryObject(query_info.queryPool, query_info.firstQuery + i)); |
| } |
| } |
| |
| void CommandBuffer::RecordDecodeVideo(const VkVideoDecodeInfoKHR &decode_info, const Location &loc) { |
| RecordCommand(loc); |
| if (!bound_video_session) { |
| return; |
| } |
| |
| // Need to record substate first |
| for (auto &item : sub_states_) { |
| item.second->RecordDecodeVideo(*bound_video_session, decode_info, loc); |
| } |
| |
| if (decode_info.pSetupReferenceSlot && decode_info.pSetupReferenceSlot->pPictureResource) { |
| vvl::VideoReferenceSlot setup_slot(dev_data, *bound_video_session->profile, *decode_info.pSetupReferenceSlot); |
| |
| // Update bound video picture resource DPB slot index association |
| bound_video_picture_resources[setup_slot.resource] = setup_slot.index; |
| |
| // Enqueue submission time reference slot setup or invalidation |
| bool reference_setup_requested = bound_video_session->ReferenceSetupRequested(decode_info); |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [setup_slot, reference_setup_requested](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, |
| bool do_validate) { |
| if (reference_setup_requested) { |
| dev_state.Activate(setup_slot.index, setup_slot.picture_id, setup_slot.resource); |
| } else { |
| dev_state.Invalidate(setup_slot.index, setup_slot.picture_id); |
| } |
| return false; |
| }); |
| } |
| |
| // Update active query indices |
| for (auto &query : active_queries) { |
| uint32_t op_count = bound_video_session->GetVideoDecodeOperationCount(&decode_info); |
| query.active_query_index += op_count; |
| } |
| |
| // Update inline queries |
| if (bound_video_session->create_info.flags & VK_VIDEO_SESSION_CREATE_INLINE_QUERIES_BIT_KHR) { |
| const auto inline_query_info = vku::FindStructInPNextChain<VkVideoInlineQueryInfoKHR>(decode_info.pNext); |
| if (inline_query_info != nullptr && inline_query_info->queryPool != VK_NULL_HANDLE) { |
| RecordVideoInlineQueries(*inline_query_info); |
| } |
| } |
| } |
| |
| void vvl::CommandBuffer::RecordEncodeVideo(const VkVideoEncodeInfoKHR &encode_info, const Location &loc) { |
| RecordCommand(loc); |
| if (!bound_video_session) { |
| return; |
| } |
| |
| // Need to record substate first |
| for (auto &item : sub_states_) { |
| item.second->RecordEncodeVideo(*bound_video_session, encode_info, loc); |
| } |
| |
| if (encode_info.pSetupReferenceSlot && encode_info.pSetupReferenceSlot->pPictureResource) { |
| vvl::VideoReferenceSlot setup_slot(dev_data, *bound_video_session->profile, *encode_info.pSetupReferenceSlot); |
| |
| // Update bound video picture resource DPB slot index association |
| bound_video_picture_resources[setup_slot.resource] = setup_slot.index; |
| |
| // Enqueue submission time reference slot setup or invalidation |
| bool reference_setup_requested = bound_video_session->ReferenceSetupRequested(encode_info); |
| video_session_updates[bound_video_session->VkHandle()].emplace_back( |
| [setup_slot, reference_setup_requested](const vvl::VideoSession *vs_state, vvl::VideoSessionDeviceState &dev_state, |
| bool do_validate) { |
| if (reference_setup_requested) { |
| dev_state.Activate(setup_slot.index, setup_slot.picture_id, setup_slot.resource); |
| } else { |
| dev_state.Invalidate(setup_slot.index, setup_slot.picture_id); |
| } |
| return false; |
| }); |
| } |
| |
| // Update active query indices |
| for (auto &query : active_queries) { |
| uint32_t op_count = bound_video_session->GetVideoEncodeOperationCount(&encode_info); |
| query.active_query_index += op_count; |
| } |
| |
| // Update inline queries |
| if (bound_video_session->create_info.flags & VK_VIDEO_SESSION_CREATE_INLINE_QUERIES_BIT_KHR) { |
| const auto inline_query_info = vku::FindStructInPNextChain<VkVideoInlineQueryInfoKHR>(encode_info.pNext); |
| if (inline_query_info != nullptr && inline_query_info->queryPool != VK_NULL_HANDLE) { |
| RecordVideoInlineQueries(*inline_query_info); |
| } |
| } |
| } |
| |
| static void SetRenderingAttachmentLocations(CommandBuffer::RenderingAttachment &attachments, const VkRenderingAttachmentLocationInfo *pLocationInfo) { |
| attachments.color_locations.resize(pLocationInfo->colorAttachmentCount); |
| const uint32_t *locations = pLocationInfo->pColorAttachmentLocations; |
| for (uint32_t i = 0; i < pLocationInfo->colorAttachmentCount; ++i) { |
| attachments.color_locations[i] = locations ? locations[i] : i; |
| } |
| } |
| |
| static void SetRenderingInputAttachmentIndices(CommandBuffer::RenderingAttachment &attachments, const VkRenderingInputAttachmentIndexInfo *pLocationInfo) { |
| attachments.color_indexes.resize(pLocationInfo->colorAttachmentCount); |
| const uint32_t *indexes = pLocationInfo->pColorAttachmentInputIndices; |
| for (uint32_t i = 0; i < pLocationInfo->colorAttachmentCount; ++i) { |
| attachments.color_indexes[i] = indexes ? indexes[i] : i; |
| } |
| if (pLocationInfo->pDepthInputAttachmentIndex) { |
| attachments.depth_index_storage = *pLocationInfo->pDepthInputAttachmentIndex; |
| attachments.depth_index = &attachments.depth_index_storage; |
| } else { |
| attachments.depth_index = nullptr; |
| } |
| if (pLocationInfo->pStencilInputAttachmentIndex) { |
| attachments.stencil_index_storage = *pLocationInfo->pStencilInputAttachmentIndex; |
| attachments.stencil_index = &attachments.stencil_index_storage; |
| } else { |
| attachments.stencil_index = nullptr; |
| } |
| } |
| |
| void CommandBuffer::Begin(const VkCommandBufferBeginInfo *pBeginInfo) { |
| if (IsRecorded(state)) { |
| Location loc(Func::vkBeginCommandBuffer); |
| Reset(loc); |
| } |
| |
| // Set updated state here in case implicit reset occurs above |
| state = CbState::Recording; |
| ASSERT_AND_RETURN(pBeginInfo); |
| |
| begin_info_flags = pBeginInfo->flags; |
| |
| if (pBeginInfo->pInheritanceInfo && IsSecondary()) { |
| // pInheritanceInfo could be valid, but ignored, if in a primary command buffer |
| has_inheritance = true; |
| inheritance_info.initialize(pBeginInfo->pInheritanceInfo); |
| |
| // If we are a secondary command-buffer and inheriting. Update the items we should inherit. |
| if (begin_info_flags & VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT) { |
| if (inheritance_info.renderPass) { |
| active_render_pass = dev_data.Get<vvl::RenderPass>(inheritance_info.renderPass); |
| SetActiveSubpass(inheritance_info.subpass); |
| |
| if (inheritance_info.framebuffer) { |
| active_framebuffer = dev_data.Get<vvl::Framebuffer>(inheritance_info.framebuffer); |
| attachment_source = AttachmentSource::Inheritance; |
| active_subpasses.clear(); |
| active_attachments.clear(); |
| |
| if (active_framebuffer) { |
| active_subpasses.resize(active_framebuffer->create_info.attachmentCount); |
| active_attachments.resize(active_framebuffer->create_info.attachmentCount); |
| UpdateAttachmentsView(nullptr); |
| |
| // Connect this framebuffer and its children to this cmdBuffer |
| if (!dev_data.disabled[command_buffer_state]) { |
| AddChild(active_framebuffer); |
| } |
| } |
| } |
| } else { |
| auto inheritance_rendering_info = |
| vku::FindStructInPNextChain<VkCommandBufferInheritanceRenderingInfo>(pBeginInfo->pInheritanceInfo->pNext); |
| if (inheritance_rendering_info) { |
| active_render_pass = std::make_shared<vvl::RenderPass>(inheritance_rendering_info); |
| |
| InitDefaultRenderingAttachments(rendering_attachments, inheritance_rendering_info->colorAttachmentCount); |
| if (auto locations = |
| vku::FindStructInPNextChain<VkRenderingAttachmentLocationInfo>(pBeginInfo->pInheritanceInfo->pNext)) { |
| SetRenderingAttachmentLocations(rendering_attachments, locations); |
| } |
| if (auto indexes = |
| vku::FindStructInPNextChain<VkRenderingInputAttachmentIndexInfo>(pBeginInfo->pInheritanceInfo->pNext)) { |
| SetRenderingInputAttachmentIndices(rendering_attachments, indexes); |
| } |
| } |
| } |
| } |
| |
| if (auto descriptor_heap_info = |
| vku::FindStructInPNextChain<VkCommandBufferInheritanceDescriptorHeapInfoEXT>(pBeginInfo->pInheritanceInfo->pNext)) { |
| inheritance_descriptor_heap_info.initialize(descriptor_heap_info); |
| } |
| } |
| |
| auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupCommandBufferBeginInfo>(pBeginInfo->pNext); |
| if (chained_device_group_struct) { |
| initial_device_mask = chained_device_group_struct->deviceMask; |
| } else { |
| initial_device_mask = (1 << dev_data.physical_device_count) - 1; |
| } |
| |
| performance_lock_acquired = dev_data.performance_lock_acquired; |
| updated_queries.clear(); |
| |
| for (auto &item : sub_states_) { |
| item.second->Begin(*pBeginInfo); |
| } |
| } |
| |
| void CommandBuffer::End(VkResult result) { |
| if (result == VK_SUCCESS) { |
| state = CbState::Recorded; |
| } |
| for (auto &item : sub_states_) { |
| item.second->End(); |
| } |
| } |
| |
| void CommandBuffer::RecordExecuteCommands(vvl::span<const VkCommandBuffer> secondary_command_buffers, const Location &loc) { |
| RecordCommand(loc); |
| uint32_t cmd_index = 0; |
| for (const VkCommandBuffer sub_command_buffer : secondary_command_buffers) { |
| auto secondary_cb_state = dev_data.GetWrite<CommandBuffer>(sub_command_buffer); |
| ASSERT_AND_RETURN(secondary_cb_state); |
| if (!(secondary_cb_state->begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) { |
| if (begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT) { |
| // TODO: Because this is a state change, clearing the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT needs to be moved |
| // from the validation step to the recording step |
| begin_info_flags &= ~VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; |
| } |
| } |
| |
| // Propagate inital layout and current layout state to the primary cmd buffer |
| // NOTE: The update/population of the image_layout_map is done in CoreChecks, but for other classes derived from |
| // Device these maps will be empty, so leaving the propagation in the the state tracker should be a no-op |
| // for those other classes. |
| for (const auto &[image, secondary_cb_layout_map] : secondary_cb_state->image_layout_registry) { |
| const auto image_state = dev_data.Get<vvl::Image>(image); |
| if (!image_state || image_state->Destroyed() || !secondary_cb_layout_map || |
| image_state->GetId() != secondary_cb_layout_map->image_id) { |
| continue; |
| } |
| if (auto cb_layout_map = GetOrCreateImageLayoutMap(*image_state)) { |
| struct Updater { |
| void update(ImageLayoutState &dst, const ImageLayoutState &src) const { |
| if (src.current_layout != kInvalidLayout && src.current_layout != dst.current_layout) { |
| dst.current_layout = src.current_layout; |
| } |
| } |
| std::optional<ImageLayoutState> insert(const ImageLayoutState &src) const { |
| return std::optional<ImageLayoutState>(vvl::in_place, src); |
| } |
| }; |
| sparse_container::splice(*cb_layout_map, *secondary_cb_layout_map, Updater()); |
| } |
| } |
| |
| secondary_cb_state->primary_command_buffer = VkHandle(); |
| linked_command_buffers.insert(secondary_cb_state.get()); |
| AddChild(secondary_cb_state); |
| |
| for (auto &event : secondary_cb_state->events) { |
| events.push_back(event); |
| } |
| |
| if (first_action_or_sync_command == Func::Empty) { |
| first_action_or_sync_command = secondary_cb_state->first_action_or_sync_command; |
| } |
| |
| // Handle secondary command buffer updates for dynamic rendering. |
| if (!has_render_pass_instance) { |
| resumes_render_pass_instance = secondary_cb_state->resumes_render_pass_instance; |
| } |
| has_render_pass_instance |= secondary_cb_state->has_render_pass_instance; |
| if (secondary_cb_state->last_suspend_state != SuspendState::Empty) { |
| last_suspend_state = secondary_cb_state->last_suspend_state; |
| } |
| if (!first_rendering_info.has_value() && secondary_cb_state->first_rendering_info.has_value()) { |
| first_rendering_info = secondary_cb_state->first_rendering_info; |
| first_rendering_info_loc = std::make_unique<LocationCapture>(secondary_cb_state->first_rendering_info_loc->Get()); |
| } |
| if (secondary_cb_state->last_rendering_info.has_value()) { |
| last_rendering_info = secondary_cb_state->last_rendering_info; |
| } |
| |
| // Handle debug labels |
| label_stack_depth_ += secondary_cb_state->label_stack_depth_; |
| label_commands_.insert(label_commands_.end(), secondary_cb_state->label_commands_.begin(), |
| secondary_cb_state->label_commands_.end()); |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordExecuteCommand(*secondary_cb_state, cmd_index, loc); |
| } |
| |
| cmd_index++; |
| } |
| } |
| |
| void CommandBuffer::PushDescriptorSetState(VkPipelineBindPoint pipelineBindPoint, |
| std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t set, |
| uint32_t descriptorWriteCount, const VkWriteDescriptorSet *pDescriptorWrites, |
| const Location &loc) { |
| // Short circuit invalid updates |
| if ((set >= pipeline_layout->set_layouts.list.size()) || !pipeline_layout->set_layouts.list[set] || |
| !pipeline_layout->set_layouts.list[set]->IsPushDescriptor()) { |
| return; |
| } |
| |
| // We need a descriptor set to update the bindings with, compatible with the passed layout |
| const auto &dsl = pipeline_layout->set_layouts.list[set]; |
| auto &last_bound = lastBound[ConvertToVvlBindPoint(pipelineBindPoint)]; |
| auto &push_descriptor_set = last_bound.push_descriptor_set; |
| // If we are disturbing the current push_desriptor_set clear it |
| if (!push_descriptor_set || !last_bound.IsBoundSetCompatible(set, *pipeline_layout)) { |
| last_bound.UnbindAndResetPushDescriptorSet(dev_data.CreatePushDescriptorSet(dsl)); |
| } |
| |
| UpdateLastBoundDescriptorSets(pipelineBindPoint, pipeline_layout, set, 1, nullptr, push_descriptor_set, 0, nullptr, loc); |
| |
| // Now that we have either the new or extant push_descriptor set ... do the write updates against it |
| push_descriptor_set->PerformPushDescriptorsUpdate(descriptorWriteCount, pDescriptorWrites); |
| } |
| |
| // Generic function to handle state update for all CmdDraw* type functions |
| void CommandBuffer::RecordDraw(const Location &loc) { |
| RecordCommand(loc); |
| LastBound &last_bound = lastBound[vvl::BindPointGraphics]; |
| for (auto &item : sub_states_) { |
| item.second->RecordActionCommand(last_bound, loc); |
| } |
| } |
| |
| // Generic function to handle state update for all CmdDispatch* type functions |
| void CommandBuffer::RecordDispatch(const Location &loc) { |
| RecordCommand(loc); |
| LastBound &last_bound = lastBound[vvl::BindPointCompute]; |
| for (auto &item : sub_states_) { |
| item.second->RecordActionCommand(last_bound, loc); |
| } |
| } |
| |
| // Generic function to handle state update for all CmdTraceRay* type functions |
| void CommandBuffer::RecordTraceRay(const Location &loc) { |
| RecordCommand(loc); |
| LastBound &last_bound = lastBound[vvl::BindPointRayTracing]; |
| for (auto &item : sub_states_) { |
| item.second->RecordActionCommand(last_bound, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordBindPipeline(VkPipelineBindPoint bind_point, vvl::Pipeline &pipeline) { |
| BindLastBoundPipeline(ConvertToVvlBindPoint(bind_point), &pipeline); |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordBindPipeline(bind_point, pipeline); |
| } |
| |
| if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| dynamic_state_status.pipeline.reset(); |
| |
| // Make a copy and then xor the new change |
| // This gives us which state has been invalidated, allows us to save time for most cases where nothing changes |
| CBDynamicFlags invalidated_state = dynamic_state_status.cb; |
| |
| // Spec: "[dynamic state] made invalid by another pipeline bind with that state specified as static" |
| // So unset the bitmask for the command buffer lifetime tracking (unless ignored, keep set) |
| dynamic_state_status.cb &= (pipeline.dynamic_state | pipeline.ignored_dynamic_state); |
| |
| invalidated_state ^= dynamic_state_status.cb; |
| if (invalidated_state.any()) { |
| // Reset dynamic state values |
| dynamic_state_value.reset(invalidated_state); |
| |
| for (int index = 1; index < CB_DYNAMIC_STATE_STATUS_NUM; ++index) { |
| CBDynamicState status = static_cast<CBDynamicState>(index); |
| if (invalidated_state[status]) { |
| invalidated_state_pipe[index] = pipeline.VkHandle(); |
| } |
| } |
| } |
| |
| if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT) && |
| !pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE) && pipeline.vertex_input_state) { |
| for (const auto &[binding_index, binding_state] : pipeline.vertex_input_state->bindings) { |
| current_vertex_buffer_binding_info[binding_index].stride = binding_state.desc.stride; |
| } |
| } |
| |
| if (!dev_data.enabled_features.variableMultisampleRate) { |
| if (const auto *multisample_state = pipeline.MultisampleState(); multisample_state) { |
| if (const auto &render_pass = active_render_pass) { |
| const uint32_t subpass = GetActiveSubpass(); |
| // if render pass uses no attachment, all bound pipelines in the same subpass must have the same |
| // pMultisampleState->rasterizationSamples. To check that, record pMultisampleState->rasterizationSamples of the |
| // first bound pipeline. |
| if (render_pass->UsesNoAttachment(subpass)) { |
| if (std::optional<VkSampleCountFlagBits> subpass_rasterization_samples = |
| GetActiveSubpassRasterizationSampleCount(); |
| !subpass_rasterization_samples) { |
| SetActiveSubpassRasterizationSampleCount(multisample_state->rasterizationSamples); |
| } |
| } |
| } |
| } |
| } |
| |
| } else if (bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) { |
| dynamic_state_status.rtx_stack_size_pipeline = false; |
| if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR)) { |
| dynamic_state_status.rtx_stack_size_cb = false; // invalidated |
| } |
| } |
| |
| dirty_static_state = false; |
| } |
| |
| // Helper for descriptor set (and buffer) updates. |
| static bool PushDescriptorCleanup(LastBound &last_bound, uint32_t set_idx) { |
| // All uses are from loops over ds_slots, but just in case.. |
| assert(set_idx < last_bound.ds_slots.size()); |
| |
| auto descriptor_set = last_bound.ds_slots[set_idx].ds_state.get(); |
| if (descriptor_set && descriptor_set->IsPushDescriptor()) { |
| assert(descriptor_set == last_bound.push_descriptor_set.get()); |
| last_bound.push_descriptor_set = nullptr; |
| return true; |
| } |
| return true; |
| } |
| |
| // Update pipeline_layout bind points applying the "Pipeline Layout Compatibility" rules. |
| // One of pDescriptorSets or push_descriptor_set should be nullptr, indicating whether this |
| // is called for CmdBindDescriptorSets or CmdPushDescriptorSet. |
| void CommandBuffer::UpdateLastBoundDescriptorSets(VkPipelineBindPoint pipeline_bind_point, |
| std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t first_set, |
| uint32_t set_count, const VkDescriptorSet *pDescriptorSets, |
| std::shared_ptr<vvl::DescriptorSet> &push_descriptor_set, |
| uint32_t dynamic_offset_count, const uint32_t *p_dynamic_offsets, |
| const Location &loc) { |
| ASSERT_AND_RETURN((pDescriptorSets == nullptr) ^ (push_descriptor_set == nullptr)); |
| |
| uint32_t required_size = first_set + set_count; |
| const uint32_t last_binding_index = required_size - 1; |
| ASSERT_AND_RETURN(last_binding_index < pipeline_layout->set_compat_ids.size()); |
| |
| auto &last_bound = lastBound[ConvertToVvlBindPoint(pipeline_bind_point)]; |
| last_bound.desc_set_pipeline_layout = pipeline_layout; |
| last_bound.desc_set_bound_command = loc.function; |
| last_bound.SetDescriptorMode(DescriptorModeClassic, loc.function); |
| auto &pipe_compat_ids = pipeline_layout->set_compat_ids; |
| // Resize binding arrays |
| if (last_binding_index >= last_bound.ds_slots.size()) { |
| last_bound.ds_slots.resize(required_size); |
| } |
| const uint32_t current_size = static_cast<uint32_t>(last_bound.ds_slots.size()); |
| |
| // Clean up the "disturbed" before and after the range to be set |
| if (required_size < current_size) { |
| if (last_bound.ds_slots[last_binding_index].compat_id_for_set != pipe_compat_ids[last_binding_index]) { |
| // We're disturbing those after last, we'll shrink below, but first need to check for and cleanup the push_descriptor |
| for (auto set_idx = required_size; set_idx < current_size; ++set_idx) { |
| if (PushDescriptorCleanup(last_bound, set_idx)) { |
| break; |
| } |
| } |
| } else { |
| // We're not disturbing past last, so leave the upper binding data alone. |
| required_size = current_size; |
| } |
| } |
| |
| // We resize if we need more set entries or if those past "last" are disturbed |
| if (required_size != current_size) { |
| last_bound.ds_slots.resize(required_size); |
| } |
| |
| // For any previously bound sets, need to set them to "invalid" if they were disturbed by this update |
| for (uint32_t set_idx = 0; set_idx < first_set; ++set_idx) { |
| auto &ds_slot = last_bound.ds_slots[set_idx]; |
| if (ds_slot.compat_id_for_set != pipe_compat_ids[set_idx]) { |
| PushDescriptorCleanup(last_bound, set_idx); |
| ds_slot.Reset(); |
| ds_slot.compat_id_for_set = pipe_compat_ids[set_idx]; |
| } |
| } |
| |
| // Now update the bound sets with the input sets |
| const uint32_t *input_dynamic_offsets = p_dynamic_offsets; // "read" pointer for dynamic offset data |
| for (uint32_t input_idx = 0; input_idx < set_count; input_idx++) { |
| auto set_idx = input_idx + first_set; // set_idx is index within layout, input_idx is index within input descriptor sets |
| auto &ds_slot = last_bound.ds_slots[set_idx]; |
| auto descriptor_set = |
| push_descriptor_set ? push_descriptor_set : dev_data.Get<vvl::DescriptorSet>(pDescriptorSets[input_idx]); |
| |
| ds_slot.Reset(); |
| // Record binding (or push) |
| if (descriptor_set != last_bound.push_descriptor_set) { |
| // Only cleanup the push descriptors if they aren't the currently used set. |
| PushDescriptorCleanup(last_bound, set_idx); |
| } |
| ds_slot.ds_state = descriptor_set; |
| ds_slot.compat_id_for_set = pipe_compat_ids[set_idx]; // compat ids are canonical *per* set index |
| |
| if (descriptor_set) { |
| auto set_dynamic_descriptor_count = descriptor_set->GetDynamicDescriptorCount(); |
| // TODO: Add logic for tracking push_descriptor offsets (here or in caller) |
| if (set_dynamic_descriptor_count && input_dynamic_offsets) { |
| const uint32_t *end_offset = input_dynamic_offsets + set_dynamic_descriptor_count; |
| ds_slot.dynamic_offsets = std::vector<uint32_t>(input_dynamic_offsets, end_offset); |
| input_dynamic_offsets = end_offset; |
| assert(input_dynamic_offsets <= (p_dynamic_offsets + dynamic_offset_count)); |
| } else { |
| ds_slot.dynamic_offsets.clear(); |
| } |
| } |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->UpdateLastBoundDescriptorSets(pipeline_bind_point, loc); |
| } |
| } |
| |
| void CommandBuffer::UpdateLastBoundDescriptorBuffers(VkPipelineBindPoint pipeline_bind_point, |
| std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t first_set, |
| uint32_t set_count, const uint32_t *buffer_indicies, |
| const VkDeviceSize *buffer_offsets) { |
| uint32_t required_size = first_set + set_count; |
| const uint32_t last_binding_index = required_size - 1; |
| assert(last_binding_index < pipeline_layout->set_compat_ids.size()); |
| |
| const vvl::BindPoint vvl_bind_point = ConvertToVvlBindPoint(pipeline_bind_point); |
| auto &last_bound = lastBound[vvl_bind_point]; |
| last_bound.desc_set_pipeline_layout = pipeline_layout; |
| auto &pipe_compat_ids = pipeline_layout->set_compat_ids; |
| // Resize binding arrays |
| if (last_binding_index >= last_bound.ds_slots.size()) { |
| last_bound.ds_slots.resize(required_size); |
| } |
| const uint32_t current_size = static_cast<uint32_t>(last_bound.ds_slots.size()); |
| |
| // Clean up the "disturbed" before and after the range to be set |
| if (required_size < current_size) { |
| if (last_bound.ds_slots[last_binding_index].compat_id_for_set != pipe_compat_ids[last_binding_index]) { |
| // We're disturbing those after last, we'll shrink below, but first need to check for and cleanup the push_descriptor |
| for (auto set_idx = required_size; set_idx < current_size; ++set_idx) { |
| if (PushDescriptorCleanup(last_bound, set_idx)) { |
| break; |
| } |
| } |
| } else { |
| // We're not disturbing past last, so leave the upper binding data alone. |
| required_size = current_size; |
| } |
| } |
| |
| // We resize if we need more set entries or if those past "last" are disturbed |
| if (required_size != current_size) { |
| last_bound.ds_slots.resize(required_size); |
| } |
| |
| // For any previously bound sets, need to set them to "invalid" if they were disturbed by this update |
| for (uint32_t set_idx = 0; set_idx < first_set; ++set_idx) { |
| PushDescriptorCleanup(last_bound, set_idx); |
| last_bound.ds_slots[set_idx].Reset(); |
| } |
| |
| // Now update the bound sets with the input sets |
| for (uint32_t input_idx = 0; input_idx < set_count; input_idx++) { |
| auto set_idx = input_idx + first_set; // set_idx is index within layout, input_idx is index within input descriptor sets |
| auto &ds_slot = last_bound.ds_slots[set_idx]; |
| ds_slot.Reset(); |
| |
| // Record binding |
| ds_slot.descriptor_buffer_binding = {buffer_indicies[input_idx], buffer_offsets[input_idx]}; |
| ds_slot.compat_id_for_set = pipe_compat_ids[set_idx]; // compat ids are canonical *per* set index |
| } |
| } |
| |
| // Set image layout for given subresource range |
| void CommandBuffer::SetImageLayout(const vvl::Image &image_state, const VkImageSubresourceRange &normalized_subresource_range, |
| VkImageLayout layout, VkImageLayout expected_layout) { |
| if (auto image_layout_map = GetOrCreateImageLayoutMap(image_state)) { |
| if (image_state.subresource_encoder.InRange(normalized_subresource_range)) { |
| RangeGenerator range_gen(image_state.subresource_encoder, normalized_subresource_range); |
| if (UpdateCurrentLayout(*image_layout_map, std::move(range_gen), layout, expected_layout, |
| normalized_subresource_range.aspectMask)) { |
| image_layout_change_count++; // Change the version of this data to force revalidation |
| } |
| } |
| } |
| } |
| |
| void CommandBuffer::TrackImageViewFirstLayout(const vvl::ImageView &view_state, VkImageLayout layout, |
| const char *submit_time_layout_mismatch_vuid) { |
| if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) { |
| RangeGenerator range_gen(view_state.range_generator); |
| TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, view_state.normalized_subresource_range.aspectMask, |
| submit_time_layout_mismatch_vuid); |
| } |
| } |
| |
| void CommandBuffer::TrackDepthAttachmentFirstLayout(const vvl::ImageView &view_state, VkImageLayout layout, |
| const char *submit_time_layout_mismatch_vuid) { |
| if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) { |
| // According to the spec for dynamic rendering depth attachment, we must ignore |
| // the aspect used to create the image view and use the DEPTH aspect instead |
| VkImageSubresourceRange image_layout_range = view_state.GetRangeGeneratorRange(dev_data.extensions); |
| image_layout_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; |
| RangeGenerator range_gen(view_state.image_state->subresource_encoder, image_layout_range); |
| |
| TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, VK_IMAGE_ASPECT_DEPTH_BIT, |
| submit_time_layout_mismatch_vuid); |
| } |
| } |
| |
| void CommandBuffer::TrackStencilAttachmentFirstLayout(const vvl::ImageView &view_state, VkImageLayout layout, |
| const char *submit_time_layout_mismatch_vuid) { |
| if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) { |
| // According to the spec for dynamic rendering stencil attachment, we must ignore |
| // the aspect used to create the image view and use the STENCIL aspect instead |
| VkImageSubresourceRange image_layout_range = view_state.GetRangeGeneratorRange(dev_data.extensions); |
| image_layout_range.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; |
| RangeGenerator range_gen(view_state.image_state->subresource_encoder, image_layout_range); |
| |
| TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, VK_IMAGE_ASPECT_STENCIL_BIT, |
| submit_time_layout_mismatch_vuid); |
| } |
| } |
| |
| void CommandBuffer::TrackImageFirstLayout(const vvl::Image &image_state, const VkImageSubresourceRange &subresource_range, |
| int32_t depth_offset, uint32_t depth_extent, VkImageLayout layout) { |
| if (auto image_layout_map = GetOrCreateImageLayoutMap(image_state)) { |
| VkImageSubresourceRange normalized_subresource_range = image_state.NormalizeSubresourceRange(subresource_range); |
| |
| if (depth_extent != 0 && CanTransitionDepthSlices(dev_data.extensions, image_state.create_info)) { |
| normalized_subresource_range.baseArrayLayer = (uint32_t)depth_offset; |
| normalized_subresource_range.layerCount = depth_extent; |
| } |
| if (image_state.subresource_encoder.InRange(normalized_subresource_range)) { |
| RangeGenerator range_gen(image_state.subresource_encoder, normalized_subresource_range); |
| TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, normalized_subresource_range.aspectMask, nullptr); |
| } |
| } |
| } |
| |
| void CommandBuffer::SetImageViewLayout(const vvl::ImageView &view_state, VkImageLayout layout, VkImageLayout layoutStencil) { |
| const vvl::Image *image_state = view_state.image_state.get(); |
| |
| VkImageSubresourceRange sub_range = view_state.GetRangeGeneratorRange(dev_data.extensions); |
| |
| if (sub_range.aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) && layoutStencil != kInvalidLayout) { |
| sub_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; |
| SetImageLayout(*image_state, sub_range, layout); |
| sub_range.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; |
| SetImageLayout(*image_state, sub_range, layoutStencil); |
| } else { |
| // If layoutStencil is kInvalidLayout (meaning no separate depth/stencil layout), image view format has both depth and |
| // stencil aspects, and subresource has only one of aspect out of depth or stencil, then the missing aspect will also be |
| // transitioned and thus must be included explicitly |
| if (const VkFormat format = view_state.create_info.format; vkuFormatIsDepthAndStencil(format)) { |
| if (layoutStencil == kInvalidLayout && |
| (sub_range.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) { |
| sub_range.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
| } |
| } |
| SetImageLayout(*image_state, sub_range, layout); |
| } |
| } |
| |
| void CommandBuffer::RecordStateCmd(CBDynamicState state) { |
| // NOTE: this can be extended to use RecordCommand for state commands if needed (currently not needed) |
| command_count++; |
| RecordDynamicState(state); |
| |
| vvl::Pipeline *pipeline = GetLastBoundGraphics().pipeline_state; |
| if (pipeline && !pipeline->IsDynamic(state)) { |
| dirty_static_state = true; |
| } |
| } |
| |
| void CommandBuffer::RecordDynamicState(CBDynamicState state) { |
| dynamic_state_status.cb.set(state); |
| dynamic_state_status.pipeline.set(state); |
| dynamic_state_status.history.set(state); |
| } |
| |
| void CommandBuffer::RecordSetViewport(uint32_t first_viewport, uint32_t viewport_count, const VkViewport *viewports) { |
| RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT); |
| if (dynamic_state_value.viewports.size() < first_viewport + viewport_count) { |
| dynamic_state_value.viewports.resize(first_viewport + viewport_count); |
| } |
| for (size_t i = 0; i < viewport_count; ++i) { |
| dynamic_state_value.viewports[first_viewport + i] = viewports[i]; |
| } |
| for (auto &item : sub_states_) { |
| item.second->RecordSetViewport(first_viewport, viewport_count); |
| } |
| } |
| |
| void CommandBuffer::RecordSetViewportWithCount(uint32_t viewport_count, const VkViewport *viewports) { |
| RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT); |
| dynamic_state_value.viewport_count = viewport_count; |
| dynamic_state_value.viewports.resize(viewport_count); |
| for (size_t i = 0; i < viewport_count; ++i) { |
| dynamic_state_value.viewports[i] = viewports[i]; |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordSetViewportWithCount(viewport_count); |
| } |
| } |
| |
| void CommandBuffer::RecordSetScissor(uint32_t first_scissor, uint32_t scissor_count) { |
| RecordStateCmd(CB_DYNAMIC_STATE_SCISSOR); |
| for (auto &item : sub_states_) { |
| item.second->RecordSetScissor(first_scissor, scissor_count); |
| } |
| } |
| |
| void CommandBuffer::RecordSetScissorWithCount(uint32_t scissor_count) { |
| RecordStateCmd(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT); |
| dynamic_state_value.scissor_count = scissor_count; |
| for (auto &item : sub_states_) { |
| item.second->RecordSetScissorWithCount(scissor_count); |
| } |
| } |
| |
| void CommandBuffer::RecordSetDepthCompareOp(VkCompareOp depth_compare_op) { |
| RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_COMPARE_OP); |
| for (auto &item : sub_states_) { |
| item.second->RecordSetDepthCompareOp(depth_compare_op); |
| } |
| } |
| void CommandBuffer::RecordSetDepthTestEnable(VkBool32 depth_test_enable) { |
| RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_TEST_ENABLE); |
| dynamic_state_value.depth_test_enable = depth_test_enable; |
| for (auto &item : sub_states_) { |
| item.second->RecordSetDepthTestEnable(depth_test_enable); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyBuffer(vvl::Buffer &src_buffer_state, vvl::Buffer &dst_buffer_state, uint32_t region_count, |
| const VkBufferCopy *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyBuffer(src_buffer_state, dst_buffer_state, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyBuffer2(vvl::Buffer &src_buffer_state, vvl::Buffer &dst_buffer_state, uint32_t region_count, |
| const VkBufferCopy2 *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyBuffer2(src_buffer_state, dst_buffer_state, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyImage(vvl::Image &src_image_state, vvl::Image &dst_image_state, VkImageLayout src_image_layout, |
| VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyImage(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions, |
| loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyImage2(vvl::Image &src_image_state, vvl::Image &dst_image_state, VkImageLayout src_image_layout, |
| VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy2 *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyImage2(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions, |
| loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyBufferToImage(vvl::Buffer &src_buffer_state, vvl::Image &dst_image_state, |
| VkImageLayout dst_image_layout, uint32_t region_count, const VkBufferImageCopy *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyBufferToImage(src_buffer_state, dst_image_state, dst_image_layout, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyBufferToImage2(vvl::Buffer &src_buffer_state, vvl::Image &dst_image_state, |
| VkImageLayout dst_image_layout, uint32_t region_count, |
| const VkBufferImageCopy2 *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyBufferToImage2(src_buffer_state, dst_image_state, dst_image_layout, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyImageToBuffer(vvl::Image &src_image_state, vvl::Buffer &dst_buffer_state, |
| VkImageLayout src_image_layout, uint32_t region_count, const VkBufferImageCopy *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyImageToBuffer(src_image_state, dst_buffer_state, src_image_layout, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordCopyImageToBuffer2(vvl::Image &src_image_state, vvl::Buffer &dst_buffer_state, |
| VkImageLayout src_image_layout, uint32_t region_count, |
| const VkBufferImageCopy2 *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordCopyImageToBuffer2(src_image_state, dst_buffer_state, src_image_layout, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordBlitImage(vvl::Image &src_image_state, vvl::Image &dst_image_state, VkImageLayout src_image_layout, |
| VkImageLayout dst_image_layout, uint32_t region_count, const VkImageBlit *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordBlitImage(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions, |
| loc); |
| } |
| } |
| |
| void CommandBuffer::RecordBlitImage2(vvl::Image &src_image_state, vvl::Image &dst_image_state, VkImageLayout src_image_layout, |
| VkImageLayout dst_image_layout, uint32_t region_count, const VkImageBlit2 *regions, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordBlitImage2(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions, |
| loc); |
| } |
| } |
| |
| void CommandBuffer::RecordResolveImage(vvl::Image &src_image_state, vvl::Image &dst_image_state, uint32_t region_count, |
| const VkImageResolve *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordResolveImage(src_image_state, dst_image_state, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordResolveImage2(vvl::Image &src_image_state, vvl::Image &dst_image_state, uint32_t region_count, |
| const VkImageResolve2 *regions, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordResolveImage2(src_image_state, dst_image_state, region_count, regions, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordClearColorImage(vvl::Image &image_state, VkImageLayout image_layout, |
| const VkClearColorValue *color_values, uint32_t range_count, |
| const VkImageSubresourceRange *ranges, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordClearColorImage(image_state, image_layout, color_values, range_count, ranges, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordClearDepthStencilImage(vvl::Image &image_state, VkImageLayout image_layout, |
| const VkClearDepthStencilValue *depth_stencil_values, uint32_t range_count, |
| const VkImageSubresourceRange *ranges, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordClearDepthStencilImage(image_state, image_layout, depth_stencil_values, range_count, ranges, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment *pAttachments, uint32_t rect_count, |
| const VkClearRect *pRects, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordClearAttachments(attachment_count, pAttachments, rect_count, pRects, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordFillBuffer(vvl::Buffer &buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordFillBuffer(buffer_state, offset, size, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordUpdateBuffer(vvl::Buffer &buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordUpdateBuffer(buffer_state, offset, size, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordSetEvent(VkEvent event, VkPipelineStageFlags2 stage_mask, const VkDependencyInfo *dependency_info, |
| const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordSetEvent(event, stage_mask, dependency_info); |
| } |
| |
| if (!dev_data.disabled[command_buffer_state]) { |
| if (auto event_state = dev_data.Get<vvl::Event>(event)) { |
| AddChild(event_state); |
| } |
| } |
| events.push_back(event); |
| if (!waited_events.count(event)) { |
| write_events_before_wait.push_back(event); |
| } |
| } |
| |
| void CommandBuffer::RecordResetEvent(VkEvent event, VkPipelineStageFlags2 stage_mask, const Location &loc) { |
| RecordCommand(loc); |
| for (auto &item : sub_states_) { |
| item.second->RecordResetEvent(event, stage_mask); |
| } |
| |
| if (!dev_data.disabled[command_buffer_state]) { |
| if (auto event_state = dev_data.Get<vvl::Event>(event)) { |
| AddChild(event_state); |
| } |
| } |
| events.push_back(event); |
| if (!waited_events.count(event)) { |
| write_events_before_wait.push_back(event); |
| } |
| } |
| |
| void CommandBuffer::RecordWaitEvents(uint32_t eventCount, const VkEvent *pEvents, VkPipelineStageFlags2 src_stage_mask, |
| const VkDependencyInfo *dependency_info, const Location &loc) { |
| for (auto &item : sub_states_) { |
| item.second->RecordWaitEvents(eventCount, pEvents, src_stage_mask, dependency_info, loc); |
| } |
| for (uint32_t i = 0; i < eventCount; ++i) { |
| const VkEvent event_hanle = pEvents[i]; |
| if (!dev_data.disabled[command_buffer_state]) { |
| if (auto event_state = dev_data.Get<vvl::Event>(event_hanle)) { |
| AddChild(event_state); |
| } |
| } |
| waited_events.insert(event_hanle); |
| events.push_back(event_hanle); |
| } |
| } |
| |
| void CommandBuffer::RecordBarrierObjects(uint32_t buffer_barrier_count, const VkBufferMemoryBarrier *buffer_barriers, |
| uint32_t image_barrier_count, const VkImageMemoryBarrier *image_barriers, |
| VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, |
| const Location &loc) { |
| if (!dev_data.disabled[command_buffer_state]) { |
| for (uint32_t i = 0; i < buffer_barrier_count; i++) { |
| if (auto buffer_state = dev_data.Get<vvl::Buffer>(buffer_barriers[i].buffer)) { |
| AddChild(buffer_state); |
| } |
| } |
| for (uint32_t i = 0; i < image_barrier_count; i++) { |
| if (auto image_state = dev_data.Get<vvl::Image>(image_barriers[i].image)) { |
| AddChild(image_state); |
| } |
| } |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordBarriers(buffer_barrier_count, buffer_barriers, image_barrier_count, image_barriers, src_stage_mask, |
| dst_stage_mask, loc); |
| } |
| } |
| |
| void CommandBuffer::RecordBarrierObjects(const VkDependencyInfo &dep_info, const Location &loc) { |
| if (!dev_data.disabled[command_buffer_state]) { |
| for (uint32_t i = 0; i < dep_info.bufferMemoryBarrierCount; i++) { |
| if (auto buffer_state = dev_data.Get<vvl::Buffer>(dep_info.pBufferMemoryBarriers[i].buffer)) { |
| AddChild(buffer_state); |
| } |
| } |
| for (uint32_t i = 0; i < dep_info.imageMemoryBarrierCount; i++) { |
| if (auto image_state = dev_data.Get<vvl::Image>(dep_info.pImageMemoryBarriers[i].image)) { |
| AddChild(image_state); |
| } |
| } |
| } |
| |
| // TODO - When moving here, these were not in CoreCheck, need to understand if we want SetEvents or not to validate the same |
| // things as WaitEvent/PipelineBarriers |
| if (loc.function != vvl::Func::vkCmdSetEvent2 && loc.function != vvl::Func::vkCmdSetEvent2KHR) { |
| for (auto &item : sub_states_) { |
| item.second->RecordBarriers2(dep_info, loc); |
| } |
| } |
| } |
| |
| void CommandBuffer::RecordPushConstants(const vvl::PipelineLayout &pipeline_layout_state, VkShaderStageFlags stage_flags, |
| uint32_t offset, uint32_t size, const void *values) { |
| // Discussed in details in https://github.com/KhronosGroup/Vulkan-Docs/issues/1081 |
| // Internal discussion and CTS were written to prove that this is not called after an incompatible vkCmdBindPipeline |
| // "Binding a pipeline with a layout that is not compatible with the push constant layout does not disturb the push constant |
| // values" |
| // |
| // vkCmdBindDescriptorSet has nothing to do with push constants and don't need to call this after neither |
| // |
| // Part of this assumes apps at draw/dispatch/traceRays/etc time will have it properly compatible or else other VU will be |
| // triggered |
| if (push_constant_ranges_layout != pipeline_layout_state.push_constant_ranges_layout) { |
| push_constant_ranges_layout = pipeline_layout_state.push_constant_ranges_layout; |
| for (auto &item : sub_states_) { |
| item.second->ClearPushConstants(); |
| } |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->RecordPushConstants(pipeline_layout_state.VkHandle(), stage_flags, offset, size, values); |
| } |
| } |
| |
| void CommandBuffer::RecordCmdPushDataEXT(const VkPushDataInfoEXT& push_data_info, const Location& loc) { |
| RecordCommand(loc); |
| if (push_data_info.data.size > 0) { |
| const size_t begin = push_data_info.offset; |
| const size_t end = push_data_info.offset + push_data_info.data.size; |
| if (descriptor_heap.push_data.size() < end) { |
| descriptor_heap.push_data.resize(end); |
| } |
| memset(descriptor_heap.push_data.data() + begin, 1, end - begin); |
| } |
| } |
| |
| void CommandBuffer::RecordBeginConditionalRendering(const Location &loc) { |
| RecordCommand(loc); |
| conditional_rendering_active = true; |
| conditional_rendering_inside_render_pass = active_render_pass != nullptr; |
| conditional_rendering_subpass = GetActiveSubpass(); |
| } |
| |
| void CommandBuffer::RecordEndConditionalRendering(const Location &loc) { |
| RecordCommand(loc); |
| conditional_rendering_active = false; |
| conditional_rendering_inside_render_pass = false; |
| conditional_rendering_subpass = 0; |
| } |
| |
| void CommandBuffer::RecordSetRenderingAttachmentLocations(const VkRenderingAttachmentLocationInfo *pLocationInfo, |
| const Location &loc) { |
| RecordCommand(loc); |
| rendering_attachments.set_color_locations = true; |
| SetRenderingAttachmentLocations(rendering_attachments, pLocationInfo); |
| } |
| |
| void CommandBuffer::RecordSetRenderingInputAttachmentIndices(const VkRenderingInputAttachmentIndexInfo *pLocationInfo, |
| const Location &loc) { |
| RecordCommand(loc); |
| rendering_attachments.set_color_indexes = true; |
| SetRenderingInputAttachmentIndices(rendering_attachments, pLocationInfo); |
| } |
| |
| void CommandBuffer::SubmitTimeValidate(Queue &queue_state, uint32_t perf_submit_pass, const Location &loc) { |
| for (const auto &it : video_session_updates) { |
| auto video_session_state = dev_data.Get<vvl::VideoSession>(it.first); |
| auto device_state = video_session_state->DeviceStateWrite(); |
| for (const auto &function : it.second) { |
| function(video_session_state.get(), *device_state, /*do_validate*/ false); |
| } |
| } |
| |
| for (auto &item : sub_states_) { |
| item.second->Submit(queue_state, perf_submit_pass, loc); |
| } |
| } |
| |
| uint32_t CommandBuffer::GetDynamicRenderingColorAttachmentCount() const { |
| return active_render_pass ? active_render_pass->dynamic_rendering_color_attachment_count : 0; |
| } |
| |
| uint32_t CommandBuffer::GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type type) const { |
| // The first indexes are the color attachments, multiply by 2 as each has a resolve attachment index |
| const uint32_t color_offset = 2 * GetDynamicRenderingColorAttachmentCount(); |
| switch (type) { |
| case AttachmentInfo::Type::Depth: |
| return color_offset; |
| case AttachmentInfo::Type::DepthResolve: |
| return color_offset + 1; |
| case AttachmentInfo::Type::Stencil: |
| return color_offset + 2; |
| case AttachmentInfo::Type::StencilResolve: |
| return color_offset + 3; |
| case AttachmentInfo::Type::FragmentDensityMap: |
| return color_offset + 4; |
| case AttachmentInfo::Type::FragmentShadingRate: |
| return color_offset + 5; |
| default: |
| assert(false); |
| } |
| return 0; |
| } |
| |
| uint32_t CommandBuffer::GetViewMask() const { |
| if (!active_render_pass) { |
| return 0; |
| } else if (active_render_pass->UsesDynamicRendering()) { |
| return active_render_pass->GetDynamicRenderingViewMask(); |
| } else { |
| return active_render_pass->create_info.pSubpasses[GetActiveSubpass()].viewMask; |
| } |
| } |
| |
| bool CommandBuffer::HasExternalFormatResolveAttachment() const { |
| if (active_render_pass && active_render_pass->use_dynamic_rendering && |
| active_render_pass->dynamic_rendering_begin_rendering_info.colorAttachmentCount > 0) { |
| return active_render_pass->dynamic_rendering_begin_rendering_info.pColorAttachments->resolveMode == |
| VK_RESOLVE_MODE_EXTERNAL_FORMAT_DOWNSAMPLE_BIT_ANDROID; |
| } |
| return false; |
| } |
| |
| void CommandBuffer::BindShader(VkShaderStageFlagBits shader_stage, vvl::ShaderObject *shader_object_state) { |
| auto &last_bound_state = lastBound[ConvertStageToVvlBindPoint(shader_stage)]; |
| const auto stage_index = static_cast<uint32_t>(VkShaderStageToShaderObjectStage(shader_stage)); |
| last_bound_state.shader_object_bound[stage_index] = true; |
| last_bound_state.shader_object_states[stage_index] = shader_object_state; |
| } |
| |
| // Only called for Graphics and during Multiview |
| // "When multiview is enabled, at the beginning of each subpass all non-render pass state is undefined." |
| void CommandBuffer::UnbindResources() { |
| // Vertex and index buffers |
| index_buffer_binding = {}; |
| current_vertex_buffer_binding_info.clear(); |
| |
| // Push constants |
| push_constant_ranges_layout.reset(); |
| |
| // Reset status of graphics cb to force rebinding of all resources |
| dynamic_state_status.cb.reset(); |
| dynamic_state_status.pipeline.reset(); |
| dynamic_state_status.history.reset(); |
| |
| // Pipeline and descriptor sets |
| lastBound[vvl::BindPointGraphics].Reset(); |
| } |
| |
| LogObjectList CommandBuffer::GetObjectList(VkShaderStageFlagBits stage) const { |
| LogObjectList objlist(handle_); |
| const auto &last_bound = lastBound[ConvertStageToVvlBindPoint(stage)]; |
| const auto *pipeline_state = last_bound.pipeline_state; |
| |
| if (pipeline_state) { |
| objlist.add(pipeline_state->Handle()); |
| } else if (VkShaderEXT shader = last_bound.GetShaderObject(VkShaderStageToShaderObjectStage(stage))) { |
| objlist.add(shader); |
| } |
| return objlist; |
| } |
| |
| LogObjectList CommandBuffer::GetObjectList(VkPipelineBindPoint pipeline_bind_point) const { |
| LogObjectList objlist(handle_); |
| |
| const auto &last_bound = lastBound[ConvertToVvlBindPoint(pipeline_bind_point)]; |
| const auto *pipeline_state = last_bound.pipeline_state; |
| |
| if (pipeline_state) { |
| objlist.add(pipeline_state->Handle()); |
| } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) { |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::COMPUTE)) { |
| objlist.add(shader); |
| } |
| } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| // If using non-compute, need to check all graphics stages |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::VERTEX)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TESSELLATION_CONTROL)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TESSELLATION_EVALUATION)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::GEOMETRY)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::FRAGMENT)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::MESH)) { |
| objlist.add(shader); |
| } |
| if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TASK)) { |
| objlist.add(shader); |
| } |
| } |
| |
| // If using dynamic rendering, will just not add anything |
| if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS && active_render_pass) { |
| objlist.add(active_render_pass->Handle()); |
| } |
| |
| return objlist; |
| } |
| |
| void CommandBuffer::BeginLabel(const char *label_name) { |
| ++label_stack_depth_; |
| label_commands_.emplace_back(LabelCommand{true, label_name}); |
| } |
| |
| void CommandBuffer::EndLabel() { |
| --label_stack_depth_; |
| label_commands_.emplace_back(LabelCommand{false, std::string()}); |
| } |
| |
| void CommandBuffer::ReplayLabelCommands(const vvl::span<const LabelCommand> &label_commands, |
| std::vector<std::string> &label_stack) { |
| for (const LabelCommand &command : label_commands) { |
| if (command.begin) { |
| label_stack.emplace_back(command.label_name.empty() ? "(empty label)" : command.label_name); |
| } else if (!label_stack.empty()) { |
| // The above condition is needed for several reasons. On the primary command buffer level |
| // the labels are not necessary balanced. And if the empty stack is detected in the context |
| // where it is an error, then it will be reported by the core validation, but we still need |
| // a safety check. |
| label_stack.pop_back(); |
| } |
| } |
| } |
| |
| std::string CommandBuffer::GetDebugRegionName(const std::vector<LabelCommand> &label_commands, uint32_t label_command_index, |
| const std::vector<std::string> &initial_label_stack) { |
| if (label_command_index >= label_commands.size()) { |
| // Can happen due to core validation error when in-use command buffer was re-recorded. |
| // It's a bug if this happens in a valid vulkan program. |
| return {}; |
| } |
| auto label_commands_to_replay = vvl::make_span(label_commands.data(), label_command_index + 1); |
| auto label_stack = initial_label_stack; |
| vvl::CommandBuffer::ReplayLabelCommands(label_commands_to_replay, label_stack); |
| |
| // Build up complete debug region name from all enclosing regions |
| std::string debug_region; |
| for (const std::string &label_name : label_stack) { |
| if (!debug_region.empty()) { |
| debug_region += "::"; |
| } |
| debug_region += label_name; |
| } |
| return debug_region; |
| } |
| |
| std::string CommandBuffer::DescribeInvalidatedState(CBDynamicState dynamic_state) const { |
| std::ostringstream ss; |
| if (dynamic_state_status.history[dynamic_state] && !dynamic_state_status.cb[dynamic_state]) { |
| ss << " (There was a call to vkCmdBindPipeline"; |
| if (auto pipeline = dev_data.Get<vvl::Pipeline>(invalidated_state_pipe[dynamic_state])) { |
| ss << " with " << dev_data.FormatHandle(*pipeline); |
| } |
| ss << " that didn't have " << DynamicStateToString(dynamic_state) << " and invalidated the prior " |
| << DescribeDynamicStateCommand(dynamic_state) << " call)"; |
| } |
| if (GetActiveSubpass() != 0 && active_render_pass->has_multiview_enabled) { |
| ss << " (When multiview is enabled, vkCmdNextSubpass will invalidate all dynamic state)"; |
| } |
| return ss.str(); |
| } |
| |
| VulkanTypedHandle CommandBufferSubState::Handle() const { return base.Handle(); } |
| VkCommandBuffer CommandBufferSubState::VkHandle() const { return base.VkHandle(); } |
| |
| } // namespace vvl |