| /* |
| * 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 "sync/sync_op.h" |
| #include "sync/sync_renderpass.h" |
| #include "sync/sync_access_context.h" |
| #include "sync/sync_commandbuffer.h" |
| #include "sync/sync_image.h" |
| #include "sync/sync_validation.h" |
| |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/cmd_buffer_state.h" |
| #include "state_tracker/render_pass_state.h" |
| |
| #include "utils/image_utils.h" |
| #include "utils/sync_utils.h" |
| |
| namespace syncval { |
| |
| // Range generators for to allow event scope filtration to be limited to the top of the resource access traversal pipeline |
| // |
| // Note: there is no "begin/end" or reset facility. These are each written as "one time through" generators. |
| // |
| // Usage: |
| // Constructor() -- initializes the generator to point to the begin of the space declared. |
| // * -- the current range of the generator empty signfies end |
| // ++ -- advance to the next non-empty range (or end) |
| |
| // Generate the ranges that are the intersection of range and the entries in the RangeMap |
| class MapRangesRangeGenerator { |
| public: |
| // Default constructed is safe to dereference for "empty" test, but for no other operation. |
| MapRangesRangeGenerator() { |
| // Default construction *must* be empty range |
| assert(current_.empty()); |
| } |
| MapRangesRangeGenerator(const AccessMap &filter, const AccessRange &range) |
| : range_(range), map_(&filter), map_pos_(), current_() { |
| SeekBegin(); |
| } |
| MapRangesRangeGenerator(const MapRangesRangeGenerator &from) = default; |
| |
| const AccessRange &operator*() const { return current_; } |
| const AccessRange *operator->() const { return ¤t_; } |
| MapRangesRangeGenerator &operator++() { |
| ++map_pos_; |
| UpdateCurrent(); |
| return *this; |
| } |
| |
| bool operator==(const MapRangesRangeGenerator &other) const { return current_ == other.current_; } |
| |
| protected: |
| void UpdateCurrent() { |
| if (map_pos_ != map_->end()) { |
| current_ = range_ & map_pos_->first; |
| } else { |
| current_ = {}; |
| } |
| } |
| void SeekBegin() { |
| map_pos_ = map_->LowerBound(range_.begin); |
| UpdateCurrent(); |
| } |
| |
| // Adding this functionality here, to avoid gratuitous Base:: qualifiers in the derived class |
| // Note: Not exposed in this classes public interface to encourage using a consistent ++/empty generator semantic |
| template <typename Pred> |
| MapRangesRangeGenerator &PredicatedIncrement(Pred &pred) { |
| do { |
| ++map_pos_; |
| } while (map_pos_ != map_->end() && map_pos_->first.intersects(range_) && !pred(map_pos_)); |
| UpdateCurrent(); |
| return *this; |
| } |
| |
| const AccessRange range_; |
| const AccessMap *map_ = nullptr; |
| AccessMap::const_iterator map_pos_; |
| AccessRange current_; |
| }; |
| using EventSimpleRangeGenerator = MapRangesRangeGenerator; |
| |
| // Generate the ranges that are the intersection of the RangeGen ranges and the entries in the FilterMap |
| template <typename RangeGen> |
| class FilteredGeneratorGenerator { |
| public: |
| // Default constructed is safe to dereference for "empty" test, but for no other operation. |
| FilteredGeneratorGenerator() : filter_(nullptr), gen_(), filter_pos_(), current_() { |
| // Default construction for KeyType *must* be empty range |
| assert(current_.empty()); |
| } |
| FilteredGeneratorGenerator(const AccessMap &filter, RangeGen &gen) : filter_(&filter), gen_(gen), filter_pos_(), current_() { |
| SeekBegin(); |
| } |
| FilteredGeneratorGenerator(const FilteredGeneratorGenerator &from) = default; |
| const AccessRange &operator*() const { return current_; } |
| const AccessRange *operator->() const { return ¤t_; } |
| FilteredGeneratorGenerator &operator++() { |
| AccessRange gen_range = GenRange(); |
| AccessRange filter_range = FilterRange(); |
| current_ = {}; |
| while (gen_range.non_empty() && filter_range.non_empty() && current_.empty()) { |
| if (gen_range.end > filter_range.end) { |
| // if the generated range is beyond the filter_range, advance the filter range |
| filter_range = AdvanceFilter(); |
| } else { |
| gen_range = AdvanceGen(); |
| } |
| current_ = gen_range & filter_range; |
| } |
| return *this; |
| } |
| |
| bool operator==(const FilteredGeneratorGenerator &other) const { return current_ == other.current_; } |
| |
| private: |
| AccessRange AdvanceFilter() { |
| ++filter_pos_; |
| auto filter_range = FilterRange(); |
| assert(filter_range.valid()); |
| if (filter_range.valid()) { |
| FastForwardGen(filter_range); |
| } |
| return filter_range; |
| } |
| AccessRange AdvanceGen() { |
| ++gen_; |
| auto gen_range = GenRange(); |
| if (gen_range.valid()) { |
| FastForwardFilter(gen_range); |
| } |
| return gen_range; |
| } |
| |
| AccessRange FilterRange() const { return (filter_pos_ != filter_->end()) ? filter_pos_->first : AccessRange{}; } |
| AccessRange GenRange() const { return *gen_; } |
| |
| AccessRange FastForwardFilter(const AccessRange &range) { |
| auto filter_range = FilterRange(); |
| int retry_count = 0; |
| const static int kRetryLimit = 2; // TODO -- determine whether this limit is optimal |
| while (!filter_range.empty() && (filter_range.end <= range.begin)) { |
| if (retry_count < kRetryLimit) { |
| ++filter_pos_; |
| filter_range = FilterRange(); |
| retry_count++; |
| } else { |
| // Okay we've tried walking, do a seek. |
| filter_pos_ = filter_->LowerBound(range.begin); |
| break; |
| } |
| } |
| return FilterRange(); |
| } |
| |
| // TODO: Consider adding "seek" (or an absolute bound "get" to range generators to make this walk |
| // faster. |
| AccessRange FastForwardGen(const AccessRange &range) { |
| auto gen_range = GenRange(); |
| while (!gen_range.empty() && (gen_range.end <= range.begin)) { |
| ++gen_; |
| gen_range = GenRange(); |
| } |
| return gen_range; |
| } |
| |
| void SeekBegin() { |
| auto gen_range = GenRange(); |
| if (gen_range.empty()) { |
| current_ = {}; |
| filter_pos_ = filter_->end(); |
| } else { |
| filter_pos_ = filter_->LowerBound(gen_range.begin); |
| current_ = gen_range & FilterRange(); |
| } |
| } |
| |
| const AccessMap *filter_ = nullptr; |
| RangeGen gen_; |
| AccessMap::const_iterator filter_pos_; |
| AccessRange current_; |
| }; |
| |
| using EventImageRangeGenerator = FilteredGeneratorGenerator<subresource_adapter::ImageRangeGenerator>; |
| |
| void BarrierSet::MakeMemoryBarriers(const SyncExecScope &src, const SyncExecScope &dst, uint32_t barrier_count, |
| const VkMemoryBarrier *barriers) { |
| memory_barriers.reserve(std::max<uint32_t>(1, barrier_count)); |
| for (const VkMemoryBarrier &barrier : vvl::make_span(barriers, barrier_count)) { |
| SyncBarrier sync_barrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask); |
| memory_barriers.emplace_back(sync_barrier); |
| } |
| |
| // Ensure we have a barrier that handles execution dependencies. |
| // NOTE: the reason to have execution barrier is explained in details in the comment for Sync2 |
| // MakeMemoryBarriers overload. The Sync1 implementation is much simpler since execution scopes |
| // are the same for all barriers. |
| if (barrier_count == 0) { |
| memory_barriers.emplace_back(SyncBarrier(src, dst)); |
| } |
| single_exec_scope = true; |
| execution_dependency_barrier_count = (barrier_count == 0) ? 1 : 0; |
| } |
| |
| void BarrierSet::MakeMemoryBarriers(VkQueueFlags queue_flags, const VkDependencyInfo &dep_info) { |
| // Collect unique execution dependencies from buffer and image barriers. |
| // |
| // NOTE: the reason to collect execution dependencies in addition to original buffer/image |
| // barriers is because syncval applies buffer/image barriers to the memory ranges defined |
| // by the resource. But execution dependency can affect any resource/memory range, not |
| // only the one specified by the barrier. For example, execution dependency synchronizes |
| // all READ accesses that are in scope. To emulate this behavior we collect unique |
| // execution dependencies and apply them to all memory accesses (don't specify access mask). |
| small_vector<std::pair<VkPipelineStageFlags2, VkPipelineStageFlags2>, 4> buffer_image_barrier_exec_deps; |
| for (const VkBufferMemoryBarrier2 &buffer_barrier : |
| vvl::make_span(dep_info.pBufferMemoryBarriers, dep_info.bufferMemoryBarrierCount)) { |
| const auto src_dst = std::make_pair(buffer_barrier.srcStageMask, buffer_barrier.dstStageMask); |
| if (!buffer_image_barrier_exec_deps.Contains(src_dst)) { |
| buffer_image_barrier_exec_deps.emplace_back(src_dst); |
| } |
| } |
| for (const VkImageMemoryBarrier2 &image_barrier : |
| vvl::make_span(dep_info.pImageMemoryBarriers, dep_info.imageMemoryBarrierCount)) { |
| const auto src_dst = std::make_pair(image_barrier.srcStageMask, image_barrier.dstStageMask); |
| if (!buffer_image_barrier_exec_deps.Contains(src_dst)) { |
| buffer_image_barrier_exec_deps.emplace_back(src_dst); |
| } |
| } |
| |
| memory_barriers.reserve(dep_info.memoryBarrierCount + buffer_image_barrier_exec_deps.size()); |
| |
| // Add global memory barriers specified in VkDependencyInfo |
| for (const VkMemoryBarrier2 &barrier : vvl::make_span(dep_info.pMemoryBarriers, dep_info.memoryBarrierCount)) { |
| auto src = SyncExecScope::MakeSrc(queue_flags, barrier.srcStageMask); |
| auto dst = SyncExecScope::MakeDst(queue_flags, barrier.dstStageMask); |
| memory_barriers.emplace_back(SyncBarrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask)); |
| } |
| // Add execution dependencies from buffer and image barriers |
| for (const auto &src_dst : buffer_image_barrier_exec_deps) { |
| auto src = SyncExecScope::MakeSrc(queue_flags, src_dst.first); |
| auto dst = SyncExecScope::MakeDst(queue_flags, src_dst.second); |
| memory_barriers.emplace_back(SyncBarrier(src, dst)); |
| } |
| single_exec_scope = false; |
| execution_dependency_barrier_count = (uint32_t)buffer_image_barrier_exec_deps.size(); |
| } |
| |
| void BarrierSet::MakeBufferMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst, |
| uint32_t barrier_count, const VkBufferMemoryBarrier *barriers) { |
| buffer_barriers.reserve(barrier_count); |
| for (const VkBufferMemoryBarrier &barrier : vvl::make_span(barriers, barrier_count)) { |
| if (auto buffer = sync_state.Get<vvl::Buffer>(barrier.buffer)) { |
| const auto range = MakeRange(*buffer, barrier.offset, barrier.size); |
| const SyncBarrier sync_barrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask); |
| buffer_barriers.emplace_back(buffer, sync_barrier, range); |
| } |
| } |
| } |
| |
| void BarrierSet::MakeBufferMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t barrier_count, |
| const VkBufferMemoryBarrier2 *barriers) { |
| buffer_barriers.reserve(barrier_count); |
| for (const VkBufferMemoryBarrier2 &barrier : vvl::make_span(barriers, barrier_count)) { |
| auto src = SyncExecScope::MakeSrc(queue_flags, barrier.srcStageMask); |
| auto dst = SyncExecScope::MakeDst(queue_flags, barrier.dstStageMask); |
| if (auto buffer = sync_state.Get<vvl::Buffer>(barrier.buffer)) { |
| const auto range = MakeRange(*buffer, barrier.offset, barrier.size); |
| const SyncBarrier sync_barrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask); |
| buffer_barriers.emplace_back(buffer, sync_barrier, range); |
| } |
| } |
| } |
| |
| void BarrierSet::MakeImageMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst, |
| uint32_t barrier_count, const VkImageMemoryBarrier *barriers, |
| const DeviceExtensions &extensions) { |
| image_barriers.reserve(barrier_count); |
| for (const auto [index, barrier] : vvl::enumerate(barriers, barrier_count)) { |
| if (auto image = sync_state.Get<vvl::Image>(barrier.image)) { |
| auto subresource_range = image->NormalizeSubresourceRange(barrier.subresourceRange); |
| |
| // VK_REMAINING_ARRAY_LAYERS for sliced 3d image in the context of layout transition means image's depth extent. |
| if (barrier.subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS && |
| CanTransitionDepthSlices(extensions, image->create_info)) { |
| subresource_range.layerCount = image->create_info.extent.depth - subresource_range.baseArrayLayer; |
| } |
| |
| const SyncBarrier sync_barrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask); |
| const bool layout_transition = barrier.oldLayout != barrier.newLayout; |
| image_barriers.emplace_back(image, sync_barrier, subresource_range, layout_transition, index); |
| } |
| } |
| } |
| |
| void BarrierSet::MakeImageMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t barrier_count, |
| const VkImageMemoryBarrier2 *barriers, const DeviceExtensions &extensions) { |
| image_barriers.reserve(barrier_count); |
| for (const auto [index, barrier] : vvl::enumerate(barriers, barrier_count)) { |
| auto src = SyncExecScope::MakeSrc(queue_flags, barrier.srcStageMask); |
| auto dst = SyncExecScope::MakeDst(queue_flags, barrier.dstStageMask); |
| auto image = sync_state.Get<vvl::Image>(barrier.image); |
| if (image) { |
| auto subresource_range = image->NormalizeSubresourceRange(barrier.subresourceRange); |
| |
| // VK_REMAINING_ARRAY_LAYERS for sliced 3d image in the context of layout transition means image's depth extent. |
| if (barrier.subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS && |
| CanTransitionDepthSlices(extensions, image->create_info)) { |
| subresource_range.layerCount = image->create_info.extent.depth - subresource_range.baseArrayLayer; |
| } |
| |
| const SyncBarrier sync_barrier(src, barrier.srcAccessMask, dst, barrier.dstAccessMask); |
| const bool layout_transition = barrier.oldLayout != barrier.newLayout; |
| image_barriers.emplace_back(image, sync_barrier, subresource_range, layout_transition, index); |
| } |
| } |
| } |
| |
| SyncOpPipelineBarrier::SyncOpPipelineBarrier(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, |
| VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, |
| uint32_t memory_barrier_count, const VkMemoryBarrier *memory_barriers, |
| uint32_t buffer_barrier_count, const VkBufferMemoryBarrier *buffer_barriers, |
| uint32_t image_barrier_count, const VkImageMemoryBarrier *image_barriers) |
| : SyncOpBase(command) { |
| const auto src_exec_scope = SyncExecScope::MakeSrc(queue_flags, src_stage_mask); |
| const auto dst_exec_scope = SyncExecScope::MakeDst(queue_flags, dst_stage_mask); |
| |
| barrier_set_.src_exec_scope = src_exec_scope; |
| barrier_set_.dst_exec_scope = dst_exec_scope; |
| |
| barrier_set_.MakeMemoryBarriers(src_exec_scope, dst_exec_scope, memory_barrier_count, memory_barriers); |
| barrier_set_.MakeBufferMemoryBarriers(sync_state, src_exec_scope, dst_exec_scope, buffer_barrier_count, buffer_barriers); |
| barrier_set_.MakeImageMemoryBarriers(sync_state, src_exec_scope, dst_exec_scope, image_barrier_count, image_barriers, |
| sync_state.device_state->extensions); |
| } |
| |
| SyncOpPipelineBarrier::SyncOpPipelineBarrier(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, |
| const VkDependencyInfo &dep_info) |
| : SyncOpBase(command) { |
| const ExecScopes stage_masks = sync_utils::GetExecScopes(dep_info); |
| |
| barrier_set_.src_exec_scope = SyncExecScope::MakeSrc(queue_flags, stage_masks.src); |
| barrier_set_.dst_exec_scope = SyncExecScope::MakeDst(queue_flags, stage_masks.dst); |
| |
| barrier_set_.MakeMemoryBarriers(queue_flags, dep_info); |
| barrier_set_.MakeBufferMemoryBarriers(sync_state, queue_flags, dep_info.bufferMemoryBarrierCount, |
| dep_info.pBufferMemoryBarriers); |
| barrier_set_.MakeImageMemoryBarriers(sync_state, queue_flags, dep_info.imageMemoryBarrierCount, dep_info.pImageMemoryBarriers, |
| sync_state.device_state->extensions); |
| } |
| |
| bool SyncOpPipelineBarrier::Validate(const CommandBufferAccessContext &cb_context) const { |
| bool skip = false; |
| const auto *context = cb_context.GetCurrentAccessContext(); |
| assert(context); |
| if (!context) return skip; |
| |
| // Validate Image Layout transitions |
| for (const auto &image_barrier : barrier_set_.image_barriers) { |
| if (!image_barrier.layout_transition) { |
| continue; |
| } |
| const vvl::Image &image_state = *image_barrier.image; |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(cb_context.GetSyncState().extensions, image_state.create_info); |
| const auto hazard = context->DetectImageBarrierHazard( |
| image_state, image_barrier.barrier.src_exec_scope.exec_scope, image_barrier.barrier.src_access_scope, |
| image_barrier.subresource_range, can_transition_depth_slices, AccessContext::kDetectAll); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(cb_context.GetCBState().Handle(), image_state.Handle()); |
| const Location loc(command_); |
| const SyncValidator &sync_state = cb_context.GetSyncState(); |
| const std::string resource_description = sync_state.FormatHandle(image_state.Handle()); |
| const std::string error = |
| sync_state.error_messages_.ImageBarrierError(hazard, cb_context, command_, resource_description, image_barrier); |
| skip |= sync_state.SyncError(hazard.Hazard(), objlist, loc, error); |
| } |
| } |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpPipelineBarrier::Record(CommandBufferAccessContext *cb_context) { |
| const auto tag = cb_context->NextCommandTag(command_); |
| for (const auto &buffer_barrier : barrier_set_.buffer_barriers) { |
| cb_context->AddCommandHandle(tag, buffer_barrier.buffer->Handle()); |
| } |
| for (auto &image_barrier : barrier_set_.image_barriers) { |
| if (image_barrier.layout_transition) { |
| const auto tag_ex = cb_context->AddCommandHandle(tag, image_barrier.image->Handle()); |
| image_barrier.handle_index = tag_ex.handle_index; |
| } |
| } |
| ReplayRecord(*cb_context, tag); |
| return tag; |
| } |
| |
| void SyncOpPipelineBarrier::ApplySingleBufferBarrier(CommandExecutionContext &exec_context, const SyncBufferBarrier &buffer_barrier, |
| const SyncBarrier &exec_dep_barrier) const { |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| if (SimpleBinding(*buffer_barrier.buffer)) { |
| const BarrierScope barrier_scope(buffer_barrier.barrier, queue_id); |
| ApplySingleBufferBarrierFunctor apply_barrier(*access_context, barrier_scope, buffer_barrier.barrier); |
| |
| const VkDeviceSize base_address = ResourceBaseAddress(*buffer_barrier.buffer); |
| const AccessRange range = buffer_barrier.range + base_address; |
| |
| access_context->UpdateMemoryAccessState(apply_barrier, range); |
| } |
| access_context->RegisterGlobalBarrier(exec_dep_barrier, queue_id); |
| } |
| |
| void SyncOpPipelineBarrier::ApplySingleImageBarrier(CommandExecutionContext &exec_context, const SyncImageBarrier &image_barrier, |
| const SyncBarrier &exec_dep_barrier, const ResourceUsageTag exec_tag) const { |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| const BarrierScope barrier_scope(image_barrier.barrier, queue_id); |
| ApplySingleImageBarrierFunctor apply_barrier(*access_context, barrier_scope, image_barrier.barrier, |
| image_barrier.layout_transition, image_barrier.handle_index, exec_tag); |
| |
| const auto &sub_state = SubState(*image_barrier.image); |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(exec_context.GetSyncState().extensions, sub_state.base.create_info); |
| auto range_gen = sub_state.MakeImageRangeGen(image_barrier.subresource_range, can_transition_depth_slices); |
| |
| access_context->UpdateMemoryAccessState(apply_barrier, range_gen); |
| access_context->RegisterGlobalBarrier(exec_dep_barrier, queue_id); |
| } |
| |
| void SyncOpPipelineBarrier::ApplySingleMemoryBarrier(CommandExecutionContext &exec_context, |
| const SyncBarrier &memory_barrier) const { |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| access_context->RegisterGlobalBarrier(memory_barrier, queue_id); |
| } |
| |
| void SyncOpPipelineBarrier::ApplyMultipleBarriers(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| // Apply markup action. |
| // The markup action does not change any access state but it can trim the access map according to the |
| // provided range and creates infill ranges if necessary (for layout transitions). The purpose of all |
| // this is to ensure that after markup action the topology of access map ranges is finalized so we can |
| // safely cache pointers to specific access states with a goal to apply pending barriers in the end. |
| // |
| // NOTE: it is enough to apply markup action to buffer and image barriers. The global barriers |
| // do not use infill operations (no layout transitions) and also do not split access map ranges |
| // because global barriers are applied to the full range. |
| for (const SyncBufferBarrier &barrier : barrier_set_.buffer_barriers) { |
| if (SimpleBinding(*barrier.buffer)) { |
| const VkDeviceSize base_address = ResourceBaseAddress(*barrier.buffer); |
| const AccessRange range = barrier.range + base_address; |
| ApplyMarkupFunctor markup_action(false); |
| access_context->UpdateMemoryAccessState(markup_action, range); |
| } |
| } |
| for (const SyncImageBarrier &barrier : barrier_set_.image_barriers) { |
| const auto &sub_state = SubState(*barrier.image); |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(exec_context.GetSyncState().extensions, sub_state.base.create_info); |
| auto range_gen = sub_state.MakeImageRangeGen(barrier.subresource_range, can_transition_depth_slices); |
| // TODO: check if we need: barrier.layout_transition && (queue_id == kQueueIdInvalid) |
| ApplyMarkupFunctor markup_action(barrier.layout_transition); |
| access_context->UpdateMemoryAccessState(markup_action, range_gen); |
| } |
| |
| // Use PendingBarriers to collect barriers that must be applied independently |
| PendingBarriers pending_barriers; |
| for (const SyncBufferBarrier &barrier : barrier_set_.buffer_barriers) { |
| if (SimpleBinding(*barrier.buffer)) { |
| const BarrierScope barrier_scope(barrier.barrier, queue_id); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, barrier.barrier, false, vvl::kNoIndex32, |
| pending_barriers); |
| |
| const VkDeviceSize base_address = ResourceBaseAddress(*barrier.buffer); |
| const AccessRange range = barrier.range + base_address; |
| |
| access_context->UpdateMemoryAccessState(collect_barriers, range); |
| } |
| } |
| for (const SyncImageBarrier &barrier : barrier_set_.image_barriers) { |
| const BarrierScope barrier_scope(barrier.barrier, queue_id); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, barrier.barrier, barrier.layout_transition, |
| barrier.handle_index, pending_barriers); |
| |
| const auto &sub_state = SubState(*barrier.image); |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(exec_context.GetSyncState().extensions, sub_state.base.create_info); |
| auto range_gen = sub_state.MakeImageRangeGen(barrier.subresource_range, can_transition_depth_slices); |
| |
| access_context->UpdateMemoryAccessState(collect_barriers, range_gen); |
| } |
| |
| // Do kFullRange update only when there is multiple memory barriers. |
| // For a single barrier we can use global barrier functionality. |
| if (barrier_set_.memory_barriers.size() > 1) { |
| for (const SyncBarrier &barrier : barrier_set_.memory_barriers) { |
| const BarrierScope barrier_scope(barrier, queue_id); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, barrier, false, vvl::kNoIndex32, |
| pending_barriers); |
| access_context->UpdateMemoryAccessState(collect_barriers, kFullRange); |
| } |
| } |
| |
| // Apply collected barriers to access states |
| pending_barriers.Apply(exec_tag); |
| |
| // Register global barriers if we have the only memory barrier (likely execution dependency) |
| if (barrier_set_.memory_barriers.size() == 1) { |
| access_context->RegisterGlobalBarrier(barrier_set_.memory_barriers[0], queue_id); |
| } |
| } |
| |
| void SyncOpPipelineBarrier::ReplayRecord(CommandExecutionContext &exec_context, const ResourceUsageTag exec_tag) const { |
| if (!exec_context.ValidForSyncOps()) { |
| return; |
| } |
| |
| const bool has_buffer_barriers = !barrier_set_.buffer_barriers.empty(); |
| const bool has_image_barriers = !barrier_set_.image_barriers.empty(); |
| |
| const bool single_buffer_barrier = barrier_set_.buffer_barriers.size() == 1 && |
| barrier_set_.memory_barriers.size() == 1 && // buffer barrier exec dependency |
| !has_image_barriers; |
| |
| const bool single_image_barrier = barrier_set_.image_barriers.size() == 1 && |
| barrier_set_.memory_barriers.size() == 1 && // image barrier exec dependency |
| !has_buffer_barriers; |
| |
| const bool single_memory_barrier = barrier_set_.memory_barriers.size() == 1 && !has_buffer_barriers && !has_image_barriers; |
| |
| if (single_buffer_barrier) { |
| ApplySingleBufferBarrier(exec_context, barrier_set_.buffer_barriers[0], barrier_set_.memory_barriers[0]); |
| } else if (single_image_barrier) { |
| ApplySingleImageBarrier(exec_context, barrier_set_.image_barriers[0], barrier_set_.memory_barriers[0], exec_tag); |
| } else if (single_memory_barrier) { |
| ApplySingleMemoryBarrier(exec_context, barrier_set_.memory_barriers[0]); |
| } else { |
| ApplyMultipleBarriers(exec_context, exec_tag); |
| } |
| |
| SyncEventsContext *events_context = exec_context.GetCurrentEventsContext(); |
| if (barrier_set_.single_exec_scope) { |
| events_context->ApplyBarrier(barrier_set_.src_exec_scope, barrier_set_.dst_exec_scope, exec_tag); |
| } else { |
| for (const auto &barrier : barrier_set_.memory_barriers) { |
| events_context->ApplyBarrier(barrier.src_exec_scope, barrier.dst_exec_scope, exec_tag); |
| } |
| } |
| } |
| |
| bool SyncOpPipelineBarrier::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| // The layout transitions happen at the replay tag |
| ResourceUsageRange first_use_range = {recorded_tag, recorded_tag + 1}; |
| return replay.DetectFirstUseHazard(first_use_range); |
| } |
| |
| SyncOpWaitEvents::SyncOpWaitEvents(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, |
| uint32_t eventCount, const VkEvent *pEvents, VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, |
| const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, |
| const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, |
| const VkImageMemoryBarrier *pImageMemoryBarriers) |
| : SyncOpBase(command), barrier_sets_(1) { |
| auto &barrier_set = barrier_sets_[0]; |
| const auto src_exec_scope = SyncExecScope::MakeSrc(queue_flags, srcStageMask); |
| const auto dst_exec_scope = SyncExecScope::MakeDst(queue_flags, dstStageMask); |
| |
| barrier_set.src_exec_scope = src_exec_scope; |
| barrier_set.dst_exec_scope = dst_exec_scope; |
| |
| barrier_set.MakeMemoryBarriers(src_exec_scope, dst_exec_scope, memoryBarrierCount, pMemoryBarriers); |
| barrier_set.MakeBufferMemoryBarriers(sync_state, src_exec_scope, dst_exec_scope, bufferMemoryBarrierCount, |
| pBufferMemoryBarriers); |
| barrier_set.MakeImageMemoryBarriers(sync_state, src_exec_scope, dst_exec_scope, imageMemoryBarrierCount, pImageMemoryBarriers, |
| sync_state.device_state->extensions); |
| |
| MakeEventsList(sync_state, eventCount, pEvents); |
| } |
| |
| SyncOpWaitEvents::SyncOpWaitEvents(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, |
| uint32_t eventCount, const VkEvent *pEvents, const VkDependencyInfo *pDependencyInfo) |
| : SyncOpBase(command), barrier_sets_(eventCount) { |
| for (uint32_t i = 0; i < eventCount; i++) { |
| const auto &dep_info = pDependencyInfo[i]; |
| auto &barrier_set = barrier_sets_[i]; |
| auto stage_masks = sync_utils::GetExecScopes(dep_info); |
| |
| barrier_set.src_exec_scope = SyncExecScope::MakeSrc(queue_flags, stage_masks.src); |
| barrier_set.dst_exec_scope = SyncExecScope::MakeDst(queue_flags, stage_masks.dst); |
| |
| barrier_set.MakeMemoryBarriers(queue_flags, dep_info); |
| barrier_set.MakeBufferMemoryBarriers(sync_state, queue_flags, dep_info.bufferMemoryBarrierCount, |
| dep_info.pBufferMemoryBarriers); |
| barrier_set.MakeImageMemoryBarriers(sync_state, queue_flags, dep_info.imageMemoryBarrierCount, |
| dep_info.pImageMemoryBarriers, sync_state.device_state->extensions); |
| } |
| MakeEventsList(sync_state, eventCount, pEvents); |
| } |
| |
| const char *const SyncOpWaitEvents::kIgnored = "Wait operation is ignored for this event."; |
| |
| bool SyncOpWaitEvents::Validate(const CommandBufferAccessContext &cb_context) const { |
| bool skip = false; |
| const auto &sync_state = cb_context.GetSyncState(); |
| const VkCommandBuffer command_buffer_handle = cb_context.GetCBState().VkHandle(); |
| |
| // This is only interesting at record and not replay (Execute/Submit) time. |
| for (size_t barrier_set_index = 0; barrier_set_index < barrier_sets_.size(); barrier_set_index++) { |
| const auto &barrier_set = barrier_sets_[barrier_set_index]; |
| if (barrier_set.single_exec_scope) { |
| const Location loc(command_); |
| if (barrier_set.src_exec_scope.mask_param & VK_PIPELINE_STAGE_HOST_BIT) { |
| const std::string vuid = std::string("SYNC-") + std::string(CmdName()) + std::string("-hostevent-unsupported"); |
| sync_state.LogInfo(vuid, command_buffer_handle, loc, |
| "srcStageMask includes %s, unsupported by synchronization validation.", |
| string_VkPipelineStageFlagBits(VK_PIPELINE_STAGE_HOST_BIT)); |
| } else { |
| const auto &barriers = barrier_set.memory_barriers; |
| for (size_t barrier_index = 0; barrier_index < barriers.size(); barrier_index++) { |
| const auto &barrier = barriers[barrier_index]; |
| if (barrier.src_exec_scope.mask_param & VK_PIPELINE_STAGE_HOST_BIT) { |
| const std::string vuid = |
| std::string("SYNC-") + std::string(CmdName()) + std::string("-hostevent-unsupported"); |
| |
| sync_state.LogInfo(vuid, command_buffer_handle, loc, |
| "srcStageMask %s of %s %zu, %s %zu, unsupported by synchronization validation.", |
| string_VkPipelineStageFlagBits(VK_PIPELINE_STAGE_HOST_BIT), "pDependencyInfo", |
| barrier_set_index, "pMemoryBarriers", barrier_index); |
| } |
| } |
| } |
| } |
| } |
| |
| // The rest is common to record time and replay time. |
| skip |= DoValidate(cb_context, ResourceUsageRecord::kMaxIndex); |
| return skip; |
| } |
| |
| bool SyncOpWaitEvents::DoValidate(const CommandExecutionContext &exec_context, const ResourceUsageTag base_tag) const { |
| bool skip = false; |
| const auto &sync_state = exec_context.GetSyncState(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| VkPipelineStageFlags2 event_stage_masks = 0U; |
| VkPipelineStageFlags2 barrier_mask_params = 0U; |
| bool events_not_found = false; |
| const auto *events_context = exec_context.GetCurrentEventsContext(); |
| assert(events_context); |
| size_t barrier_set_index = 0; |
| size_t barrier_set_incr = (barrier_sets_.size() == 1) ? 0 : 1; |
| const Location loc(command_); |
| for (const auto &event : events_) { |
| const auto *sync_event = events_context->Get(event.get()); |
| const auto &barrier_set = barrier_sets_[barrier_set_index]; |
| if (!sync_event) { |
| // NOTE PHASE2: This is where we'll need queue submit time validation to come back and check the srcStageMask bits |
| // or solve this with replay creating the SyncEventState in the queue context... also this will be a |
| // new validation error... wait without previously submitted set event... |
| events_not_found = true; // Demote "extra_stage_bits" error to warning, to avoid false positives at *record time* |
| barrier_set_index += barrier_set_incr; |
| continue; // Core, Lifetimes, or Param check needs to catch invalid events. |
| } |
| |
| // For replay calls, don't revalidate "same command buffer" events |
| if (sync_event->last_command_tag >= base_tag) continue; |
| |
| const VkEvent event_handle = sync_event->event->VkHandle(); |
| // TODO add "destroyed" checks |
| |
| if (sync_event->first_scope) { |
| // Only accumulate barrier and event stages if there is a pending set in the current context |
| barrier_mask_params |= barrier_set.src_exec_scope.mask_param; |
| event_stage_masks |= sync_event->scope.mask_param; |
| } |
| |
| const auto &src_exec_scope = barrier_set.src_exec_scope; |
| |
| const auto ignore_reason = sync_event->IsIgnoredByWait(command_, src_exec_scope.mask_param); |
| if (ignore_reason) { |
| switch (ignore_reason) { |
| case SyncEventState::ResetWaitRace: |
| case SyncEventState::Reset2WaitRace: { |
| // Four permuations of Reset and Wait calls... |
| const char *vuid = (command_ == vvl::Func::vkCmdWaitEvents) ? "VUID-vkCmdResetEvent-event-03834" |
| : "VUID-vkCmdResetEvent-event-03835"; |
| if (ignore_reason == SyncEventState::Reset2WaitRace) { |
| vuid = (command_ == vvl::Func::vkCmdWaitEvents) ? "VUID-vkCmdResetEvent2-event-03831" |
| : "VUID-vkCmdResetEvent2-event-03832"; |
| } |
| const char *const message = |
| "%s %s operation following %s without intervening execution barrier, may cause race condition. %s"; |
| skip |= sync_state.LogError(vuid, event_handle, loc, message, sync_state.FormatHandle(event_handle).c_str(), |
| CmdName(), vvl::String(sync_event->last_command), kIgnored); |
| break; |
| } |
| case SyncEventState::SetRace: { |
| // Issue error message that Wait is waiting on an signal subject to race condition, and is thus ignored for |
| // this event |
| const char *const vuid = "SYNC-vkCmdWaitEvents-unsynchronized-setops"; |
| const char *const message = |
| "%s Unsychronized %s calls result in race conditions w.r.t. event signalling, %s %s"; |
| const char *const reason = "First synchronization scope is undefined."; |
| skip |= sync_state.LogError(vuid, event_handle, loc, message, sync_state.FormatHandle(event_handle).c_str(), |
| vvl::String(sync_event->last_command), reason, kIgnored); |
| break; |
| } |
| case SyncEventState::MissingStageBits: { |
| const auto missing_bits = sync_event->scope.mask_param & ~src_exec_scope.mask_param; |
| // Issue error message that event waited for is not in wait events scope |
| const char *const vuid = "VUID-vkCmdWaitEvents-srcStageMask-01158"; |
| const char *const message = |
| "%s stageMask %s includes stages not present in srcStageMask %s. Stages missing from srcStageMask: %s. %s"; |
| skip |= sync_state.LogError(vuid, event_handle, loc, message, sync_state.FormatHandle(event_handle).c_str(), |
| sync_utils::StringPipelineStageFlags(sync_event->scope.mask_param).c_str(), |
| sync_utils::StringPipelineStageFlags(src_exec_scope.mask_param).c_str(), |
| sync_utils::StringPipelineStageFlags(missing_bits).c_str(), kIgnored); |
| break; |
| } |
| case SyncEventState::SetVsWait2: { |
| skip |= sync_state.LogError( |
| "VUID-vkCmdWaitEvents2-pEvents-03837", event_handle, loc, "Follows set of %s by %s. Disallowed.", |
| sync_state.FormatHandle(event_handle).c_str(), vvl::String(sync_event->last_command)); |
| break; |
| } |
| case SyncEventState::MissingSetEvent: { |
| // TODO: There are conditions at queue submit time where we can definitively say that |
| // a missing set event is an error. Add those if not captured in CoreChecks |
| break; |
| } |
| default: |
| assert(ignore_reason == SyncEventState::NotIgnored); |
| } |
| } else if (barrier_set.image_barriers.size()) { |
| const auto &image_memory_barriers = barrier_set.image_barriers; |
| const auto *context = exec_context.GetCurrentAccessContext(); |
| assert(context); |
| for (const auto &image_memory_barrier : image_memory_barriers) { |
| if (!image_memory_barrier.layout_transition) continue; |
| const auto *image_state = image_memory_barrier.image.get(); |
| if (!image_state) continue; |
| const auto &subresource_range = image_memory_barrier.subresource_range; |
| const auto &src_access_scope = image_memory_barrier.barrier.src_access_scope; |
| const auto hazard = context->DetectImageBarrierHazard( |
| *image_state, subresource_range, sync_event->scope.exec_scope, src_access_scope, queue_id, |
| sync_event->FirstScope(), sync_event->first_scope_tag, AccessContext::DetectOptions::kDetectAll); |
| if (hazard.IsHazard()) { |
| LogObjectList objlist(exec_context.Handle(), image_state->Handle()); |
| const std::string resource_description = sync_state.FormatHandle(image_state->Handle()); |
| const std::string error = sync_state.error_messages_.ImageBarrierError( |
| hazard, exec_context, command_, resource_description, image_memory_barrier); |
| skip |= sync_state.SyncError(hazard.Hazard(), image_state->Handle(), loc, error); |
| break; |
| } |
| } |
| } |
| // TODO: Add infrastructure for checking pDependencyInfo's vs. CmdSetEvent2 VUID - vkCmdWaitEvents2KHR - pEvents - |
| // 03839 |
| barrier_set_index += barrier_set_incr; |
| } |
| |
| // Note that we can't check for HOST in pEvents as we don't track that set event type |
| const auto extra_stage_bits = (barrier_mask_params & ~VK_PIPELINE_STAGE_2_HOST_BIT) & ~event_stage_masks; |
| if (extra_stage_bits) { |
| assert(vvl::Func::vkCmdWaitEvents == command_); |
| // Issue error message that event waited for is not in wait events scope |
| const char *const message = |
| "srcStageMask 0x%" PRIx64 " contains stages not present in pEvents stageMask. Extra stages are %s.%s"; |
| const auto handle = exec_context.Handle(); |
| if (!events_not_found) { |
| skip |= sync_state.LogError("VUID-vkCmdWaitEvents-srcStageMask-01158", handle, loc, message, barrier_mask_params, |
| sync_utils::StringPipelineStageFlags(extra_stage_bits).c_str(), ""); |
| } |
| } |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpWaitEvents::Record(CommandBufferAccessContext *cb_context) { |
| const auto tag = cb_context->NextCommandTag(command_); |
| |
| ReplayRecord(*cb_context, tag); |
| return tag; |
| } |
| |
| // Need to restrict to only valid exec and access scope for this event |
| static SyncBarrier RestrictToEvent(const SyncBarrier &barrier, const SyncEventState &sync_event) { |
| SyncBarrier result = barrier; |
| result.src_exec_scope.exec_scope = sync_event.scope.exec_scope & barrier.src_exec_scope.exec_scope; |
| result.src_access_scope = sync_event.scope.valid_accesses & barrier.src_access_scope; |
| return result; |
| } |
| |
| void SyncOpWaitEvents::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| // Unlike PipelineBarrier, WaitEvent is *not* limited to accesses within the current subpass (if any) and thus needs to import |
| // all accesses. Can instead import for all first_scopes, or a union of them, if this becomes a performance/memory issue, |
| // but with no idea of the performance of the union, nor of whether it even matters... take the simplest approach here, |
| if (!exec_context.ValidForSyncOps()) return; |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| SyncEventsContext *events_context = exec_context.GetCurrentEventsContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| access_context->ResolvePreviousAccesses(); |
| |
| assert(barrier_sets_.size() == 1 || (barrier_sets_.size() == events_.size())); |
| |
| // Apply markup action. |
| // The markup action does not change any access state but it can trim the access map according to the |
| // provided range and creates infill ranges if necessary (for layout transitions). The purpose of all |
| // this is to ensure that after markup action the topology of access map ranges is finalized for the |
| // duration of barrier application (so we can cache pointers to specific access states with a goal |
| // to apply pending barriers in the end). |
| // |
| // NOTE: event's global barriers can split() access map because EventSimpleRangeGenerator filters kFullRange. |
| // That's why, in contrast to SyncOpPipelineBarrier, we need apply markup action also to global barriers. |
| // TODO: need a test that demonstrates this (when doing some work on syncval events) |
| size_t barrier_set_index = 0; |
| size_t barrier_set_incr = (barrier_sets_.size() == 1) ? 0 : 1; |
| for (auto &event_shared : events_) { |
| if (!event_shared.get()) continue; |
| auto *sync_event = events_context->GetFromShared(event_shared); |
| |
| sync_event->last_command = command_; |
| sync_event->last_command_tag = exec_tag; |
| |
| const auto &barrier_set = barrier_sets_[barrier_set_index]; |
| if (!sync_event->IsIgnoredByWait(command_, barrier_set.src_exec_scope.mask_param)) { |
| for (const SyncBufferBarrier &barrier : barrier_set.buffer_barriers) { |
| if (SimpleBinding(*barrier.buffer)) { |
| const VkDeviceSize base_address = ResourceBaseAddress(*barrier.buffer); |
| const AccessRange range = barrier.range + base_address; |
| EventSimpleRangeGenerator filtered_range_gen(sync_event->FirstScope(), range); |
| ApplyMarkupFunctor markup_action(false); |
| access_context->UpdateMemoryAccessState(markup_action, filtered_range_gen); |
| } |
| } |
| for (const SyncImageBarrier &barrier : barrier_set.image_barriers) { |
| const auto &sub_state = SubState(*barrier.image); |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(exec_context.GetSyncState().extensions, sub_state.base.create_info); |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(barrier.subresource_range, can_transition_depth_slices); |
| EventImageRangeGenerator filtered_range_gen(sync_event->FirstScope(), range_gen); |
| ApplyMarkupFunctor markup_action(barrier.layout_transition); |
| access_context->UpdateMemoryAccessState(markup_action, filtered_range_gen); |
| } |
| auto global_barriers_range_gen = EventSimpleRangeGenerator(sync_event->FirstScope(), kFullRange); |
| ApplyMarkupFunctor markup_action(false); |
| access_context->UpdateMemoryAccessState(markup_action, global_barriers_range_gen); |
| } |
| barrier_set_index += barrier_set_incr; |
| } |
| |
| // Apply barriers independently and store the result in the pending object. |
| PendingBarriers pending_barriers; |
| barrier_set_index = 0; |
| barrier_set_incr = (barrier_sets_.size() == 1) ? 0 : 1; |
| for (auto &event_shared : events_) { |
| if (!event_shared.get()) continue; |
| auto *sync_event = events_context->GetFromShared(event_shared); |
| |
| const auto &barrier_set = barrier_sets_[barrier_set_index]; |
| const auto &dst = barrier_set.dst_exec_scope; |
| if (!sync_event->IsIgnoredByWait(command_, barrier_set.src_exec_scope.mask_param)) { |
| // These apply barriers one at a time as the are restricted to the resource ranges specified per each barrier, |
| // but do not update the dependency chain information (but set the "pending" state) // s.t. the order independence |
| // of the barriers is maintained. |
| |
| for (const SyncBufferBarrier &barrier : barrier_set.buffer_barriers) { |
| if (SimpleBinding(*barrier.buffer)) { |
| const SyncBarrier event_barrier = RestrictToEvent(barrier.barrier, *sync_event); |
| const BarrierScope barrier_scope(event_barrier, queue_id, sync_event->first_scope_tag); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, event_barrier, false, vvl::kNoIndex32, |
| pending_barriers); |
| |
| const VkDeviceSize base_address = ResourceBaseAddress(*barrier.buffer); |
| const AccessRange range = barrier.range + base_address; |
| EventSimpleRangeGenerator range_gen(sync_event->FirstScope(), range); |
| |
| access_context->UpdateMemoryAccessState(collect_barriers, range_gen); |
| } |
| } |
| for (const SyncImageBarrier &barrier : barrier_set.image_barriers) { |
| const SyncBarrier event_barrier = RestrictToEvent(barrier.barrier, *sync_event); |
| const BarrierScope barrier_scope(event_barrier, queue_id, sync_event->first_scope_tag); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, event_barrier, barrier.layout_transition, |
| barrier.handle_index, pending_barriers); |
| |
| const auto &sub_state = SubState(*barrier.image); |
| const bool can_transition_depth_slices = |
| CanTransitionDepthSlices(exec_context.GetSyncState().extensions, sub_state.base.create_info); |
| ImageRangeGen range_gen = sub_state.MakeImageRangeGen(barrier.subresource_range, can_transition_depth_slices); |
| EventImageRangeGenerator filtered_range_gen(sync_event->FirstScope(), range_gen); |
| |
| access_context->UpdateMemoryAccessState(collect_barriers, filtered_range_gen); |
| } |
| // TODO: because each iteration applies functor to the same range, investigate if it is |
| // beneficial for the functor to support multiple barriers, so we traverse access map once. |
| auto global_range_gen = EventSimpleRangeGenerator(sync_event->FirstScope(), kFullRange); |
| for (const auto &barrier : barrier_set.memory_barriers) { |
| const SyncBarrier event_barrier = RestrictToEvent(barrier, *sync_event); |
| const BarrierScope barrier_scope(event_barrier, queue_id, sync_event->first_scope_tag); |
| CollectBarriersFunctor collect_barriers(*access_context, barrier_scope, event_barrier, false, vvl::kNoIndex32, |
| pending_barriers); |
| |
| auto range_gen = global_range_gen; // intentional copy |
| access_context->UpdateMemoryAccessState(collect_barriers, range_gen); |
| } |
| |
| // Apply the global barrier to the event itself (for race condition tracking) |
| // Events don't happen at a stage, so we need to store the unexpanded ALL_COMMANDS if set for inter-event-calls |
| sync_event->barriers = dst.mask_param & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| sync_event->barriers |= dst.exec_scope; |
| } else { |
| // We ignored this wait, so we don't have any effective synchronization barriers for it. |
| sync_event->barriers = 0U; |
| } |
| barrier_set_index += barrier_set_incr; |
| } |
| |
| // Update access states with collected barriers |
| pending_barriers.Apply(exec_tag); |
| } |
| |
| bool SyncOpWaitEvents::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| return DoValidate(replay.GetExecutionContext(), replay.GetBaseTag() + recorded_tag); |
| } |
| |
| void SyncOpWaitEvents::MakeEventsList(const SyncValidator &sync_state, uint32_t event_count, const VkEvent *events) { |
| events_.reserve(event_count); |
| for (uint32_t event_index = 0; event_index < event_count; event_index++) { |
| events_.emplace_back(sync_state.Get<vvl::Event>(events[event_index])); |
| } |
| } |
| |
| SyncOpResetEvent::SyncOpResetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event, |
| VkPipelineStageFlags2 stageMask) |
| : SyncOpBase(command), event_(sync_state.Get<vvl::Event>(event)), exec_scope_(SyncExecScope::MakeSrc(queue_flags, stageMask)) {} |
| |
| bool SyncOpResetEvent::Validate(const CommandBufferAccessContext &cb_context) const { |
| return DoValidate(cb_context, ResourceUsageRecord::kMaxIndex); |
| } |
| |
| bool SyncOpResetEvent::DoValidate(const CommandExecutionContext &exec_context, const ResourceUsageTag base_tag) const { |
| auto *events_context = exec_context.GetCurrentEventsContext(); |
| assert(events_context); |
| bool skip = false; |
| if (!events_context) return skip; |
| |
| const auto &sync_state = exec_context.GetSyncState(); |
| const auto *sync_event = events_context->Get(event_); |
| if (!sync_event) return skip; // Core, Lifetimes, or Param check needs to catch invalid events. |
| |
| if (sync_event->last_command_tag > base_tag) return skip; // if we validated this in recording of the secondary, don't repeat |
| |
| const char *const set_wait = |
| "%s %s operation following %s without intervening execution barrier, is a race condition and may result in data " |
| "hazards."; |
| const char *message = set_wait; // Only one message this call. |
| if (!sync_event->HasBarrier(exec_scope_.mask_param, exec_scope_.exec_scope)) { |
| const char *vuid = nullptr; |
| switch (sync_event->last_command) { |
| case vvl::Func::vkCmdSetEvent: |
| case vvl::Func::vkCmdSetEvent2KHR: |
| case vvl::Func::vkCmdSetEvent2: |
| // Needs a barrier between set and reset |
| vuid = "SYNC-vkCmdResetEvent-missingbarrier-set"; |
| break; |
| case vvl::Func::vkCmdWaitEvents: |
| case vvl::Func::vkCmdWaitEvents2KHR: |
| case vvl::Func::vkCmdWaitEvents2: { |
| // Needs to be in the barriers chain (either because of a barrier, or because of dstStageMask |
| vuid = "SYNC-vkCmdResetEvent-missingbarrier-wait"; |
| break; |
| } |
| case vvl::Func::Empty: |
| case vvl::Func::vkCmdResetEvent: |
| case vvl::Func::vkCmdResetEvent2KHR: |
| case vvl::Func::vkCmdResetEvent2: |
| break; // Valid, but nothing to do |
| default: |
| assert(false); |
| break; |
| } |
| if (vuid) { |
| const Location loc(command_); |
| skip |= sync_state.LogError(vuid, event_->Handle(), loc, message, sync_state.FormatHandle(event_->Handle()).c_str(), |
| CmdName(), vvl::String(sync_event->last_command)); |
| } |
| } |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpResetEvent::Record(CommandBufferAccessContext *cb_context) { |
| const auto tag = cb_context->NextCommandTag(command_); |
| ReplayRecord(*cb_context, tag); |
| return tag; |
| } |
| |
| bool SyncOpResetEvent::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| return DoValidate(replay.GetExecutionContext(), replay.GetBaseTag() + recorded_tag); |
| } |
| |
| void SyncOpResetEvent::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| if (!exec_context.ValidForSyncOps()) return; |
| SyncEventsContext *events_context = exec_context.GetCurrentEventsContext(); |
| |
| auto *sync_event = events_context->GetFromShared(event_); |
| if (!sync_event) return; // Core, Lifetimes, or Param check needs to catch invalid events. |
| |
| // Update the event state |
| sync_event->last_command = command_; |
| sync_event->last_command_tag = exec_tag; |
| sync_event->unsynchronized_set = vvl::Func::Empty; |
| sync_event->ResetFirstScope(); |
| sync_event->barriers = 0U; |
| } |
| |
| SyncOpSetEvent::SyncOpSetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event, |
| VkPipelineStageFlags2 stageMask, const AccessContext *access_context) |
| : SyncOpBase(command), |
| event_(sync_state.Get<vvl::Event>(event)), |
| src_exec_scope_(SyncExecScope::MakeSrc(queue_flags, stageMask)), |
| dep_info_() { |
| // Snapshot the current access_context for later inspection at wait time. |
| // NOTE: This appears brute force, but given that we only save a "first-last" model of access history, the current |
| // access context (include barrier state for chaining) won't necessarily contain the needed information at Wait |
| // or Submit time reference. |
| if (access_context) { |
| auto new_context = std::make_shared<AccessContext>(sync_state); |
| new_context->InitFrom(*access_context); |
| recorded_context_ = new_context; |
| } |
| } |
| |
| SyncOpSetEvent::SyncOpSetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event, |
| const VkDependencyInfo &dep_info, const AccessContext *access_context) |
| : SyncOpBase(command), |
| event_(sync_state.Get<vvl::Event>(event)), |
| src_exec_scope_(SyncExecScope::MakeSrc(queue_flags, sync_utils::GetExecScopes(dep_info).src)), |
| dep_info_(new vku::safe_VkDependencyInfo(&dep_info)) { |
| if (access_context) { |
| auto new_context = std::make_shared<AccessContext>(sync_state); |
| new_context->InitFrom(*access_context); |
| recorded_context_ = new_context; |
| } |
| } |
| |
| bool SyncOpSetEvent::Validate(const CommandBufferAccessContext &cb_context) const { |
| return DoValidate(cb_context, ResourceUsageRecord::kMaxIndex); |
| } |
| bool SyncOpSetEvent::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| return DoValidate(replay.GetExecutionContext(), replay.GetBaseTag() + recorded_tag); |
| } |
| |
| bool SyncOpSetEvent::DoValidate(const CommandExecutionContext &exec_context, const ResourceUsageTag base_tag) const { |
| bool skip = false; |
| |
| const auto &sync_state = exec_context.GetSyncState(); |
| auto *events_context = exec_context.GetCurrentEventsContext(); |
| assert(events_context); |
| if (!events_context) return skip; |
| |
| const auto *sync_event = events_context->Get(event_); |
| if (!sync_event) return skip; // Core, Lifetimes, or Param check needs to catch invalid events. |
| |
| if (sync_event->last_command_tag >= base_tag) return skip; // for replay we don't want to revalidate internal "last commmand" |
| |
| const char *const reset_set = |
| "%s %s operation following %s without intervening execution barrier, is a race condition and may result in data " |
| "hazards."; |
| const char *const wait = |
| "%s %s operation following %s without intervening vkCmdResetEvent, may result in data hazard and is ignored."; |
| |
| if (!sync_event->HasBarrier(src_exec_scope_.mask_param, src_exec_scope_.exec_scope)) { |
| const char *vuid_stem = nullptr; |
| const char *message = nullptr; |
| switch (sync_event->last_command) { |
| case vvl::Func::vkCmdResetEvent: |
| case vvl::Func::vkCmdResetEvent2KHR: |
| case vvl::Func::vkCmdResetEvent2: |
| // Needs a barrier between reset and set |
| vuid_stem = "-missingbarrier-reset"; |
| message = reset_set; |
| break; |
| case vvl::Func::vkCmdSetEvent: |
| case vvl::Func::vkCmdSetEvent2KHR: |
| case vvl::Func::vkCmdSetEvent2: |
| // Needs a barrier between set and set |
| vuid_stem = "-missingbarrier-set"; |
| message = reset_set; |
| break; |
| case vvl::Func::vkCmdWaitEvents: |
| case vvl::Func::vkCmdWaitEvents2KHR: |
| case vvl::Func::vkCmdWaitEvents2: |
| // Needs a barrier or is in second execution scope |
| vuid_stem = "-missingbarrier-wait"; |
| message = wait; |
| break; |
| default: |
| // The only other valid last command that wasn't one. |
| assert(sync_event->last_command == vvl::Func::Empty); |
| break; |
| } |
| if (vuid_stem) { |
| assert(nullptr != message); |
| const Location loc(command_); |
| std::string vuid("SYNC-"); |
| vuid.append(CmdName()).append(vuid_stem); |
| skip |= |
| sync_state.LogError(vuid.c_str(), event_->Handle(), loc, message, sync_state.FormatHandle(event_->Handle()).c_str(), |
| CmdName(), vvl::String(sync_event->last_command)); |
| } |
| } |
| |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpSetEvent::Record(CommandBufferAccessContext *cb_context) { |
| const auto tag = cb_context->NextCommandTag(command_); |
| auto *events_context = cb_context->GetCurrentEventsContext(); |
| const QueueId queue_id = cb_context->GetQueueId(); |
| assert(recorded_context_); |
| if (recorded_context_ && events_context) { |
| DoRecord(queue_id, tag, recorded_context_, events_context); |
| } |
| return tag; |
| } |
| |
| void SyncOpSetEvent::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| // Create a copy of the current context, and merge in the state snapshot at record set event time |
| // Note: we mustn't change the recorded context copy, as a given CB could be submitted more than once (in generaL) |
| if (!exec_context.ValidForSyncOps()) return; |
| SyncEventsContext *events_context = exec_context.GetCurrentEventsContext(); |
| AccessContext *access_context = exec_context.GetCurrentAccessContext(); |
| const QueueId queue_id = exec_context.GetQueueId(); |
| |
| // Note: merged_context is a copy of the access_context, combined with the recorded context |
| auto merged_context = std::make_shared<AccessContext>(*access_context->validator); |
| merged_context->InitFrom(*access_context); |
| merged_context->ResolveFromContext(QueueTagOffsetBarrierAction(queue_id, exec_tag), *recorded_context_); |
| merged_context->TrimAndClearFirstAccess(); // Ensure the copy is minimal and normalized |
| DoRecord(queue_id, exec_tag, merged_context, events_context); |
| } |
| |
| void SyncOpSetEvent::DoRecord(QueueId queue_id, ResourceUsageTag tag, const std::shared_ptr<const AccessContext> &access_context, |
| SyncEventsContext *events_context) const { |
| auto *sync_event = events_context->GetFromShared(event_); |
| if (!sync_event) return; // Core, Lifetimes, or Param check needs to catch invalid events. |
| |
| // NOTE: We're going to simply record the sync scope here, as anything else would be implementation defined/undefined |
| // and we're issuing errors re: missing barriers between event commands, which if the user fixes would fix |
| // any issues caused by naive scope setting here. |
| |
| // What happens with two SetEvent is that one cannot know what group of operations will be waited for. |
| // Given: |
| // Stuff1; SetEvent; Stuff2; SetEvent; WaitEvents; |
| // WaitEvents cannot know which of Stuff1, Stuff2, or both has completed execution. |
| |
| if (!sync_event->HasBarrier(src_exec_scope_.mask_param, src_exec_scope_.exec_scope)) { |
| sync_event->unsynchronized_set = sync_event->last_command; |
| sync_event->ResetFirstScope(); |
| } else if (!sync_event->first_scope) { |
| // We only set the scope if there isn't one |
| sync_event->scope = src_exec_scope_; |
| |
| // Save the shared_ptr to copy of the access_context present at set time (sent us by the caller) |
| sync_event->first_scope = access_context; |
| sync_event->unsynchronized_set = vvl::Func::Empty; |
| sync_event->first_scope_tag = tag; |
| } |
| // TODO: Store dep_info_ shared ptr in sync_state for WaitEvents2 validation |
| sync_event->last_command = command_; |
| sync_event->last_command_tag = tag; |
| sync_event->barriers = 0U; |
| } |
| |
| SyncOpBeginRenderPass::SyncOpBeginRenderPass(vvl::Func command, const SyncValidator &sync_state, |
| const VkRenderPassBeginInfo *pRenderPassBegin, |
| const VkSubpassBeginInfo *pSubpassBeginInfo) |
| : SyncOpBase(command), rp_context_(nullptr) { |
| if (pRenderPassBegin) { |
| rp_state_ = sync_state.Get<vvl::RenderPass>(pRenderPassBegin->renderPass); |
| renderpass_begin_info_ = vku::safe_VkRenderPassBeginInfo(pRenderPassBegin); |
| auto fb_state = sync_state.Get<vvl::Framebuffer>(pRenderPassBegin->framebuffer); |
| if (fb_state) { |
| shared_attachments_ = sync_state.device_state->GetAttachmentViews(*renderpass_begin_info_.ptr(), *fb_state); |
| // TODO: Revisit this when all attachment validation is through SyncOps to see if we can discard the plain pointer copy |
| // Note that this a safe to presist as long as shared_attachments is not cleared |
| attachments_.reserve(shared_attachments_.size()); |
| for (const auto &attachment : shared_attachments_) { |
| attachments_.emplace_back(attachment.get()); |
| } |
| } |
| if (pSubpassBeginInfo) { |
| subpass_begin_info_ = vku::safe_VkSubpassBeginInfo(pSubpassBeginInfo); |
| } |
| } |
| } |
| |
| bool SyncOpBeginRenderPass::Validate(const CommandBufferAccessContext &cb_context) const { |
| // Check if any of the layout transitions are hazardous.... but we don't have the renderpass context to work with, so we |
| bool skip = false; |
| |
| assert(rp_state_.get()); |
| if (nullptr == rp_state_.get()) return skip; |
| auto &rp_state = *rp_state_.get(); |
| |
| const uint32_t subpass = 0; |
| |
| // Construct the state to validate against (since validation is const and RecordCmdBeginRenderPass hasn't happened yet). |
| // TODO: investigate if using nullptr in InitFrom is safe (this just follows the initial implementation - it assumes |
| // that array of subpass dependencies won't be indexed, but it's not obvious). |
| AccessContext temp_context(cb_context.GetSyncState()); |
| |
| temp_context.InitFrom(subpass, cb_context.GetQueueFlags(), rp_state.subpass_dependencies, nullptr, |
| cb_context.GetCurrentAccessContext()); |
| |
| // Validate attachment operations |
| if (attachments_.empty()) return skip; |
| const auto &render_area = renderpass_begin_info_.renderArea; |
| const uint32_t render_pass_instance_id = cb_context.GetCurrentRenderPassInstanceId(); |
| |
| // Since the isn't a valid RenderPassAccessContext until Record, needs to create the view/generator list... we could limit this |
| // by predicating on whether subpass 0 uses the attachment if it is too expensive to create the full list redundantly here. |
| // More broadly we could look at thread specific state shared between Validate and Record as is done for other heavyweight |
| // operations (though it's currently a messy approach) |
| const AttachmentViewGenVector view_gens = RenderPassAccessContext::CreateAttachmentViewGen(render_area, attachments_); |
| skip |= RenderPassAccessContext::ValidateLayoutTransitions(cb_context, temp_context, rp_state, render_area, |
| render_pass_instance_id, subpass, view_gens, command_); |
| |
| // Validate load operations if there were no layout transition hazards |
| if (!skip) { |
| RenderPassAccessContext::RecordLayoutTransitions(rp_state, subpass, view_gens, kInvalidTag, temp_context); |
| skip |= RenderPassAccessContext::ValidateLoadOperation(cb_context, temp_context, rp_state, render_area, |
| render_pass_instance_id, subpass, view_gens, command_); |
| } |
| |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpBeginRenderPass::Record(CommandBufferAccessContext *cb_context) { |
| assert(rp_state_.get()); |
| if (nullptr == rp_state_.get()) return cb_context->NextCommandTag(command_); |
| const ResourceUsageTag begin_tag = |
| cb_context->RecordBeginRenderPass(command_, *rp_state_.get(), renderpass_begin_info_.renderArea, attachments_); |
| |
| // Note: this state update must be after RecordBeginRenderPass as there is no current render pass until that function runs |
| rp_context_ = cb_context->GetCurrentRenderPassContext(); |
| |
| return begin_tag; |
| } |
| |
| bool SyncOpBeginRenderPass::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| CommandExecutionContext &exec_context = replay.GetExecutionContext(); |
| // this operation is not allowed in secondary command buffers |
| assert(exec_context.Handle().type == kVulkanObjectTypeQueue); |
| auto &batch_context = static_cast<QueueBatchContext &>(exec_context); |
| batch_context.BeginRenderPassReplaySetup(replay, *this); |
| |
| // Only the layout transitions happen at the replay tag, loadOp's happen at a subsequent tag |
| ResourceUsageRange first_use_range = {recorded_tag, recorded_tag + 1}; |
| return replay.DetectFirstUseHazard(first_use_range); |
| } |
| |
| void SyncOpBeginRenderPass::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| // All the needed replay state changes (for the layout transition, and context update) have to happen in ReplayValidate |
| } |
| |
| SyncOpNextSubpass::SyncOpNextSubpass(vvl::Func command, const SyncValidator &sync_state, |
| const VkSubpassBeginInfo *pSubpassBeginInfo, const VkSubpassEndInfo *pSubpassEndInfo) |
| : SyncOpBase(command) { |
| if (pSubpassBeginInfo) { |
| subpass_begin_info_.initialize(pSubpassBeginInfo); |
| } |
| if (pSubpassEndInfo) { |
| subpass_end_info_.initialize(pSubpassEndInfo); |
| } |
| } |
| |
| bool SyncOpNextSubpass::Validate(const CommandBufferAccessContext &cb_context) const { |
| bool skip = false; |
| const auto *renderpass_context = cb_context.GetCurrentRenderPassContext(); |
| if (!renderpass_context) return skip; |
| |
| skip |= renderpass_context->ValidateNextSubpass(cb_context, command_); |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpNextSubpass::Record(CommandBufferAccessContext *cb_context) { |
| return cb_context->RecordNextSubpass(command_); |
| } |
| |
| bool SyncOpNextSubpass::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| // Any store/resolve operations happen before the NextSubpass tag so we can advance to the next subpass state |
| CommandExecutionContext &exec_context = replay.GetExecutionContext(); |
| // this operation is not allowed in secondary command buffers |
| assert(exec_context.Handle().type == kVulkanObjectTypeQueue); |
| auto &batch_context = static_cast<QueueBatchContext &>(exec_context); |
| batch_context.NextSubpassReplaySetup(replay); |
| |
| // Only the layout transitions happen at the replay tag, loadOp's happen at a subsequent tag |
| ResourceUsageRange first_use_range = {recorded_tag, recorded_tag + 1}; |
| return replay.DetectFirstUseHazard(first_use_range); |
| } |
| |
| void SyncOpNextSubpass::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const { |
| // All the needed replay state changes (for the layout transition, and context update) have to happen in ReplayValidate |
| } |
| SyncOpEndRenderPass::SyncOpEndRenderPass(vvl::Func command, const SyncValidator &sync_state, |
| const VkSubpassEndInfo *pSubpassEndInfo) |
| : SyncOpBase(command) { |
| if (pSubpassEndInfo) { |
| subpass_end_info_.initialize(pSubpassEndInfo); |
| } |
| } |
| |
| bool SyncOpEndRenderPass::Validate(const CommandBufferAccessContext &cb_context) const { |
| bool skip = false; |
| const auto *renderpass_context = cb_context.GetCurrentRenderPassContext(); |
| |
| if (!renderpass_context) return skip; |
| skip |= renderpass_context->ValidateEndRenderPass(cb_context, command_); |
| return skip; |
| } |
| |
| ResourceUsageTag SyncOpEndRenderPass::Record(CommandBufferAccessContext *cb_context) { |
| return cb_context->RecordEndRenderPass(command_); |
| } |
| |
| bool SyncOpEndRenderPass::ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const { |
| // Any store/resolve operations happen before the EndRenderPass tag so we can ignore them |
| // Only the layout transitions happen at the replay tag |
| ResourceUsageRange first_use_range = {recorded_tag, recorded_tag + 1}; |
| bool skip = false; |
| skip |= replay.DetectFirstUseHazard(first_use_range); |
| |
| // We can cleanup here as the recorded tag represents the final layout transition (which is the last operation or the RP) |
| CommandExecutionContext &exec_context = replay.GetExecutionContext(); |
| // this operation is not allowed in secondary command buffers |
| assert(exec_context.Handle().type == kVulkanObjectTypeQueue); |
| auto &batch_context = static_cast<QueueBatchContext &>(exec_context); |
| batch_context.EndRenderPassReplayCleanup(replay); |
| |
| return skip; |
| } |
| |
| void SyncOpEndRenderPass::ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const {} |
| |
| ReplayState::ReplayState(CommandExecutionContext &exec_context, const CommandBufferAccessContext &recorded_context, |
| const ErrorObject &error_obj, uint32_t index, ResourceUsageTag base_tag) |
| : exec_context_(exec_context), recorded_context_(recorded_context), error_obj_(error_obj), index_(index), base_tag_(base_tag) {} |
| |
| AccessContext *ReplayState::ReplayStateRenderPassBegin(VkQueueFlags queue_flags, const SyncOpBeginRenderPass &begin_op, |
| const AccessContext &external_context) { |
| return rp_replay_.Begin(queue_flags, begin_op, external_context); |
| } |
| |
| AccessContext *ReplayState::ReplayStateRenderPassNext() { return rp_replay_.Next(); } |
| |
| void ReplayState::ReplayStateRenderPassEnd(AccessContext &external_context) { rp_replay_.End(external_context); } |
| |
| const AccessContext *ReplayState::GetRecordedAccessContext() const { |
| if (rp_replay_.begin_op) { |
| return rp_replay_.replay_context; |
| } |
| return recorded_context_.GetCurrentAccessContext(); |
| } |
| |
| bool ReplayState::DetectFirstUseHazard(const ResourceUsageRange &first_use_range) const { |
| bool skip = false; |
| if (first_use_range.non_empty()) { |
| // We're allowing for the Replay(Validate|Record) to modify the exec_context (e.g. for Renderpass operations), so |
| // we need to fetch the current access context each time |
| const AccessContext *access_context = GetRecordedAccessContext(); |
| |
| const HazardResult hazard = access_context->DetectFirstUseHazard(exec_context_.GetQueueId(), first_use_range, |
| *exec_context_.GetCurrentAccessContext()); |
| if (hazard.IsHazard()) { |
| const SyncValidator &sync_state = exec_context_.GetSyncState(); |
| LogObjectList objlist(exec_context_.Handle(), recorded_context_.Handle()); |
| const std::string error = sync_state.error_messages_.FirstUseError(hazard, exec_context_, recorded_context_, index_); |
| skip |= sync_state.SyncError(hazard.Hazard(), objlist, error_obj_.location, error); |
| } |
| } |
| return skip; |
| } |
| |
| bool ReplayState::ValidateFirstUse() { |
| if (!exec_context_.ValidForSyncOps()) return false; |
| |
| bool skip = false; |
| ResourceUsageRange first_use_range = {0, 0}; |
| |
| for (const auto &sync_op : recorded_context_.GetSyncOps()) { |
| // Set the range to cover all accesses until the next sync_op, and validate |
| first_use_range.end = sync_op.tag; |
| skip |= DetectFirstUseHazard(first_use_range); |
| |
| // Call to replay validate support for syncop with non-trivial replay |
| skip |= sync_op.sync_op->ReplayValidate(*this, sync_op.tag); |
| |
| // Record the barrier into the proxy context. |
| sync_op.sync_op->ReplayRecord(exec_context_, base_tag_ + sync_op.tag); |
| first_use_range.begin = sync_op.tag + 1; |
| } |
| |
| // and anything after the last syncop |
| first_use_range.end = ResourceUsageRecord::kMaxIndex; |
| skip |= DetectFirstUseHazard(first_use_range); |
| |
| return skip; |
| } |
| AccessContext *ReplayState::RenderPassReplayState::Begin(VkQueueFlags queue_flags, const SyncOpBeginRenderPass &begin_op_, |
| const AccessContext &external_context) { |
| const RenderPassAccessContext *rp_context = begin_op_.GetRenderPassAccessContext(); |
| assert(rp_context); |
| |
| begin_op = &begin_op_; |
| replay_context = &rp_context->GetSubpassContexts()[0]; |
| subpass = 0; |
| subpass_contexts = InitSubpassContexts(queue_flags, *rp_context->GetRenderPassState(), external_context); |
| |
| // Replace the Async contexts with the the async context of the "external" context |
| // For replay we don't care about async subpasses, just async queue batches |
| for (AccessContext &context : GetSubpassContexts()) { |
| context.ClearAsyncContexts(); |
| context.ImportAsyncContexts(external_context); |
| } |
| |
| return &subpass_contexts[0]; |
| } |
| |
| AccessContext *ReplayState::RenderPassReplayState::Next() { |
| subpass++; |
| |
| const RenderPassAccessContext *rp_context = begin_op->GetRenderPassAccessContext(); |
| |
| replay_context = &rp_context->GetSubpassContexts()[subpass]; |
| return &subpass_contexts[subpass]; |
| } |
| |
| void ReplayState::RenderPassReplayState::End(AccessContext &external_context) { |
| external_context.ResolveChildContexts(GetSubpassContexts()); |
| *this = RenderPassReplayState{}; |
| } |
| |
| vvl::span<AccessContext> ReplayState::RenderPassReplayState::GetSubpassContexts() { |
| return vvl::make_span(subpass_contexts.get(), |
| begin_op->GetRenderPassAccessContext()->GetRenderPassState()->create_info.subpassCount); |
| } |
| |
| void SyncEventsContext::ApplyBarrier(const SyncExecScope &src, const SyncExecScope &dst, ResourceUsageTag tag) { |
| const bool all_commands_bit = 0 != (src.mask_param & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); |
| for (auto &event_pair : map_) { |
| assert(event_pair.second); // Shouldn't be storing empty |
| auto &sync_event = *event_pair.second; |
| // Events don't happen at a stage, so we need to check and store the unexpanded ALL_COMMANDS if set for inter-event-calls |
| // But only if occuring before the tag |
| if (((sync_event.barriers & src.exec_scope) || all_commands_bit) && (sync_event.last_command_tag <= tag)) { |
| sync_event.barriers |= dst.exec_scope; |
| sync_event.barriers |= dst.mask_param & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| } |
| } |
| } |
| |
| void SyncEventsContext::ApplyTaggedWait(VkQueueFlags queue_flags, ResourceUsageTag tag) { |
| const SyncExecScope src_scope = |
| SyncExecScope::MakeSrc(queue_flags, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_2_HOST_BIT); |
| const SyncExecScope dst_scope = SyncExecScope::MakeDst(queue_flags, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT); |
| ApplyBarrier(src_scope, dst_scope, tag); |
| } |
| |
| SyncEventsContext &SyncEventsContext::DeepCopy(const SyncEventsContext &from) { |
| // We need a deep copy of the const context to update during validation phase |
| for (const auto &event : from.map_) { |
| map_.emplace(event.first, std::make_shared<SyncEventState>(*event.second)); |
| } |
| return *this; |
| } |
| |
| void SyncEventsContext::AddReferencedTags(ResourceUsageTagSet &referenced) const { |
| for (const auto &event : map_) { |
| const std::shared_ptr<const SyncEventState> &event_state = event.second; |
| if (event_state) { |
| event_state->AddReferencedTags(referenced); |
| } |
| } |
| } |
| |
| SyncEventState::SyncEventState(const SyncEventState::EventPointer &event_state) : SyncEventState() { |
| event = event_state; |
| destroyed = (event.get() == nullptr) || event_state->Destroyed(); |
| } |
| |
| void SyncEventState::ResetFirstScope() { |
| first_scope.reset(); |
| scope = SyncExecScope(); |
| first_scope_tag = 0; |
| } |
| |
| // Keep the "ignore this event" logic in same place for ValidateWait and RecordWait to use |
| SyncEventState::IgnoreReason SyncEventState::IsIgnoredByWait(vvl::Func command, VkPipelineStageFlags2 srcStageMask) const { |
| IgnoreReason reason = NotIgnored; |
| |
| if ((vvl::Func::vkCmdWaitEvents2KHR == command || vvl::Func::vkCmdWaitEvents2 == command) && |
| (vvl::Func::vkCmdSetEvent == last_command)) { |
| reason = SetVsWait2; |
| } else if ((last_command == vvl::Func::vkCmdResetEvent || last_command == vvl::Func::vkCmdResetEvent2KHR) && |
| !HasBarrier(0U, 0U)) { |
| reason = (last_command == vvl::Func::vkCmdResetEvent) ? ResetWaitRace : Reset2WaitRace; |
| } else if (unsynchronized_set != vvl::Func::Empty) { |
| reason = SetRace; |
| } else if (first_scope) { |
| const VkPipelineStageFlags2 missing_bits = scope.mask_param & ~srcStageMask; |
| // Note it is the "not missing bits" path that is the only "NotIgnored" path |
| if (missing_bits) reason = MissingStageBits; |
| } else { |
| reason = MissingSetEvent; |
| } |
| |
| return reason; |
| } |
| |
| bool SyncEventState::HasBarrier(VkPipelineStageFlags2 stageMask, VkPipelineStageFlags2 exec_scope_arg) const { |
| return (last_command == vvl::Func::Empty) || (stageMask & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT) || (barriers & exec_scope_arg) || |
| (barriers & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); |
| } |
| |
| void SyncEventState::AddReferencedTags(ResourceUsageTagSet &referenced) const { |
| if (first_scope) { |
| first_scope->AddReferencedTags(referenced); |
| } |
| } |
| |
| } // namespace syncval |