| /* |
| * Copyright (c) 2019-2026 Valve Corporation |
| * Copyright (c) 2019-2026 LunarG, Inc. |
| * |
| * 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 <vulkan/utility/vk_format_utils.h> |
| #include "sync/sync_command_buffer.h" |
| #include "error_message/error_location.h" |
| #include "sync/sync_image.h" |
| #include "sync/sync_op.h" |
| #include "sync/sync_reporting.h" |
| #include "sync/sync_validation.h" |
| #include "state_tracker/descriptor_sets.h" |
| #include "state_tracker/image_state.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/event_state.h" |
| #include "state_tracker/ray_tracing_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/shader_module.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "utils/image_utils.h" |
| #include "utils/math_utils.h" |
| #include "utils/text_utils.h" |
| |
| namespace syncval { |
| |
| constexpr VkImageAspectFlags kColorAspects = |
| VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT; |
| |
| struct ShaderStageAccesses { |
| SyncAccessIndex sampled_read; |
| SyncAccessIndex storage_read; |
| SyncAccessIndex storage_write; |
| SyncAccessIndex uniform_read; |
| SyncAccessIndex acceleration_structure_read; |
| }; |
| |
| // TODO: generate me |
| static ShaderStageAccesses GetShaderStageAccesses(VkShaderStageFlagBits shader_stage) { |
| static const vvl::unordered_map<VkShaderStageFlagBits, ShaderStageAccesses> map = { |
| // clang-format off |
| {VK_SHADER_STAGE_VERTEX_BIT, { |
| SYNC_VERTEX_SHADER_SHADER_SAMPLED_READ, |
| SYNC_VERTEX_SHADER_SHADER_STORAGE_READ, |
| SYNC_VERTEX_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_VERTEX_SHADER_UNIFORM_READ, |
| SYNC_VERTEX_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, { |
| SYNC_TESSELLATION_CONTROL_SHADER_SHADER_SAMPLED_READ, |
| SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_READ, |
| SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_TESSELLATION_CONTROL_SHADER_UNIFORM_READ, |
| SYNC_TESSELLATION_CONTROL_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, { |
| SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_SAMPLED_READ, |
| SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_READ, |
| SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_TESSELLATION_EVALUATION_SHADER_UNIFORM_READ, |
| SYNC_TESSELLATION_EVALUATION_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_GEOMETRY_BIT, { |
| SYNC_GEOMETRY_SHADER_SHADER_SAMPLED_READ, |
| SYNC_GEOMETRY_SHADER_SHADER_STORAGE_READ, |
| SYNC_GEOMETRY_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_GEOMETRY_SHADER_UNIFORM_READ, |
| SYNC_GEOMETRY_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_FRAGMENT_BIT, { |
| SYNC_FRAGMENT_SHADER_SHADER_SAMPLED_READ, |
| SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ, |
| SYNC_FRAGMENT_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_FRAGMENT_SHADER_UNIFORM_READ, |
| SYNC_FRAGMENT_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_COMPUTE_BIT, { |
| SYNC_COMPUTE_SHADER_SHADER_SAMPLED_READ, |
| SYNC_COMPUTE_SHADER_SHADER_STORAGE_READ, |
| SYNC_COMPUTE_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_COMPUTE_SHADER_UNIFORM_READ, |
| SYNC_COMPUTE_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_RAYGEN_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_ANY_HIT_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_MISS_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_INTERSECTION_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_CALLABLE_BIT_KHR, { |
| SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ, |
| SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE, |
| SYNC_RAY_TRACING_SHADER_UNIFORM_READ, |
| SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_TASK_BIT_EXT, { |
| SYNC_TASK_SHADER_EXT_SHADER_SAMPLED_READ, |
| SYNC_TASK_SHADER_EXT_SHADER_STORAGE_READ, |
| SYNC_TASK_SHADER_EXT_SHADER_STORAGE_WRITE, |
| SYNC_TASK_SHADER_EXT_UNIFORM_READ, |
| SYNC_TASK_SHADER_EXT_ACCELERATION_STRUCTURE_READ, |
| }}, |
| {VK_SHADER_STAGE_MESH_BIT_EXT, { |
| SYNC_MESH_SHADER_EXT_SHADER_SAMPLED_READ, |
| SYNC_MESH_SHADER_EXT_SHADER_STORAGE_READ, |
| SYNC_MESH_SHADER_EXT_SHADER_STORAGE_WRITE, |
| SYNC_MESH_SHADER_EXT_UNIFORM_READ, |
| SYNC_MESH_SHADER_EXT_ACCELERATION_STRUCTURE_READ, |
| }}, |
| // clang-format on |
| }; |
| auto it = map.find(shader_stage); |
| assert(it != map.end()); |
| return it->second; |
| } |
| |
| static AccessRange MakeRangeForVertexData(VkDeviceSize offset, uint32_t first_vertex, uint32_t vertex_count, |
| const VertexBindingState& vertex_binding) { |
| uint32_t element_size = 0; |
| for (const auto& [_, vertex_attrib] : vertex_binding.locations) { |
| element_size = std::max(element_size, vertex_attrib.desc.offset + GetVertexInputFormatSize(vertex_attrib.desc.format)); |
| } |
| const VkDeviceSize range_start = offset + (first_vertex * vertex_binding.desc.stride); |
| VkDeviceSize range_size = 0; |
| if (vertex_count > 0) { |
| // Take into account stride between elements but not after the last element. |
| range_size = (vertex_count - 1) * vertex_binding.desc.stride + element_size; |
| } |
| return MakeRange(range_start, range_size); |
| } |
| |
| static AccessRange MakeRangeForIndexData(VkDeviceSize offset, uint32_t first_index, uint32_t index_count, uint32_t index_size) { |
| const VkDeviceSize range_start = offset + (first_index * index_size); |
| const VkDeviceSize range_size = index_count * index_size; |
| return MakeRange(range_start, range_size); |
| } |
| |
| static AccessRange MakeRange(const vvl::BufferView& buf_view_state) { |
| return MakeRange(*buf_view_state.buffer_state.get(), buf_view_state.create_info.offset, buf_view_state.create_info.range); |
| } |
| |
| static SyncAccessIndex GetSyncStageAccessIndexsByDescriptorSet(VkDescriptorType descriptor_type, |
| const spirv::ResourceInterfaceVariable& variable, |
| VkShaderStageFlagBits stage_flag) { |
| if (!variable.IsAccessed()) { |
| return SYNC_ACCESS_INDEX_NONE; |
| } |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) { |
| assert(stage_flag == VK_SHADER_STAGE_FRAGMENT_BIT); |
| return SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ; |
| } |
| const auto stage_accesses = GetShaderStageAccesses(stage_flag); |
| |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) { |
| return stage_accesses.uniform_read; |
| } |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR || |
| descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV || |
| descriptor_type == VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV) { |
| return stage_accesses.acceleration_structure_read; |
| } |
| |
| // If the desriptorSet is writable, we don't need to care SHADER_READ. SHADER_WRITE is enough. |
| // Because if write hazard happens, read hazard might or might not happen. |
| // But if write hazard doesn't happen, read hazard is impossible to happen. |
| if (variable.IsWrittenTo()) { |
| return stage_accesses.storage_write; |
| } else if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || |
| descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || |
| descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER) { |
| return stage_accesses.sampled_read; |
| } else { |
| if (variable.IsImage() && !variable.IsImageReadFrom()) { |
| // only image descriptor was accessed, not the image data |
| return SYNC_ACCESS_INDEX_NONE; |
| } |
| return stage_accesses.storage_read; |
| } |
| } |
| |
| static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage, |
| const VkImageSubresourceRange& subresource_range, const ResourceUsageTag& tag) { |
| const auto& sub_state = SubState(image); |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false); |
| access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag}); |
| } |
| |
| static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage, |
| const VkImageSubresourceRange& subresource_range, const VkOffset3D& offset, |
| const VkExtent3D& extent, ResourceUsageTagEx tag_ex) { |
| const auto& sub_state = SubState(image); |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, offset, extent, false); |
| access_context.UpdateAccessState(range_gen, current_usage, tag_ex); |
| } |
| |
| static void UpdateVideoAccessState(AccessContext& access_context, const vvl::VideoSession& vs_state, |
| const vvl::VideoPictureResource& resource, SyncAccessIndex current_usage, ResourceUsageTag tag) { |
| const auto image = static_cast<const vvl::Image*>(resource.image_state.get()); |
| const auto offset = resource.GetEffectiveImageOffset(vs_state); |
| const auto extent = resource.GetEffectiveImageExtent(vs_state); |
| const auto& sub_state = SubState(*image); |
| ImageRangeGen range_gen(sub_state.MakeImageRangeGen(resource.range, offset, extent, false)); |
| access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag}); |
| } |
| |
| CommandExecutionContext::CommandExecutionContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags) |
| : sync_state_(sync_validator), error_messages_(sync_validator.error_messages_), queue_flags_(queue_flags) {} |
| |
| bool CommandExecutionContext::ValidForSyncOps() const { |
| const bool valid = GetCurrentEventsContext() && GetCurrentAccessContext(); |
| assert(valid); |
| return valid; |
| } |
| |
| CommandBufferAccessContext::CommandBufferAccessContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags) |
| : CommandExecutionContext(sync_validator, queue_flags), |
| cb_state_(), |
| access_log_(std::make_shared<AccessLog>()), |
| cbs_referenced_(std::make_shared<CommandBufferSet>()), |
| command_number_(0), |
| reset_count_(0), |
| cb_access_context_(sync_validator), |
| current_context_(&cb_access_context_), |
| events_context_(), |
| render_pass_contexts_(), |
| current_renderpass_context_(), |
| sync_ops_() {} |
| |
| CommandBufferAccessContext::CommandBufferAccessContext(SyncValidator& sync_validator, vvl::CommandBuffer* cb_state) |
| : CommandBufferAccessContext(sync_validator, cb_state->GetQueueFlags()) { |
| cb_state_ = cb_state; |
| sync_state_.stats.AddCommandBufferContext(); |
| } |
| |
| // NOTE: Make sure the proxy doesn't outlive from, as the proxy is pointing directly to access contexts owned by from. |
| CommandBufferAccessContext::CommandBufferAccessContext(const CommandBufferAccessContext& from, AsProxyContext dummy) |
| : CommandBufferAccessContext(from.sync_state_, from.cb_state_->GetQueueFlags()) { |
| // Copy only the needed fields out of from for a temporary, proxy command buffer context |
| cb_state_ = from.cb_state_; |
| access_log_ = std::make_shared<AccessLog>(*from.access_log_); // potentially large, but no choice given tagging lookup. |
| command_number_ = from.command_number_; |
| reset_count_ = from.reset_count_; |
| |
| handles_ = from.handles_; |
| sync_state_.stats.AddHandleRecord((uint32_t)from.handles_.size()); |
| |
| const auto* from_context = from.GetCurrentAccessContext(); |
| assert(from_context); |
| |
| // Construct a fully resolved single access context out of from |
| cb_access_context_.ResolveFromContext(*from_context); |
| // The proxy has flatten the current render pass context (if any), but the async contexts are needed for hazard detection |
| cb_access_context_.ImportAsyncContexts(*from_context); |
| |
| events_context_ = from.events_context_; |
| |
| // We don't want to copy the full render_pass_context_ history just for the proxy. |
| sync_state_.stats.AddCommandBufferContext(); |
| } |
| |
| CommandBufferAccessContext::~CommandBufferAccessContext() { |
| sync_state_.stats.RemoveCommandBufferContext(); |
| sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size()); |
| } |
| |
| void CommandBufferAccessContext::Reset() { |
| access_log_ = std::make_shared<AccessLog>(); |
| cbs_referenced_ = std::make_shared<CommandBufferSet>(); |
| if (cb_state_) { |
| cbs_referenced_->push_back(cb_state_->shared_from_this()); |
| } |
| sync_ops_.clear(); |
| command_number_ = 0; |
| reset_count_++; |
| |
| sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size()); |
| handles_.clear(); |
| |
| current_command_tag_ = vvl::kNoIndex32; |
| cb_access_context_.Reset(); |
| render_pass_contexts_.clear(); |
| current_context_ = &cb_access_context_; |
| current_renderpass_context_ = nullptr; |
| events_context_.Clear(); |
| dynamic_rendering_info_.reset(); |
| } |
| |
| bool CommandBufferAccessContext::ValidateBeginRendering(const ErrorObject& error_obj, BeginRenderingCmdState& cmd_state) const { |
| bool skip = false; |
| const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo(); |
| |
| // Load operations do not happen when resuming |
| if (info.info.flags & VK_RENDERING_RESUMING_BIT) { |
| return skip; |
| } |
| |
| // Need to hazard detect load operations vs. the attachment views |
| for (size_t i = 0; i < info.attachments.size(); i++) { |
| const auto& attachment = info.attachments[i]; |
| const SyncAccessIndex load_index = attachment.GetLoadUsage(); |
| if (load_index == SYNC_ACCESS_INDEX_NONE) { |
| continue; |
| } |
| |
| const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp); |
| ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask); |
| const HazardResult hazard = GetCurrentAccessContext()->DetectAttachmentHazard(range_gen, load_index, attachment_access); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle()); |
| |
| std::ostringstream ss; |
| ss << vvl::String(vvl::Field::pRenderingInfo) << "."; |
| ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields(); |
| ss << " (" << sync_state_.FormatHandle(attachment.view->Handle()); |
| ss << ", loadOp " << string_VkAttachmentLoadOp(attachment.info.loadOp) << ")"; |
| std::string resource_description = ss.str(); |
| |
| const std::string error = sync_state_.error_messages_.BeginRenderingError(hazard, *this, error_obj.location.function, |
| resource_description, attachment.info.loadOp); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error); |
| if (skip) { |
| break; |
| } |
| } |
| } |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordBeginRendering(BeginRenderingCmdState& cmd_state, const Location& loc) { |
| const auto tag = NextCommandTag(loc.function); |
| |
| const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo(); |
| if ((info.info.flags & VK_RENDERING_RESUMING_BIT) == 0) { |
| for (size_t i = 0; i < info.attachments.size(); i++) { |
| const DynamicRenderingInfo::Attachment& attachment = info.attachments[i]; |
| const SyncAccessIndex load_index = attachment.GetLoadUsage(); |
| if (load_index == SYNC_ACCESS_INDEX_NONE) { |
| continue; |
| } |
| ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask); |
| const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp); |
| GetCurrentAccessContext()->UpdateAttachmentAccessState(range_gen, load_index, attachment_access, |
| ResourceUsageTagEx{tag}); |
| } |
| } |
| dynamic_rendering_info_ = std::move(cmd_state.info); |
| } |
| |
| bool CommandBufferAccessContext::ValidateEndRendering(const ErrorObject& error_obj) const { |
| bool skip = false; |
| |
| // Only validate resolve and store if not suspending (as specified by BeginRendering) |
| if (!dynamic_rendering_info_ || (dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) { |
| return skip; |
| } |
| |
| for (uint32_t i = 0; i < (uint32_t)dynamic_rendering_info_->attachments.size(); i++) { |
| const auto& attachment = dynamic_rendering_info_->attachments[i]; |
| |
| auto attachment_description = [this, &error_obj, &attachment, i](const auto& view, std::ostringstream& ss) { |
| ss << vvl::String(vvl::Field::pRenderingInfo) << "."; |
| ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields(); |
| ss << " (" << sync_state_.FormatHandle(view->Handle()); |
| }; |
| |
| // The logic about whether to resolve is embedded in the Attachment constructor |
| if (attachment.resolve_gen) { |
| const bool is_color = attachment.type == AttachmentType::kColor; |
| const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder; |
| |
| const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead); |
| ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask); |
| HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, kResolveRead, resolve_read_access); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle()); |
| |
| std::ostringstream ss; |
| attachment_description(attachment.view, ss); |
| ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")"; |
| const std::string resource_description = ss.str(); |
| |
| const std::string error = sync_state_.error_messages_.EndRenderingResolveError( |
| hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, false); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error); |
| if (skip) { |
| break; |
| } |
| } |
| |
| const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite); |
| ImageRangeGen resolve_gen = *attachment.resolve_gen; |
| hazard = current_context_->DetectAttachmentHazard(resolve_gen, kResolveWrite, resolve_write_access); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), attachment.resolve_view->Handle()); |
| |
| std::ostringstream ss; |
| attachment_description(attachment.resolve_view, ss); |
| ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")"; |
| const std::string resource_description = ss.str(); |
| |
| const std::string error = sync_state_.error_messages_.EndRenderingResolveError( |
| hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, true); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error); |
| if (skip) { |
| break; |
| } |
| } |
| } |
| |
| const SyncAccessIndex store_access = attachment.GetStoreUsage(); |
| if (store_access != SYNC_ACCESS_INDEX_NONE) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp); |
| ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask); |
| |
| HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, store_access, attachment_access); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle()); |
| |
| std::ostringstream ss; |
| attachment_description(attachment.view, ss); |
| ss << ", storeOp " << string_VkAttachmentStoreOp(attachment.info.storeOp) << ")"; |
| const std::string resource_description = ss.str(); |
| |
| const std::string error = sync_state_.error_messages_.EndRenderingStoreError( |
| hazard, *this, error_obj.location.function, resource_description, attachment.info.storeOp); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error); |
| if (skip) { |
| break; |
| } |
| } |
| } |
| } |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordEndRendering(const RecordObject& record_obj) { |
| if (!dynamic_rendering_info_) { |
| return; |
| } |
| if ((dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) { |
| dynamic_rendering_info_.reset(); |
| return; |
| } |
| |
| auto store_tag = NextCommandTag(record_obj.location.function, SubCommandType::kStoreOp); |
| AccessContext& access_context = *GetCurrentAccessContext(); |
| |
| for (const auto& attachment : dynamic_rendering_info_->attachments) { |
| if (attachment.resolve_gen) { |
| const bool is_color = attachment.type == AttachmentType::kColor; |
| const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder; |
| |
| const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead); |
| ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask); |
| access_context.UpdateAttachmentAccessState(view_gen, kResolveRead, resolve_read_access, ResourceUsageTagEx{store_tag}); |
| |
| const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite); |
| ImageRangeGen resolve_gen = *attachment.resolve_gen; |
| access_context.UpdateAttachmentAccessState(resolve_gen, kResolveWrite, resolve_write_access, |
| ResourceUsageTagEx{store_tag}); |
| } |
| |
| const SyncAccessIndex store_index = attachment.GetStoreUsage(); |
| if (store_index != SYNC_ACCESS_INDEX_NONE) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp); |
| ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask); |
| access_context.UpdateAttachmentAccessState(view_gen, store_index, attachment_access, ResourceUsageTagEx{store_tag}); |
| } |
| } |
| current_render_pass_instance_id_++; |
| dynamic_rendering_info_.reset(); |
| } |
| |
| bool CommandBufferAccessContext::ValidateDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, |
| const Location& loc) const { |
| bool skip = false; |
| if (!sync_state_.syncval_settings.shader_accesses_heuristic) { |
| return skip; |
| } |
| const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)]; |
| const vvl::Pipeline* pipe = last_bound_state.pipeline_state; |
| const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots; |
| if (!pipe) { |
| return skip; |
| } |
| |
| using DescriptorClass = vvl::DescriptorClass; |
| using BufferDescriptor = vvl::BufferDescriptor; |
| using ImageDescriptor = vvl::ImageDescriptor; |
| using TexelDescriptor = vvl::TexelDescriptor; |
| |
| for (const auto& stage_state : pipe->stage_states) { |
| if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) { |
| continue; |
| } else if (!stage_state.HasSpirv()) { |
| continue; |
| } |
| for (const auto& variable : stage_state.entrypoint->resource_interface_variables) { |
| if (variable.decorations.set >= ds_slots.size()) { |
| // This should be caught by Core validation, but if core checks are disabled SyncVal should not crash. |
| continue; |
| } |
| const auto& ds_slot = ds_slots[variable.decorations.set]; |
| const auto* descriptor_set = ds_slot.ds_state.get(); |
| if (!descriptor_set) continue; |
| auto binding = descriptor_set->GetBinding(variable.decorations.binding); |
| if (!binding) continue; |
| const auto descriptor_type = binding->type; |
| SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage()); |
| |
| // Currently, validation of memory accesses based on declared descriptors can produce false-positives. |
| // The shader can decide not to do such accesses, it can perform accesses with more narrow scope |
| // (e.g. read access, when both reads and writes are allowed) or for an array of descriptors, not all |
| // elements are accessed in the general case. |
| // |
| // This workaround disables validation for the descriptor array case. |
| if (binding->count > 1) { |
| continue; |
| } |
| |
| for (uint32_t index = 0; index < binding->count; index++) { |
| const auto* descriptor = binding->GetDescriptor(index); |
| switch (descriptor->GetClass()) { |
| case DescriptorClass::ImageSampler: |
| case DescriptorClass::Image: { |
| if (descriptor->Invalid()) { |
| continue; |
| } |
| |
| // NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types. |
| const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor); |
| const auto* img_view_state = image_descriptor->GetImageViewState(); |
| VkImageLayout image_layout = image_descriptor->GetImageLayout(); |
| |
| if (img_view_state->is_depth_sliced) { |
| // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in |
| // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment. |
| // See: VUID 00343 |
| continue; |
| } |
| |
| HazardResult hazard; |
| |
| if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) { |
| const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent); |
| const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset); |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster); |
| hazard = current_context_->DetectAttachmentHazard(*img_view_state, offset, extent, sync_index, |
| attachment_access); |
| } else { |
| hazard = current_context_->DetectHazard(*img_view_state, sync_index); |
| } |
| |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), img_view_state->Handle(), pipe->Handle()); |
| const auto error = error_messages_.ImageDescriptorError( |
| hazard, *this, loc.function, sync_state_.FormatHandle(*img_view_state), *pipe, |
| variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index, |
| stage_state.GetStage(), image_layout); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| break; |
| } |
| case DescriptorClass::TexelBuffer: { |
| const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor); |
| if (texel_descriptor->Invalid()) { |
| continue; |
| } |
| const auto* buf_view_state = texel_descriptor->GetBufferViewState(); |
| const auto* buf_state = buf_view_state->buffer_state.get(); |
| const AccessRange range = MakeRange(*buf_view_state); |
| auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), buf_view_state->Handle(), pipe->Handle()); |
| const auto error = error_messages_.BufferDescriptorError( |
| hazard, *this, loc.function, sync_state_.FormatHandle(*buf_view_state), *pipe, |
| variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index, |
| stage_state.GetStage()); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| break; |
| } |
| case DescriptorClass::GeneralBuffer: { |
| const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor); |
| if (buffer_descriptor->Invalid()) { |
| continue; |
| } |
| VkDeviceSize offset = buffer_descriptor->GetOffset(); |
| if (vvl::IsDynamicDescriptor(descriptor_type)) { |
| const uint32_t dynamic_offset_index = |
| descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding); |
| if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) { |
| continue; // core validation error |
| } |
| offset += ds_slot.dynamic_offsets[dynamic_offset_index]; |
| } |
| const auto* buf_state = buffer_descriptor->GetBufferState(); |
| const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange()); |
| auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle()); |
| const auto error = error_messages_.BufferDescriptorError( |
| hazard, *this, loc.function, sync_state_.FormatHandle(*buf_state), *pipe, variable.decorations.set, |
| *descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage()); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| break; |
| } |
| case DescriptorClass::AccelerationStructure: { |
| const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor); |
| if (accel_descriptor->Invalid()) { |
| continue; |
| } |
| const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR(); |
| if (!accel) { |
| continue; |
| } |
| if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) { |
| const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize()); |
| auto hazard = current_context_->DetectHazard(*as_buffer.state, sync_index, range); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), as_buffer.state->Handle(), pipe->Handle()); |
| const std::string resource_description = sync_state_.FormatHandle(accel->Handle()); |
| const std::string error = error_messages_.AccelerationStructureDescriptorError( |
| hazard, *this, loc.function, resource_description, *pipe, variable.decorations.set, |
| *descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage()); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| } |
| break; |
| } |
| // TODO: INLINE_UNIFORM_BLOCK_EXT |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return skip; |
| } |
| |
| // TODO: Record structure repeats Validate. Unify this code, it was the source of bugs few times already. |
| void CommandBufferAccessContext::RecordDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, |
| const ResourceUsageTag tag) { |
| if (!sync_state_.syncval_settings.shader_accesses_heuristic) { |
| return; |
| } |
| |
| const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)]; |
| const vvl::Pipeline* pipe = last_bound_state.pipeline_state; |
| const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots; |
| if (!pipe) { |
| return; |
| } |
| |
| using DescriptorClass = vvl::DescriptorClass; |
| using BufferDescriptor = vvl::BufferDescriptor; |
| using ImageDescriptor = vvl::ImageDescriptor; |
| using TexelDescriptor = vvl::TexelDescriptor; |
| |
| for (const auto& stage_state : pipe->stage_states) { |
| if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) { |
| continue; |
| } else if (!stage_state.HasSpirv()) { |
| continue; |
| } |
| for (const auto& variable : stage_state.entrypoint->resource_interface_variables) { |
| if (variable.decorations.set >= ds_slots.size()) { |
| // This should be caught by Core validation, but if core checks are disabled SyncVal should not crash. |
| continue; |
| } |
| const auto& ds_slot = ds_slots[variable.decorations.set]; |
| const auto* descriptor_set = ds_slot.ds_state.get(); |
| if (!descriptor_set) continue; |
| auto binding = descriptor_set->GetBinding(variable.decorations.binding); |
| if (!binding) continue; |
| const auto descriptor_type = binding->type; |
| SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage()); |
| |
| // Do not update state for descriptor array (the same as in Validate function). |
| if (binding->count > 1) { |
| continue; |
| } |
| |
| for (uint32_t i = 0; i < binding->count; i++) { |
| const auto* descriptor = binding->GetDescriptor(i); |
| switch (descriptor->GetClass()) { |
| case DescriptorClass::ImageSampler: |
| case DescriptorClass::Image: { |
| // NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types. |
| const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor); |
| if (image_descriptor->Invalid()) { |
| continue; |
| } |
| const auto* img_view_state = image_descriptor->GetImageViewState(); |
| if (img_view_state->is_depth_sliced) { |
| // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in |
| // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment. |
| // See: VUID 00343 |
| continue; |
| } |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, img_view_state->image_state->Handle()); |
| if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) { |
| const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent); |
| const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset); |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster); |
| |
| ImageRangeGen range_gen(MakeImageRangeGen(*img_view_state, offset, extent)); |
| current_context_->UpdateAttachmentAccessState(range_gen, SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ, |
| attachment_access, tag_ex); |
| } else { |
| ImageRangeGen range_gen = MakeImageRangeGen(*img_view_state); |
| current_context_->UpdateAccessState(range_gen, sync_index, tag_ex); |
| } |
| break; |
| } |
| case DescriptorClass::TexelBuffer: { |
| const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor); |
| if (texel_descriptor->Invalid()) { |
| continue; |
| } |
| const auto* buf_view_state = texel_descriptor->GetBufferViewState(); |
| const auto* buf_state = buf_view_state->buffer_state.get(); |
| const AccessRange range = MakeRange(*buf_view_state); |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_view_state->Handle()); |
| current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex); |
| break; |
| } |
| case DescriptorClass::GeneralBuffer: { |
| const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor); |
| if (buffer_descriptor->Invalid()) { |
| continue; |
| } |
| VkDeviceSize offset = buffer_descriptor->GetOffset(); |
| if (vvl::IsDynamicDescriptor(descriptor_type)) { |
| const uint32_t dynamic_offset_index = |
| descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding); |
| if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) { |
| continue; // core validation error |
| } |
| offset += ds_slot.dynamic_offsets[dynamic_offset_index]; |
| } |
| const auto* buf_state = buffer_descriptor->GetBufferState(); |
| const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange()); |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle()); |
| current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex); |
| break; |
| } |
| case DescriptorClass::AccelerationStructure: { |
| const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor); |
| if (accel_descriptor->Invalid()) { |
| continue; |
| } |
| const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR(); |
| if (!accel) { |
| continue; |
| } |
| if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) { |
| const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize()); |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, accel->Handle()); |
| current_context_->UpdateAccessState(*as_buffer.state, sync_index, range, tag_ex); |
| } |
| break; |
| } |
| // TODO: INLINE_UNIFORM_BLOCK_EXT |
| default: |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| bool CommandBufferAccessContext::ValidateDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const Location& loc) const { |
| bool skip = false; |
| const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state; |
| if (!pipe) { |
| return skip; |
| } |
| |
| const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info; |
| const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT) |
| ? cb_state_->dynamic_state_value.vertex_bindings |
| : pipe->vertex_input_state->bindings; |
| |
| for (const auto& [_, binding_state] : vertex_bindings) { |
| const auto& binding_desc = binding_state.desc; |
| if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) { |
| // TODO: add support to determine range of instance level attributes |
| continue; |
| } |
| if (const vvl::VertexBufferBinding* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) { |
| // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45 |
| const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer()); |
| if (!buf_state) continue; // also skips if using nullDescriptor |
| |
| const AccessRange range = |
| MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state); |
| auto hazard = current_context_->DetectHazard(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle()); |
| const std::string resource_description = "vertex " + sync_state_.FormatHandle(*buf_state); |
| const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| } |
| } |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const ResourceUsageTag tag) { |
| const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state; |
| if (!pipe) { |
| return; |
| } |
| const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info; |
| const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT) |
| ? cb_state_->dynamic_state_value.vertex_bindings |
| : pipe->vertex_input_state->bindings; |
| |
| for (const auto& [_, binding_state] : vertex_bindings) { |
| const auto& binding_desc = binding_state.desc; |
| if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) { |
| // TODO: add support to determine range of instance level attributes |
| continue; |
| } |
| if (const auto* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) { |
| // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45 |
| const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer()); |
| if (!buf_state) continue; // also skips if using nullDescriptor |
| |
| const AccessRange range = |
| MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state); |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle()); |
| current_context_->UpdateAccessState(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range, tag_ex); |
| } |
| } |
| } |
| |
| bool CommandBufferAccessContext::ValidateDrawVertexIndex(uint32_t index_count, uint32_t firstIndex, const Location& loc) const { |
| bool skip = false; |
| const auto& index_binding = cb_state_->index_buffer_binding; |
| // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45 |
| const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer()); |
| if (!index_buf_state) return skip; |
| |
| const uint32_t index_size = IndexTypeByteSize(index_binding.index_type); |
| const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, index_count, index_size); |
| |
| auto hazard = current_context_->DetectHazard(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), index_buf_state->Handle()); |
| if (const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state) { |
| objlist.add(pipe->Handle()); |
| } |
| const std::string resource_description = "index " + sync_state_.FormatHandle(*index_buf_state); |
| const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| |
| // TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed |
| // versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement |
| // and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed). |
| // Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and |
| // then assume it is immutable (common scenario). |
| // skip |= ValidateDrawVertex(?, ?, loc); |
| |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordDrawVertexIndex(uint32_t indexCount, uint32_t firstIndex, const ResourceUsageTag tag) { |
| const auto& index_binding = cb_state_->index_buffer_binding; |
| // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45 |
| const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer()); |
| if (!index_buf_state) { |
| return; |
| } |
| |
| const uint32_t index_size = IndexTypeByteSize(index_binding.index_type); |
| const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, indexCount, index_size); |
| const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, index_buf_state->Handle()); |
| current_context_->UpdateAccessState(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range, tag_ex); |
| |
| // TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed |
| // versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement |
| // and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed). |
| // Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and |
| // then assume it is immutable (common scenario). |
| // RecordDrawVertex(?, ?, tag); |
| } |
| |
| bool CommandBufferAccessContext::ValidateDrawAttachment(const Location& loc) const { |
| bool skip = false; |
| if (current_renderpass_context_) { |
| skip |= current_renderpass_context_->ValidateDrawSubpassAttachment(*this, loc.function); |
| } else if (dynamic_rendering_info_) { |
| skip |= ValidateDrawDynamicRenderingAttachment(loc); |
| } |
| return skip; |
| } |
| |
| bool CommandBufferAccessContext::ValidateDrawDynamicRenderingAttachment(const Location& location) const { |
| bool skip = false; |
| const auto& last_bound_state = cb_state_->GetLastBoundGraphics(); |
| const auto* pipe = last_bound_state.pipeline_state; |
| if (!pipe || pipe->RasterizationDisabled()) return skip; |
| |
| const auto& list = pipe->fs_writable_output_location_list; |
| const auto& access_context = *GetCurrentAccessContext(); |
| |
| const DynamicRenderingInfo& info = *dynamic_rendering_info_; |
| for (const auto output_location : list) { |
| if (output_location >= info.info.colorAttachmentCount) { |
| continue; |
| } |
| const auto& attachment = info.attachments[output_location]; |
| if (!attachment.IsWriteable(last_bound_state)) { |
| continue; |
| } |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment); |
| ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask); |
| HazardResult hazard = |
| access_context.DetectAttachmentHazard(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access); |
| |
| if (hazard.IsHazard()) { |
| LogObjectList obj_list(cb_state_->Handle(), attachment.view->Handle()); |
| Location loc = attachment.GetLocation(location, output_location); |
| const std::string error = error_messages_.Error( |
| hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view), "DynamicRenderingAttachmentError"); |
| skip |= sync_state_.SyncError(hazard.Hazard(), obj_list, loc.dot(vvl::Field::imageView), error); |
| } |
| } |
| |
| // TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes |
| // PHASE1 TODO: Add layout based read/vs. write selection. |
| // PHASE1 TODO: Read operations for both depth and stencil are possible in the future. |
| // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode. |
| for (size_t i = info.info.colorAttachmentCount; i < info.attachments.size(); i++) { |
| const auto& attachment = info.attachments[i]; |
| bool writeable = attachment.IsWriteable(last_bound_state); |
| |
| if (writeable) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment); |
| ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask); |
| HazardResult hazard = access_context.DetectAttachmentHazard( |
| view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access); |
| |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle()); |
| Location loc = attachment.GetLocation(location); |
| const std::string error = |
| error_messages_.Error(hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view), |
| "DynamicRenderingAttachmentError"); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc.dot(vvl::Field::imageView), error); |
| } |
| } |
| } |
| |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordDrawAttachment(const ResourceUsageTag tag) { |
| if (current_renderpass_context_) { |
| current_renderpass_context_->RecordDrawSubpassAttachment(*cb_state_, tag); |
| } else if (dynamic_rendering_info_) { |
| RecordDrawDynamicRenderingAttachment(tag); |
| } |
| } |
| |
| void CommandBufferAccessContext::RecordDrawDynamicRenderingAttachment(ResourceUsageTag tag) { |
| const auto& last_bound_state = cb_state_->GetLastBoundGraphics(); |
| const auto* pipe = last_bound_state.pipeline_state; |
| if (!pipe || pipe->RasterizationDisabled()) { |
| return; |
| } |
| |
| const auto& list = pipe->fs_writable_output_location_list; |
| auto& access_context = *GetCurrentAccessContext(); |
| |
| const DynamicRenderingInfo& info = *dynamic_rendering_info_; |
| for (const auto output_location : list) { |
| if (output_location >= info.info.colorAttachmentCount) { |
| continue; |
| } |
| const auto& attachment = info.attachments[output_location]; |
| if (!attachment.IsWriteable(last_bound_state)) { |
| continue; |
| } |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment); |
| ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask); |
| access_context.UpdateAttachmentAccessState(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access, |
| ResourceUsageTagEx{tag}); |
| } |
| |
| // TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes |
| // PHASE1 TODO: Add layout based read/vs. write selection. |
| // PHASE1 TODO: Read operations for both depth and stencil are possible in the future. |
| // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode. |
| |
| const uint32_t attachment_count = static_cast<uint32_t>(info.attachments.size()); |
| for (uint32_t i = info.info.colorAttachmentCount; i < attachment_count; i++) { |
| const auto& attachment = info.attachments[i]; |
| bool writeable = attachment.IsWriteable(last_bound_state); |
| |
| if (writeable) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment); |
| ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask); |
| access_context.UpdateAttachmentAccessState(view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, |
| attachment_access, ResourceUsageTagEx{tag}); |
| } |
| } |
| } |
| |
| static VkImageAspectFlags GetAttachmentAspectsToClear(VkImageAspectFlags clear_aspect_mask, const vvl::ImageView& attachment_view, |
| bool separate_depth_stencil_attachment_access) { |
| // Check if clear request is valid. |
| const bool clear_color = (clear_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; |
| const bool clear_depth = (clear_aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; |
| const bool clear_stencil = (clear_aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; |
| if (!clear_color && !clear_depth && !clear_stencil) { |
| return 0; // nothing to clear |
| } |
| if (clear_color && (clear_depth || clear_stencil)) { |
| return 0; // according to spec it's not allowed |
| } |
| |
| // View's aspect mask is used only for color attachment. |
| // For depth/stencil attachment view aspect mask is ignored according to spec. |
| const VkImageAspectFlags view_aspect_mask = attachment_view.normalized_subresource_range.aspectMask; |
| |
| // Collect aspects that should be cleared. |
| VkImageAspectFlags aspects_to_clear = VK_IMAGE_ASPECT_NONE; |
| if (clear_color && (view_aspect_mask & kColorAspects) != 0) { |
| assert(CountSetBits(view_aspect_mask) == 1); |
| aspects_to_clear |= view_aspect_mask; |
| } |
| if (clear_depth && vkuFormatHasDepth(attachment_view.create_info.format)) { |
| aspects_to_clear |= VK_IMAGE_ASPECT_DEPTH_BIT; |
| } |
| if (clear_stencil && vkuFormatHasStencil(attachment_view.create_info.format)) { |
| aspects_to_clear |= VK_IMAGE_ASPECT_STENCIL_BIT; |
| } |
| |
| // Even if only depth or stencil aspect is specified we validate access to |
| // both depth and stencil aspects because they can be interleaved. |
| if (!separate_depth_stencil_attachment_access && (aspects_to_clear & kDepthStencilAspects) != 0) { |
| aspects_to_clear = kDepthStencilAspects; |
| } |
| return aspects_to_clear; |
| } |
| |
| static std::optional<VkImageSubresourceRange> RestrictSubresourceRangeToClearLayers( |
| const VkImageSubresourceRange& normalized_subresource_range, uint32_t clear_first_layer, uint32_t clear_layer_count) { |
| // Contract of this function |
| assert(normalized_subresource_range.layerCount != VK_REMAINING_ARRAY_LAYERS); |
| // According to spec |
| assert(clear_layer_count != VK_REMAINING_ARRAY_LAYERS); |
| |
| const uint32_t first = std::max(normalized_subresource_range.baseArrayLayer, clear_first_layer); |
| const uint32_t last_range = normalized_subresource_range.baseArrayLayer + normalized_subresource_range.layerCount; |
| const uint32_t last_clear = clear_first_layer + clear_layer_count; |
| const uint32_t last = std::min(last_range, last_clear); |
| |
| if (first >= last) { |
| return {}; |
| } |
| |
| std::optional<VkImageSubresourceRange> result = normalized_subresource_range; |
| result->baseArrayLayer = first; |
| result->layerCount = last - first; |
| return result; |
| } |
| |
| std::optional<CommandBufferAccessContext::ClearAttachmentInfo> CommandBufferAccessContext::GetClearAttachmentInfo( |
| const VkClearAttachment& clear_attachment, uint32_t clear_first_layer, uint32_t clear_layer_count) const { |
| const vvl::ImageView* attachment_view = nullptr; |
| if (current_renderpass_context_) { |
| attachment_view = current_renderpass_context_->GetClearAttachmentView(clear_attachment); |
| } else if (dynamic_rendering_info_) { |
| attachment_view = dynamic_rendering_info_->GetClearAttachmentView(clear_attachment); |
| } |
| if (!attachment_view) { |
| return {}; |
| } |
| |
| const bool separate_depth_stencil_attachment_access = |
| GetSyncState().device_state->enabled_features.maintenance7 && |
| GetSyncState().device_state->phys_dev_ext_props.maintenance7_props.separateDepthStencilAttachmentAccess; |
| const VkImageAspectFlags aspects = |
| GetAttachmentAspectsToClear(clear_attachment.aspectMask, *attachment_view, separate_depth_stencil_attachment_access); |
| if (!aspects) { |
| return {}; |
| } |
| |
| const auto subresource_range = |
| RestrictSubresourceRangeToClearLayers(attachment_view->normalized_subresource_range, clear_first_layer, clear_layer_count); |
| if (!subresource_range.has_value()) { |
| return {}; |
| } |
| |
| return ClearAttachmentInfo{*attachment_view, *subresource_range}; |
| } |
| |
| bool CommandBufferAccessContext::ValidateClearAttachment(const Location& loc, const VkClearAttachment& clear_attachment, |
| uint32_t clear_rect_index, const VkClearRect& clear_rect) const { |
| bool skip = false; |
| |
| const auto optional_info = GetClearAttachmentInfo(clear_attachment, clear_rect.baseArrayLayer, clear_rect.layerCount); |
| if (!optional_info) { |
| return skip; |
| } |
| const ClearAttachmentInfo& info = *optional_info; |
| const VkImageSubresourceRange subresource_range = info.subresource_range; |
| const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask; |
| const uint32_t view_mask = GetViewMask(); |
| const ImageSubState& sub_state = SubState(*info.attachment_view.image_state); |
| |
| // NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified |
| |
| // Validate Color clear |
| auto report_color_hazard = [this, &skip, &loc, &info](const HazardResult& hazard, const VkClearAttachment& clear_attachment, |
| uint32_t clear_rect_index, const VkClearRect& clear_rect) { |
| std::ostringstream ss; |
| ss << string_VkImageAspectFlags(clear_attachment.aspectMask); |
| ss << " aspect of color attachment " << clear_attachment.colorAttachment; |
| ss << " (" << sync_state_.FormatHandle(info.attachment_view) << ")"; |
| if (current_renderpass_context_) { |
| ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass(); |
| } |
| const std::string resource_description = ss.str(); |
| const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle()); |
| const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description, |
| clear_attachment.aspectMask, clear_rect_index, clear_rect); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| }; |
| if (aspects_to_clear & kColorAspects) { |
| // [core validation check]: if COLOR_ASPECT is included then PLANE aspects are not allowed, |
| // and if PLANE aspect is included then only one is allowed. |
| assert(CountSetBits(aspects_to_clear) == 1); |
| |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment); |
| if (view_mask == 0) { |
| HazardResult hazard = current_context_->DetectAttachmentHazard( |
| *info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced, |
| SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access); |
| if (hazard.IsHazard()) { |
| report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect); |
| } |
| } else { |
| const auto view_indices = GetSetBitIndices(view_mask); |
| const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range; |
| for (uint32_t view_index : view_indices) { |
| if (view_index < attachment_subresource.layerCount) { |
| VkImageSubresourceRange view_subresource = attachment_subresource; |
| view_subresource.baseArrayLayer += view_index; |
| view_subresource.layerCount = 1; |
| |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced); |
| HazardResult hazard = current_context_->DetectAttachmentHazard( |
| range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access); |
| if (hazard.IsHazard()) { |
| report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect); |
| } |
| } |
| } |
| } |
| } |
| |
| // Validate Depth-Stencil clear |
| auto report_depth_stencil_hazard = [this, &skip, &loc, &info](const HazardResult& hazard, |
| const VkClearAttachment& clear_attachment, |
| uint32_t clear_rect_index, const VkClearRect& clear_rect) { |
| std::ostringstream ss; |
| ss << string_VkImageAspectFlags(clear_attachment.aspectMask); |
| ss << " aspect(s) of depth-stencil attachment ("; |
| ss << sync_state_.FormatHandle(info.attachment_view) << ")"; |
| if (current_renderpass_context_) { |
| ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass(); |
| } |
| const std::string resource_description = ss.str(); |
| const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle()); |
| const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description, |
| clear_attachment.aspectMask, clear_rect_index, clear_rect); |
| skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error); |
| }; |
| if (aspects_to_clear & kDepthStencilAspects) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment); |
| |
| if (view_mask == 0) { |
| // vkCmdClearAttachments depth/stencil writes are executed by the EARLY_FRAGMENT_TESTS_BIT and LATE_FRAGMENT_TESTS_BIT |
| // stages. The implementation tracks the most recent access, which happens in the LATE_FRAGMENT_TESTS_BIT stage. |
| HazardResult hazard = current_context_->DetectAttachmentHazard( |
| *info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced, |
| SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access); |
| if (hazard.IsHazard()) { |
| report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect); |
| } |
| } else { |
| const auto view_indices = GetSetBitIndices(view_mask); |
| const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range; |
| for (uint32_t view_index : view_indices) { |
| if (view_index < attachment_subresource.layerCount) { |
| VkImageSubresourceRange view_subresource = attachment_subresource; |
| view_subresource.baseArrayLayer += view_index; |
| view_subresource.layerCount = 1; |
| |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced); |
| HazardResult hazard = current_context_->DetectAttachmentHazard( |
| range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access); |
| if (hazard.IsHazard()) { |
| report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect); |
| } |
| } |
| } |
| } |
| } |
| return skip; |
| } |
| |
| void CommandBufferAccessContext::RecordClearAttachment(ResourceUsageTag tag, const VkClearAttachment& clear_attachment, |
| const VkClearRect& rect) { |
| const auto optional_info = GetClearAttachmentInfo(clear_attachment, rect.baseArrayLayer, rect.layerCount); |
| if (!optional_info) { |
| return; |
| } |
| const ClearAttachmentInfo& info = *optional_info; |
| const VkImageSubresourceRange subresource_range = info.subresource_range; |
| const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask; |
| const uint32_t view_mask = GetViewMask(); |
| const ImageSubState& sub_state = SubState(*info.attachment_view.image_state); |
| |
| auto update_access_state = [this, aspects_to_clear, tag](ImageRangeGen& range_gen) { |
| if (aspects_to_clear & kColorAspects) { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment); |
| current_context_->UpdateAttachmentAccessState(range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, |
| attachment_access, ResourceUsageTagEx{tag}); |
| } else { |
| const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment); |
| current_context_->UpdateAttachmentAccessState(range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, |
| attachment_access, ResourceUsageTagEx{tag}); |
| } |
| }; |
| // NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified |
| if (view_mask == 0) { |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false); |
| update_access_state(range_gen); |
| } else { |
| const auto view_indices = GetSetBitIndices(view_mask); |
| const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range; |
| for (uint32_t view_index : view_indices) { |
| if (view_index < attachment_subresource.layerCount) { |
| VkImageSubresourceRange view_subresource = attachment_subresource; |
| view_subresource.baseArrayLayer += view_index; |
| view_subresource.layerCount = 1; |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, false); |
| update_access_state(range_gen); |
| } |
| } |
| } |
| } |
| |
| QueueId CommandBufferAccessContext::GetQueueId() const { return kQueueIdInvalid; } |
| |
| ResourceUsageTag CommandBufferAccessContext::RecordBeginRenderPass(vvl::Func command, const vvl::RenderPass& rp_state, |
| const VkRect2D& render_area, |
| const std::vector<const vvl::ImageView*>& attachment_views) { |
| // Create an access context the current renderpass. |
| const auto barrier_tag = NextCommandTag(command, SubCommandType::kSubpassTransition, 0); |
| AddCommandHandle(barrier_tag, rp_state.Handle()); |
| const auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, 0); |
| render_pass_contexts_.emplace_back(std::make_unique<RenderPassAccessContext>( |
| rp_state, render_area, GetQueueFlags(), attachment_views, cb_access_context_, current_render_pass_instance_id_)); |
| current_renderpass_context_ = render_pass_contexts_.back().get(); |
| current_renderpass_context_->RecordBeginRenderPass(barrier_tag, load_tag); |
| current_context_ = ¤t_renderpass_context_->CurrentContext(); |
| return barrier_tag; |
| } |
| |
| ResourceUsageTag CommandBufferAccessContext::RecordNextSubpass(vvl::Func command) { |
| assert(current_renderpass_context_); |
| if (!current_renderpass_context_) return NextCommandTag(command); |
| |
| // At this point current subpass value has not updated yet to the index of "next subpass" |
| const uint32_t previous_subpass = current_renderpass_context_->GetCurrentSubpass(); |
| const uint32_t this_subpass = previous_subpass + 1; |
| |
| auto resolve_tag = NextCommandTag(command, SubCommandType::kResolveOp, previous_subpass); |
| AddCommandHandle(resolve_tag, current_renderpass_context_->GetRenderPassState()->Handle()); |
| auto store_tag = NextSubCommandTag(command, SubCommandType::kStoreOp, previous_subpass); |
| auto transition_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition, this_subpass); |
| auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, this_subpass); |
| |
| current_renderpass_context_->RecordNextSubpass(resolve_tag, store_tag, transition_tag, load_tag); |
| current_context_ = ¤t_renderpass_context_->CurrentContext(); |
| return transition_tag; |
| } |
| |
| ResourceUsageTag CommandBufferAccessContext::RecordEndRenderPass(vvl::Func command) { |
| assert(current_renderpass_context_); |
| if (!current_renderpass_context_) return NextCommandTag(command); |
| |
| const uint32_t current_subpass = current_renderpass_context_->GetCurrentSubpass(); |
| |
| auto store_tag = NextCommandTag(command, SubCommandType::kStoreOp, current_subpass); |
| AddCommandHandle(store_tag, current_renderpass_context_->GetRenderPassState()->Handle()); |
| |
| auto barrier_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition); |
| |
| current_renderpass_context_->RecordEndRenderPass(&cb_access_context_, store_tag, barrier_tag); |
| current_context_ = &cb_access_context_; |
| current_renderpass_context_ = nullptr; |
| current_render_pass_instance_id_++; |
| return barrier_tag; |
| } |
| |
| void CommandBufferAccessContext::RecordDestroyEvent(vvl::Event* event_state) { GetCurrentEventsContext()->Destroy(event_state); } |
| |
| void CommandBufferAccessContext::RecordExecutedCommandBuffer(const CommandBufferAccessContext& recorded_cb_context) { |
| const AccessContext* recorded_context = recorded_cb_context.GetCurrentAccessContext(); |
| assert(recorded_context); |
| |
| // Just run through the barriers ignoring the usage from the recorded context, as Resolve will overwrite outdated state |
| const ResourceUsageTag base_tag = GetTagCount(); |
| for (const auto& sync_op : recorded_cb_context.GetSyncOps()) { |
| // we update the range to any include layout transition first use writes, |
| // as they are stored along with the source scope (as effective barrier) when recorded |
| sync_op.sync_op->ReplayRecord(*this, base_tag + sync_op.tag); |
| } |
| |
| ImportRecordedAccessLog(recorded_cb_context); |
| ResolveExecutedCommandBuffer(*recorded_context, base_tag); |
| } |
| |
| void CommandBufferAccessContext::ResolveExecutedCommandBuffer(const AccessContext& recorded_context, ResourceUsageTag offset) { |
| auto tag_offset = [offset](AccessState* access) { access->OffsetTag(offset); }; |
| GetCurrentAccessContext()->ResolveFromContext(tag_offset, recorded_context); |
| } |
| |
| void CommandBufferAccessContext::ImportRecordedAccessLog(const CommandBufferAccessContext& recorded_context) { |
| cbs_referenced_->emplace_back(recorded_context.GetCBStateShared()); |
| access_log_->insert(access_log_->end(), recorded_context.access_log_->cbegin(), recorded_context.access_log_->cend()); |
| |
| // Adjust command indices for the log records added from recorded_context. |
| const auto& recorded_label_commands = recorded_context.cb_state_->GetLabelCommands(); |
| const bool use_proxy = !proxy_label_commands_.empty(); |
| const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands(); |
| if (!label_commands.empty()) { |
| assert(label_commands.size() >= recorded_label_commands.size()); |
| const uint32_t command_offset = static_cast<uint32_t>(label_commands.size() - recorded_label_commands.size()); |
| for (size_t i = 0; i < recorded_context.access_log_->size(); i++) { |
| size_t index = (access_log_->size() - 1) - i; |
| assert((*access_log_)[index].label_command_index != vvl::kNoIndex32); |
| (*access_log_)[index].label_command_index += command_offset; |
| } |
| } |
| } |
| |
| ResourceUsageTag CommandBufferAccessContext::NextCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) { |
| command_number_++; |
| current_command_tag_ = access_log_->size(); |
| |
| ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass); |
| |
| if (!cb_state_->GetLabelCommands().empty()) { |
| record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1); |
| } |
| CheckCommandTagDebugCheckpoint(); |
| return current_command_tag_; |
| } |
| |
| ResourceUsageTag CommandBufferAccessContext::NextSubCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) { |
| const ResourceUsageTag tag = access_log_->size(); |
| ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass); |
| |
| // By default copy handle range from the main command, but can be overwritten with AddSubcommandHandle. |
| const auto& main_command_record = (*access_log_)[current_command_tag_]; |
| record.first_handle_index = main_command_record.first_handle_index; |
| record.handle_count = main_command_record.handle_count; |
| |
| if (!cb_state_->GetLabelCommands().empty()) { |
| record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1); |
| } |
| return tag; |
| } |
| |
| uint32_t CommandBufferAccessContext::AddHandle(const VulkanTypedHandle& typed_handle, uint32_t index) { |
| const uint32_t handle_index = static_cast<uint32_t>(handles_.size()); |
| handles_.emplace_back(HandleRecord(typed_handle, index)); |
| sync_state_.stats.AddHandleRecord(); |
| return handle_index; |
| } |
| |
| ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandle(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle) { |
| return AddCommandHandleIndexed(tag, typed_handle, vvl::kNoIndex32); |
| } |
| |
| ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle, |
| uint32_t index) { |
| assert(tag < access_log_->size()); |
| const uint32_t handle_index = AddHandle(typed_handle, index); |
| // TODO: the following range check is not needed. Test and remove. |
| if (tag < access_log_->size()) { |
| auto& record = (*access_log_)[tag]; |
| if (record.first_handle_index == vvl::kNoIndex32) { |
| record.first_handle_index = handle_index; |
| record.handle_count = 1; |
| } else { |
| // assert that command handles occupy continuous range |
| assert(handle_index - record.first_handle_index == record.handle_count); |
| record.handle_count++; |
| } |
| } |
| return {tag, handle_index}; |
| } |
| |
| void CommandBufferAccessContext::AddSubcommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle, |
| uint32_t index) { |
| assert(tag < access_log_->size()); |
| const uint32_t handle_index = AddHandle(typed_handle, index); |
| // TODO: the following range check is not needed. Test and remove. |
| if (tag < access_log_->size()) { |
| auto& record = (*access_log_)[tag]; |
| const auto& main_command_record = (*access_log_)[current_command_tag_]; |
| if (record.first_handle_index == main_command_record.first_handle_index) { |
| // override default behavior that subcommand references the same handles as the main command |
| record.first_handle_index = handle_index; |
| record.handle_count = 1; |
| } else { |
| // assert that command handles occupy continuous range |
| assert(handle_index - record.first_handle_index == record.handle_count); |
| record.handle_count++; |
| } |
| } |
| } |
| |
| std::string CommandBufferAccessContext::GetDebugRegionName(const ResourceUsageRecord& record) const { |
| const bool use_proxy = !proxy_label_commands_.empty(); |
| const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands(); |
| return vvl::CommandBuffer::GetDebugRegionName(label_commands, record.label_command_index); |
| } |
| |
| void CommandBufferAccessContext::RecordSyncOp(SyncOpPointer&& sync_op) { |
| auto tag = sync_op->Record(this); |
| // As renderpass operations can have side effects on the command buffer access context, |
| // update the sync operation to record these if any. |
| sync_ops_.emplace_back(tag, std::move(sync_op)); |
| } |
| |
| AttachmentAccess CommandBufferAccessContext::GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type) const { |
| AttachmentAccess attachment_access; |
| attachment_access.type = type; |
| attachment_access.ordering = ordering; |
| attachment_access.render_pass_instance_id = current_render_pass_instance_id_; |
| attachment_access.subpass = current_renderpass_context_ ? current_renderpass_context_->GetCurrentSubpass() : vvl::kNoIndex32; |
| return attachment_access; |
| } |
| |
| uint32_t CommandBufferAccessContext::GetViewMask() const { |
| if (dynamic_rendering_info_) { |
| return dynamic_rendering_info_->info.viewMask; |
| } else if (current_renderpass_context_) { |
| const auto& render_pass_ci = current_renderpass_context_->GetRenderPassState()->create_info; |
| const uint32_t subpass = current_renderpass_context_->GetCurrentSubpass(); |
| return render_pass_ci.pSubpasses[subpass].viewMask; |
| } else { |
| assert(false && "GetViewMask musk be called only during render pass instance"); |
| return 0; |
| } |
| } |
| |
| // NOTE: debug location reporting feature works only for reproducible application sessions |
| // (it uses command number/reset count from the error message from the previous session). |
| // It's considered experimental and can be replaced with a better way to report syncval debug locations. |
| // |
| // Logs informational message when vulkan command stream reaches a specific location. |
| // The message can be intercepted by the reporting routines. For example, the message handler can trigger a breakpoint. |
| // The location can be specified through environment variables. |
| // VK_SYNCVAL_DEBUG_COMMAND_NUMBER: the command number |
| // VK_SYNCVAL_DEBUG_RESET_COUNT: (optional, default value is 1) command buffer reset count |
| // VK_SYNCVAL_DEBUG_CMDBUF_PATTERN: (optional, empty string by default) pattern to match command buffer debug name |
| void CommandBufferAccessContext::CheckCommandTagDebugCheckpoint() { |
| auto get_cmdbuf_name = [](const DebugReport& debug_report, uint64_t cmdbuf_handle) { |
| std::unique_lock<std::mutex> lock(debug_report.debug_output_mutex); |
| std::string object_name = debug_report.GetUtilsObjectNameNoLock(cmdbuf_handle); |
| if (object_name.empty()) { |
| object_name = debug_report.GetMarkerObjectNameNoLock(cmdbuf_handle); |
| } |
| text::ToLower(object_name); |
| return object_name; |
| }; |
| if (sync_state_.debug_command_number == command_number_ && sync_state_.debug_reset_count == reset_count_) { |
| const auto cmdbuf_name = get_cmdbuf_name(*sync_state_.debug_report, cb_state_->Handle().handle); |
| const auto& pattern = sync_state_.debug_cmdbuf_pattern; |
| const bool cmdbuf_match = pattern.empty() || (cmdbuf_name.find(pattern) != std::string::npos); |
| if (cmdbuf_match) { |
| sync_state_.LogInfo("SYNCVAL_DEBUG_COMMAND", LogObjectList(), Location(access_log_->back().command), |
| "Command stream has reached command #%" PRIu32 " in command buffer %s with reset count #%" PRIu32, |
| sync_state_.debug_command_number, sync_state_.FormatHandle(cb_state_->Handle()).c_str(), |
| sync_state_.debug_reset_count); |
| } |
| } |
| } |
| |
| void UpdateAccessMapStats(const AccessMap& access_map, AccessContextStats& stats); |
| |
| void CommandBufferAccessContext::UpdateStats(AccessStats& access_stats) const { |
| #if VVL_ENABLE_SYNCVAL_STATS != 0 |
| UpdateAccessMapStats(cb_access_context_.GetAccessMap(), access_stats.cb_access_stats); |
| |
| for (const auto& render_pass_context : render_pass_contexts_) { |
| for (const AccessContext& subpass_access_context : render_pass_context->GetSubpassContexts()) { |
| UpdateAccessMapStats(subpass_access_context.GetAccessMap(), access_stats.subpass_access_stats); |
| } |
| } |
| #endif |
| } |
| |
| CommandBufferSubState::CommandBufferSubState(SyncValidator& dev, vvl::CommandBuffer& cb) |
| : vvl::CommandBufferSubState(cb), access_context(dev, &cb) { |
| access_context.SetSelfReference(); |
| } |
| |
| void CommandBufferSubState::End() { |
| access_context.GetCurrentAccessContext()->Finalize(); |
| |
| // For threads that are dedicated to recording command buffers but do not submit themselves, |
| // the end of recording is a logical point to update memory stats |
| access_context.GetSyncState().stats.UpdateMemoryStats(); |
| } |
| |
| void CommandBufferSubState::Destroy() { |
| access_context.Destroy(); // must be first to clean up self references correctly. |
| } |
| |
| void CommandBufferSubState::Reset(const Location& loc) { access_context.Reset(); } |
| |
| void CommandBufferSubState::NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) { |
| for (auto& obj : invalid_nodes) { |
| switch (obj->Type()) { |
| case kVulkanObjectTypeEvent: |
| access_context.RecordDestroyEvent(static_cast<vvl::Event*>(obj.get())); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBuffer(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count, |
| const VkBufferCopy* regions, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size); |
| context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex); |
| |
| const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size); |
| context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count, |
| const VkBufferCopy2* regions, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size); |
| context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex); |
| |
| const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size); |
| context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource), |
| copy_region.srcOffset, copy_region.extent, src_tag_ex); |
| UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource), |
| copy_region.dstOffset, copy_region.extent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource), |
| copy_region.srcOffset, copy_region.extent, src_tag_ex); |
| UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource), |
| copy_region.dstOffset, copy_region.extent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBufferToImage(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout, |
| uint32_t region_count, const VkBufferImageCopy* regions, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region)); |
| context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex); |
| |
| UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource), |
| copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBufferToImage2(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout, |
| uint32_t region_count, const VkBufferImageCopy2* regions, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region)); |
| context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex); |
| |
| UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource), |
| copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource), |
| copy_region.imageOffset, copy_region.imageExtent, src_tag_ex); |
| |
| AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region)); |
| context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle()); |
| |
| for (const auto& copy_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource), |
| copy_region.imageOffset, copy_region.imageExtent, src_tag_ex); |
| |
| AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region)); |
| context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& blit_region : vvl::make_span(regions, region_count)) { |
| VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x), |
| std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y), |
| std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)}; |
| VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)), |
| static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)), |
| static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))}; |
| UpdateImageAccessState(*context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource), |
| offset, extent, src_tag_ex); |
| |
| offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x), |
| std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y), |
| std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)}; |
| extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)), |
| static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)), |
| static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))}; |
| UpdateImageAccessState(*context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource), |
| offset, extent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::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) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& blit_region : vvl::make_span(regions, region_count)) { |
| VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x), |
| std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y), |
| std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)}; |
| VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)), |
| static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)), |
| static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))}; |
| UpdateImageAccessState(*context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource), |
| offset, extent, src_tag_ex); |
| |
| offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x), |
| std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y), |
| std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)}; |
| extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)), |
| static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)), |
| static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))}; |
| UpdateImageAccessState(*context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource), |
| offset, extent, dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count, |
| const VkImageResolve* regions, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& resolve_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_RESOLVE_TRANSFER_READ, |
| RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent, |
| src_tag_ex); |
| UpdateImageAccessState(*context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE, |
| RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent, |
| dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count, |
| const VkImageResolve2* regions, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle()); |
| auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle()); |
| |
| for (const auto& resolve_region : vvl::make_span(regions, region_count)) { |
| UpdateImageAccessState(*context, src_image_state, SYNC_RESOLVE_TRANSFER_READ, |
| RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent, |
| src_tag_ex); |
| UpdateImageAccessState(*context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE, |
| RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent, |
| dst_tag_ex); |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearColorImage(vvl::Image& image_state, VkImageLayout, const VkClearColorValue*, |
| uint32_t range_count, const VkImageSubresourceRange* ranges, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| assert(context); |
| |
| access_context.AddCommandHandle(tag, image_state.Handle()); |
| |
| for (uint32_t index = 0; index < range_count; index++) { |
| const auto& range = ranges[index]; |
| UpdateImageAccessState(*context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag); |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout, const VkClearDepthStencilValue*, |
| uint32_t range_count, const VkImageSubresourceRange* ranges, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| assert(context); |
| |
| access_context.AddCommandHandle(tag, image_state.Handle()); |
| |
| for (uint32_t index = 0; index < range_count; index++) { |
| const auto& range = ranges[index]; |
| UpdateImageAccessState(*context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag); |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments, |
| uint32_t rect_count, const VkClearRect* pRects, const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| |
| for (const auto& attachment : vvl::make_span(pAttachments, attachment_count)) { |
| for (const auto& rect : vvl::make_span(pRects, rect_count)) { |
| access_context.RecordClearAttachment(tag, attachment, rect); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| assert(context); |
| |
| const AccessRange range = MakeRange(buffer_state, offset, size); |
| const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle()); |
| context->UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex); |
| } |
| |
| void CommandBufferSubState::RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| assert(context); |
| |
| // VK_WHOLE_SIZE not allowed |
| const AccessRange range = MakeRange(offset, size); |
| const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle()); |
| context->UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex); |
| } |
| |
| void CommandBufferSubState::RecordDecodeVideo(vvl::VideoSession& vs_state, const VkVideoDecodeInfoKHR& decode_info, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(decode_info.srcBuffer)) { |
| const AccessRange src_range = MakeRange(*src_buffer, decode_info.srcBufferOffset, decode_info.srcBufferRange); |
| const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle()); |
| context->UpdateAccessState(*src_buffer, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, src_range, src_tag_ex); |
| } |
| |
| const auto* device_state = access_context.GetSyncState().device_state; |
| auto dst_resource = vvl::VideoPictureResource(*device_state, decode_info.dstPictureResource); |
| if (dst_resource) { |
| UpdateVideoAccessState(*context, vs_state, dst_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag); |
| } |
| |
| if (decode_info.pSetupReferenceSlot != nullptr && decode_info.pSetupReferenceSlot->pPictureResource != nullptr) { |
| auto setup_resource = vvl::VideoPictureResource(*device_state, *decode_info.pSetupReferenceSlot->pPictureResource); |
| if (setup_resource && (setup_resource != dst_resource)) { |
| UpdateVideoAccessState(*context, vs_state, setup_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag); |
| } |
| } |
| |
| for (uint32_t i = 0; i < decode_info.referenceSlotCount; ++i) { |
| if (decode_info.pReferenceSlots[i].pPictureResource != nullptr) { |
| auto reference_resource = vvl::VideoPictureResource(*device_state, *decode_info.pReferenceSlots[i].pPictureResource); |
| if (reference_resource) { |
| UpdateVideoAccessState(*context, vs_state, reference_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, tag); |
| } |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordEncodeVideo(vvl::VideoSession& vs_state, const VkVideoEncodeInfoKHR& encode_info, |
| const Location& loc) { |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(encode_info.dstBuffer)) { |
| const AccessRange src_range = MakeRange(*src_buffer, encode_info.dstBufferOffset, encode_info.dstBufferRange); |
| const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle()); |
| context->UpdateAccessState(*src_buffer, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, src_range, src_tag_ex); |
| } |
| |
| const auto* device_state = access_context.GetSyncState().device_state; |
| auto src_resource = vvl::VideoPictureResource(*device_state, encode_info.srcPictureResource); |
| if (src_resource) { |
| UpdateVideoAccessState(*context, vs_state, src_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag); |
| } |
| |
| if (encode_info.pSetupReferenceSlot != nullptr && encode_info.pSetupReferenceSlot->pPictureResource != nullptr) { |
| auto setup_resource = vvl::VideoPictureResource(*device_state, *encode_info.pSetupReferenceSlot->pPictureResource); |
| if (setup_resource) { |
| UpdateVideoAccessState(*context, vs_state, setup_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, tag); |
| } |
| } |
| |
| for (uint32_t i = 0; i < encode_info.referenceSlotCount; ++i) { |
| if (encode_info.pReferenceSlots[i].pPictureResource != nullptr) { |
| auto reference_resource = vvl::VideoPictureResource(*device_state, *encode_info.pReferenceSlots[i].pPictureResource); |
| if (reference_resource) { |
| UpdateVideoAccessState(*context, vs_state, reference_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag); |
| } |
| } |
| } |
| |
| if (encode_info.flags & (VK_VIDEO_ENCODE_WITH_QUANTIZATION_DELTA_MAP_BIT_KHR | VK_VIDEO_ENCODE_WITH_EMPHASIS_MAP_BIT_KHR)) { |
| auto quantization_map_info = vku::FindStructInPNextChain<VkVideoEncodeQuantizationMapInfoKHR>(encode_info.pNext); |
| if (quantization_map_info) { |
| auto image_view_state = base.dev_data.Get<vvl::ImageView>(quantization_map_info->quantizationMap); |
| if (image_view_state) { |
| VkOffset3D offset = {0, 0, 0}; |
| VkExtent3D extent = {quantization_map_info->quantizationMapExtent.width, |
| quantization_map_info->quantizationMapExtent.height, 1}; |
| ImageRangeGen range_gen(MakeImageRangeGen(*image_view_state, offset, extent)); |
| context->UpdateAccessState(range_gen, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, ResourceUsageTagEx{tag}); |
| } |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyQueryPoolResults(vvl::QueryPool& pool_state, vvl::Buffer& dst_buffer_state, |
| uint32_t first_query, uint32_t query_count, VkDeviceSize dst_offset, |
| VkDeviceSize stride, VkQueryResultFlags flags, const Location& loc) { |
| if (query_count == 0) { |
| return; |
| } |
| const auto tag = access_context.NextCommandTag(loc.function); |
| auto* context = access_context.GetCurrentAccessContext(); |
| |
| const uint32_t query_size = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4; |
| const VkDeviceSize range_size = (query_count - 1) * stride + query_size; |
| const AccessRange range = MakeRange(dst_offset, range_size); |
| const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle()); |
| context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, range, tag_ex); |
| |
| // TODO:Track VkQueryPool |
| } |
| |
| void CommandBufferSubState::RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin, |
| const VkSubpassBeginInfo& subpass_begin_info, const Location& loc) { |
| if (!base.IsPrimary()) { |
| return; // [core validation check]: only primary command buffer can begin render pass |
| } |
| access_context.RecordSyncOp<SyncOpBeginRenderPass>(loc.function, access_context.GetSyncState(), &render_pass_begin, |
| &subpass_begin_info); |
| } |
| |
| void CommandBufferSubState::RecordNextSubpass(const VkSubpassBeginInfo& subpass_begin_info, |
| const VkSubpassEndInfo* subpass_end_info, const Location& loc) { |
| if (!base.IsPrimary()) { |
| return; // [core validation check]: only primary command buffer can start next subpass |
| } |
| access_context.RecordSyncOp<SyncOpNextSubpass>(loc.function, access_context.GetSyncState(), &subpass_begin_info, |
| subpass_end_info); |
| } |
| |
| void CommandBufferSubState::RecordEndRenderPass(const VkSubpassEndInfo* subpass_end_info, const Location& loc) { |
| if (!base.IsPrimary()) { |
| return; // [core validation check]: only primary command buffer can end render pass |
| } |
| // Resolve the all subpass contexts to the command buffer contexts |
| access_context.RecordSyncOp<SyncOpEndRenderPass>(loc.function, access_context.GetSyncState(), subpass_end_info); |
| } |
| |
| void CommandBufferSubState::RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t cmd_index, |
| const Location& loc) { |
| if (cmd_index == 0) { |
| ResourceUsageTag cb_tag = access_context.NextCommandTag(loc.function, SubCommandType::kIndex); |
| access_context.AddCommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index); |
| } else { |
| ResourceUsageTag cb_tag = access_context.NextSubCommandTag(loc.function, SubCommandType::kIndex); |
| access_context.AddSubcommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index); |
| } |
| access_context.RecordExecutedCommandBuffer(*GetAccessContext(secondary_command_buffer)); |
| } |
| |
| } // namespace syncval |