| /* |
| * 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_access_context.h" |
| #include "sync/sync_image.h" |
| #include "sync/sync_validation.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/video_session_state.h" |
| #include <vulkan/utility/vk_format_utils.h> |
| |
| namespace syncval { |
| |
| bool SimpleBinding(const vvl::Bindable& bindable) { return !bindable.sparse && bindable.Binding(); } |
| VkDeviceSize ResourceBaseAddress(const vvl::Buffer& buffer) { return buffer.GetFakeBaseAddress(); } |
| |
| void AccessContext::InitFrom(uint32_t subpass, VkQueueFlags queue_flags, |
| const std::vector<SubpassDependencyInfo>& subpass_dependency_infos, const AccessContext* contexts, |
| const AccessContext& external_context, QueueId queue_id) { |
| const SubpassDependencyInfo& info = subpass_dependency_infos[subpass]; |
| async_.reserve(info.async.size()); |
| for (const uint32_t async_subpass : info.async) { |
| // Start tags are not known at creation time (as it's done at BeginRenderpass) |
| async_.emplace_back(contexts[async_subpass], kInvalidTag, queue_id); |
| } |
| |
| // Initialize barriers for the preceding subpasses and the external src barrier. |
| // To resolve contexts, we usually need regular subpass contexts and the external |
| // src context, so the corresponding barriers are stored together. |
| subpass_barriers_.resize(subpass + 1); |
| for (const auto& [src_subpass, subpass_dependencies] : info.dependencies) { |
| subpass_barriers_[src_subpass] = SubpassBarrier(contexts[src_subpass], queue_flags, subpass_dependencies, queue_id); |
| } |
| subpass_barriers_[subpass] = SubpassBarrier(external_context, queue_flags, info.barrier_from_external, queue_id); |
| |
| // External dst barrier |
| dst_external_ = SubpassBarrier(*this, queue_flags, info.barrier_to_external, queue_id); |
| } |
| |
| void CollectBarriersFunctor::operator()(AccessState& access_state) const { |
| access_context.ApplyGlobalBarriers(access_state); |
| access_state.CollectPendingBarriers(barrier_scope, barrier, layout_transition, layout_transition_handle_index, |
| pending_barriers); |
| } |
| |
| void AccessContext::InitFrom(const AccessContext& other) { |
| access_state_map_.Assign(other.access_state_map_); |
| |
| async_ = other.async_; |
| start_tag_ = other.start_tag_; |
| |
| global_barriers_queue_ = other.global_barriers_queue_; |
| for (uint32_t i = 0; i < other.global_barrier_def_count_; i++) { |
| global_barrier_defs_[i] = other.global_barrier_defs_[i]; |
| } |
| global_barrier_def_count_ = other.global_barrier_def_count_; |
| global_barriers_ = other.global_barriers_; |
| |
| // TODO: the following assignments look incorrect: the copies will reference the old context. |
| // Find a scenario when this does not work, write a test and make a fix. |
| subpass_barriers_ = other.subpass_barriers_; |
| dst_external_ = other.dst_external_; |
| } |
| |
| void AccessContext::Reset() { |
| access_state_map_.Clear(); |
| async_.clear(); |
| start_tag_ = {}; |
| ResetGlobalBarriers(); |
| subpass_barriers_.clear(); |
| dst_external_ = {}; |
| } |
| |
| void AccessContext::RegisterGlobalBarrier(const SyncBarrier& barrier, QueueId queue_id) { |
| assert(global_barriers_.empty() || global_barriers_queue_ == queue_id); |
| |
| // Search for existing def |
| uint32_t def_index = 0; |
| for (; def_index < global_barrier_def_count_; def_index++) { |
| if (global_barrier_defs_[def_index].barrier == barrier) { |
| break; |
| } |
| } |
| // Register a new def if this barrier is encountered for the first time |
| if (def_index == global_barrier_def_count_) { |
| // Flush global barriers if all def slots are in use |
| if (global_barrier_def_count_ == kMaxGlobalBarrierDefCount) { |
| for (auto& [_, access] : access_state_map_) { |
| ApplyGlobalBarriers(access); |
| access.next_global_barrier_index = 0; // to match state after reset |
| } |
| ResetGlobalBarriers(); |
| def_index = 0; |
| } |
| |
| GlobalBarrierDef& new_def = global_barrier_defs_[global_barrier_def_count_++]; |
| new_def.barrier = barrier; |
| new_def.chain_mask = 0; |
| |
| // Update chain masks |
| for (uint32_t i = 0; i < global_barrier_def_count_ - 1; i++) { |
| GlobalBarrierDef& def = global_barrier_defs_[i]; |
| if ((new_def.barrier.src_exec_scope.exec_scope & def.barrier.dst_exec_scope.exec_scope) != 0) { |
| new_def.chain_mask |= 1u << i; |
| } |
| if ((def.barrier.src_exec_scope.exec_scope & new_def.barrier.dst_exec_scope.exec_scope) != 0) { |
| def.chain_mask |= 1u << (global_barrier_def_count_ - 1); |
| } |
| } |
| } |
| // A global barrier is just a reference to its def |
| global_barriers_.push_back(def_index); |
| global_barriers_queue_ = queue_id; |
| } |
| |
| void AccessContext::ApplyGlobalBarriers(AccessState& access_state) const { |
| const uint32_t global_barrier_count = GetGlobalBarrierCount(); |
| assert(access_state.next_global_barrier_index <= global_barrier_count); |
| if (access_state.next_global_barrier_index == global_barrier_count) { |
| return; // access state is up-to-date |
| } |
| uint32_t applied_barrier_mask = 0; // used to skip already applied barriers |
| uint32_t applied_count = 0; // used for early exit when all unique barriers are applied |
| uint32_t failed_mask = 0; // used to quickly test barriers that failed the first application attempt |
| |
| for (size_t i = access_state.next_global_barrier_index; i < global_barrier_count; i++) { |
| const uint32_t def_index = global_barriers_[i]; |
| const uint32_t def_mask = 1u << def_index; |
| assert(def_index < global_barrier_def_count_); |
| |
| const GlobalBarrierDef& def = global_barrier_defs_[def_index]; |
| |
| // Skip barriers that were already applied |
| if ((def_mask & applied_barrier_mask) != 0) { |
| continue; |
| } |
| |
| // If this barrier failed to apply initially, it can only be applied |
| // again if it can chain with one of the newly applied barriers |
| if ((def_mask & failed_mask) != 0) { |
| if ((def.chain_mask & applied_barrier_mask) == 0) { |
| continue; |
| } |
| } |
| |
| // TODO: for requests with multiple barriers we need to register them in groups |
| // and use PendingBarriers helper here. |
| const BarrierScope barrier_scope(def.barrier, global_barriers_queue_); |
| const bool is_barrier_applied = access_state.ApplyBarrier(barrier_scope, def.barrier); |
| if (is_barrier_applied) { |
| applied_barrier_mask |= def_mask; |
| applied_count++; |
| if (applied_count == global_barrier_def_count_) { |
| break; // no barriers left that can add new information |
| } |
| } else { |
| failed_mask |= def_mask; |
| } |
| } |
| access_state.next_global_barrier_index = global_barrier_count; |
| } |
| |
| void AccessContext::ResetGlobalBarriers() { |
| global_barriers_queue_ = kQueueIdInvalid; |
| global_barrier_def_count_ = 0; |
| global_barriers_.clear(); |
| } |
| |
| void AccessContext::Trim() { |
| for (auto& [range, access] : access_state_map_) { |
| access.Normalize(); |
| } |
| Consolidate(access_state_map_); |
| } |
| |
| void AccessContext::AddReferencedTags(ResourceUsageTagSet& used) const { |
| for (const auto& [range, access] : access_state_map_) { |
| access.GatherReferencedTags(used); |
| } |
| } |
| |
| void AccessContext::EraseContainedEntries(const AccessRange& range) { |
| auto pos = access_state_map_.LowerBound(range.begin); |
| while (pos != access_state_map_.end() && pos->first.begin < range.end) { |
| if (range.includes(pos->first)) { |
| pos = access_state_map_.Erase(pos); |
| } else { |
| ++pos; |
| } |
| } |
| } |
| |
| const SubpassBarrier& AccessContext::GetSubpassBarrier(uint32_t src_subpass) const { |
| if (src_subpass == VK_SUBPASS_EXTERNAL) { |
| return subpass_barriers_.back(); |
| } else { |
| assert(subpass_barriers_[src_subpass].src_subpass_context != nullptr); |
| return subpass_barriers_[src_subpass]; |
| } |
| } |
| |
| void AccessContext::ResolveFromContextRecursePrev(const AccessContext& from) { |
| auto noop_action = [](AccessState* access) {}; |
| from.ResolveAccessRangeRecursePrev(kFullRange, noop_action, *this, false); |
| } |
| |
| void AccessContext::ResolveFromSubpassContext(const ApplySubpassTransitionBarrierAction& subpass_transition_action, |
| const AccessContext& from_context, ImageRangeGenerator attachment_range_gen) { |
| for (; attachment_range_gen->non_empty(); ++attachment_range_gen) { |
| from_context.ResolveAccessRangeRecursePrev(*attachment_range_gen, subpass_transition_action, *this, true); |
| } |
| } |
| |
| void AccessContext::ResolveAllSubpassDependencies() { ResolveSubpassDependencies(kFullRange, *this, true); } |
| |
| void AccessContext::ResolveChildContexts(vvl::span<AccessContext> subpass_contexts) { |
| for (AccessContext& access_context : subpass_contexts) { |
| ApplySubpassBarrierAction barrier_action(access_context.GetDstExternalSubpassBarrier()); |
| access_context.ResolveAccessRange(kFullRange, barrier_action, *this); |
| } |
| } |
| |
| void AccessContext::ResolveSubpassDependencies(const AccessRange& range, AccessContext& resolve_context, bool infill, |
| const AccessStateFunction* previous_barrier_action) const { |
| for (const SubpassBarrier& subpass_barrier : subpass_barriers_) { |
| if (subpass_barrier.src_subpass_context) { |
| const ApplySubpassBarrierAction barrier_action(subpass_barrier, previous_barrier_action); |
| subpass_barrier.src_subpass_context->ResolveAccessRangeRecursePrev(range, barrier_action, resolve_context, infill); |
| } |
| } |
| } |
| |
| void AccessContext::ResolveAccessRange(const AccessRange& range, const AccessStateFunction& barrier_action, |
| AccessContext& resolve_context) const { |
| if (!range.non_empty()) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range.begin); |
| for (; pos != access_state_map_.end() && pos->first.begin < range.end; ++pos) { |
| const AccessRange src_range = pos->first & range; |
| AccessState src_access(pos->second); |
| ApplyGlobalBarriers(src_access); |
| |
| // The src_access copy joins resolve_context. |
| // Ensure global barriers already registered in resolve_context are not applied |
| src_access.next_global_barrier_index = resolve_context.GetGlobalBarrierCount(); |
| |
| barrier_action(&src_access); |
| resolve_context.ResolveAccessState(src_range, src_access); |
| } |
| } |
| |
| void AccessContext::ResolveAccessRangeRecursePrev(const AccessRange& range, const AccessStateFunction& barrier_action, |
| AccessContext& resolve_context, bool infill) const { |
| if (!range.non_empty()) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range.begin); |
| ResourceAddress begin = range.begin; |
| while (begin < range.end) { |
| const bool pos_at_end = (pos == access_state_map_.end()); |
| const bool gap = pos_at_end || begin < pos->first.begin; |
| if (gap) { |
| const ResourceAddress end = pos_at_end ? range.end : std::min(range.end, pos->first.begin); |
| ResolveGapsRecursePrev({begin, end}, resolve_context, infill, barrier_action); |
| begin = end; |
| continue; |
| } |
| const AccessRange src_range(begin, std::min(range.end, pos->first.end)); |
| AccessState src_access(pos->second); |
| ApplyGlobalBarriers(src_access); |
| |
| // The src_access copy joins resolve_context. |
| // Ensure global barriers already registered in resolve_context are not applied |
| src_access.next_global_barrier_index = resolve_context.GetGlobalBarrierCount(); |
| |
| barrier_action(&src_access); |
| resolve_context.ResolveAccessState(src_range, src_access); |
| begin = src_range.end; |
| ++pos; |
| } |
| } |
| |
| void AccessContext::ResolveAccessState(const AccessRange& range, const AccessState& src_access) { |
| auto pos = access_state_map_.LowerBound(range.begin); |
| ResourceAddress begin = range.begin; |
| while (begin < range.end) { |
| const bool pos_at_end = (pos == access_state_map_.end()); |
| const bool gap = pos_at_end || begin < pos->first.begin; |
| ResourceAddress end; |
| if (gap) { |
| end = pos_at_end ? range.end : std::min(range.end, pos->first.begin); |
| pos = access_state_map_.Insert(pos, {begin, end}, src_access); |
| } else { |
| end = std::min(range.end, pos->first.end); |
| pos = Split(pos, access_state_map_, {begin, end}); |
| AccessState& dst_access = pos->second; |
| ApplyGlobalBarriers(dst_access); |
| dst_access.Resolve(src_access); |
| } |
| begin = end; |
| ++pos; |
| } |
| } |
| |
| void AccessContext::ResolveGapsRecursePrev(const AccessRange& range, AccessContext& descent_context, bool infill, |
| const AccessStateFunction& previous_barrier_action) const { |
| assert(range.non_empty()); |
| if (!subpass_barriers_.empty()) { |
| ResolveSubpassDependencies(range, descent_context, infill, &previous_barrier_action); |
| return; |
| } |
| if (infill) { |
| AccessState access_state = AccessState::DefaultAccessState(); |
| // The following is not needed for correctness but is rather an optimization. We are going to fill |
| // the gaps and the application of the global barriers to an empty state is noop (nothing is in the |
| // barrier's source scope). Update the index to skip application of the registered global barriers. |
| access_state.next_global_barrier_index = descent_context.GetGlobalBarrierCount(); |
| |
| previous_barrier_action(&access_state); |
| descent_context.access_state_map_.InfillGaps(range, access_state); |
| } |
| } |
| |
| AccessMap::iterator AccessContext::ResolveGapRecursePrev(const AccessRange& gap_range, AccessMap::iterator pos_hint) { |
| assert(gap_range.non_empty()); |
| if (!subpass_barriers_.empty()) { |
| ResolveSubpassDependencies(gap_range, *this, true); |
| return access_state_map_.LowerBound(gap_range.begin); |
| } |
| AccessState access_state = AccessState::DefaultAccessState(); |
| // The next line is not needed for correctness but is rather an optimization. We are going to fill |
| // the gaps and the application of the global barriers to an empty state is noop (nothing is in the |
| // barrier's source scope). Update the index to skip application of the registered global barriers. |
| access_state.next_global_barrier_index = GetGlobalBarrierCount(); |
| |
| return access_state_map_.InfillGap(pos_hint, gap_range, access_state); |
| } |
| |
| // Update memory access state over the given range. |
| // This inserts new accesses for empty regions and updates existing accesses. |
| // The passed pos must either be a lower bound (can be the end iterator) or be strictly less than the range. |
| // Map entries that intersect range.begin or range.end are split at the intersection point. |
| AccessMap::iterator AccessContext::DoUpdateAccessState(AccessMap::iterator pos, const AccessRange& range, |
| SyncAccessIndex access_index, const AttachmentAccess& attachment_access, |
| ResourceUsageTagEx tag_ex, SyncFlags flags, QueueId queue_id) { |
| assert(range.non_empty()); |
| const SyncAccessInfo& access_info = GetAccessInfo(access_index); |
| |
| const auto end = access_state_map_.end(); |
| assert(pos == access_state_map_.LowerBound(range.begin) || pos->first.strictly_less(range)); |
| |
| if (pos != end && pos->first.strictly_less(range)) { |
| // pos is not a lower bound for the range (pos < range), but if the range is |
| // monotonically increasing, the next map entry may be the lower bound |
| ++pos; |
| |
| // If the new pos is not a lower bound, run the full search |
| if (pos != end && pos->first.strictly_less(range)) { |
| pos = access_state_map_.LowerBound(range.begin); |
| } |
| } |
| assert(pos == access_state_map_.LowerBound(range.begin)); |
| |
| if (pos != end && range.begin > pos->first.begin) { |
| // Lower bound starts before the range. |
| // Split the entry so that a new entry starts exactly at the range.begin |
| pos = access_state_map_.Split(pos, range.begin); |
| ++pos; |
| } |
| |
| // A write can make previously fragmented ranges identical. Merge those ranges now, |
| // so subsequent accesses (e.g. many draws) can visit less ranges during traversal |
| AccessMap::iterator merge_first = end; |
| AccessMap::iterator merge_last = end; |
| const auto finish_merge = [&]() { |
| if (merge_first != end && merge_first != merge_last) { |
| access_state_map_.Merge(merge_first, std::next(merge_last)); |
| } |
| }; |
| const auto track_updated_range = [&](AccessMap::iterator updated) { |
| if (syncAccessReadMask[access_index]) { |
| return; // merge only during writes |
| } |
| if (merge_first != end && merge_last->first.end == updated->first.begin && |
| merge_last->second.next_global_barrier_index == updated->second.next_global_barrier_index && |
| merge_last->second == updated->second) { |
| merge_last = updated; |
| } else { |
| finish_merge(); |
| merge_first = merge_last = updated; |
| } |
| }; |
| |
| AccessMap::index_type current_begin = range.begin; |
| while (current_begin < range.end) { |
| if (pos == end || current_begin < pos->first.begin) { |
| const AccessRange gap_range(current_begin, pos == end ? range.end : std::min(range.end, pos->first.begin)); |
| // Resolving previous contexts can import multiple entries into this gap |
| pos = ResolveGapRecursePrev(gap_range, pos); |
| } |
| assert(pos != end && current_begin == pos->first.begin); |
| |
| // Restrict the update to the given range |
| if (pos->first.end > range.end) { |
| pos = access_state_map_.Split(pos, range.end); |
| } |
| |
| AccessState& access_state = pos->second; |
| ApplyGlobalBarriers(access_state); |
| access_state.Update(access_info, attachment_access, tag_ex, flags, queue_id); |
| track_updated_range(pos); |
| |
| current_begin = pos->first.end; |
| ++pos; |
| } |
| finish_merge(); |
| return pos; |
| } |
| |
| void AccessContext::UpdateAccessState(const vvl::Buffer& buffer, SyncAccessIndex current_usage, const AccessRange& range, |
| ResourceUsageTagEx tag_ex, SyncFlags flags, QueueId queue_id) { |
| assert(range.valid()); |
| |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| if (!SimpleBinding(buffer)) { |
| return; |
| } |
| if (range.empty()) { |
| return; |
| } |
| |
| const VkDeviceSize base_address = ResourceBaseAddress(buffer); |
| const AccessRange buffer_range = range + base_address; |
| |
| auto pos = access_state_map_.LowerBound(buffer_range.begin); |
| DoUpdateAccessState(pos, buffer_range, current_usage, AttachmentAccess::NonAttachment(), tag_ex, flags, queue_id); |
| } |
| |
| void AccessContext::UpdateAccessState(ImageRangeGen& range_gen, SyncAccessIndex current_usage, ResourceUsageTagEx tag_ex, |
| SyncFlags flags, QueueId queue_id) { |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range_gen->begin); |
| for (; range_gen->non_empty(); ++range_gen) { |
| pos = DoUpdateAccessState(pos, *range_gen, current_usage, AttachmentAccess::NonAttachment(), tag_ex, flags, queue_id); |
| } |
| } |
| |
| void AccessContext::UpdateAttachmentAccessState(ImageRangeGen& range_gen, SyncAccessIndex current_usage, |
| const AttachmentAccess& attachment_access, ResourceUsageTagEx tag_ex, |
| QueueId queue_id) { |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range_gen->begin); |
| for (; range_gen->non_empty(); ++range_gen) { |
| pos = DoUpdateAccessState(pos, *range_gen, current_usage, attachment_access, tag_ex, 0, queue_id); |
| } |
| } |
| |
| void AccessContext::UpdateAttachmentAccessState(const AttachmentViewGen& view_gen, AttachmentViewGen::Gen gen_type, |
| SyncAccessIndex current_usage, const AttachmentAccess& attachment_access, |
| ResourceUsageTagEx tag_ex, uint32_t view_mask, QueueId queue_id) { |
| if (view_mask == 0) { |
| const bool draw_access = attachment_access.type == AttachmentAccessType::Access; |
| const AttachmentViewGen::Gen optimized_gen_type = draw_access ? view_gen.GetOptimizedDrawGen(gen_type) : gen_type; |
| ImageRangeGen range_gen = view_gen.GetRangeGen(optimized_gen_type); |
| |
| // LOAD only reads the render area. Track the draw over the whole subresource only if its |
| // write has no hazard. Check again at submission, when accesses from earlier command buffers |
| // are also known |
| if (optimized_gen_type != gen_type && view_gen.DrawOptimizationNeedsHazardCheck(gen_type)) { |
| ImageRangeGen probe = range_gen; |
| if (DetectAttachmentHazard(probe, current_usage, attachment_access, queue_id).IsHazard()) { |
| range_gen = view_gen.GetRangeGen(gen_type); |
| } |
| } |
| UpdateAttachmentAccessState(range_gen, current_usage, attachment_access, tag_ex, queue_id); |
| } else { |
| uint32_t view_index = 0; |
| while (view_mask) { |
| if (view_mask & 1) { |
| ImageRangeGen range_gen = view_gen.GetRangeGen(gen_type, view_index); |
| UpdateAttachmentAccessState(range_gen, current_usage, attachment_access, tag_ex, queue_id); |
| } |
| view_mask >>= 1; |
| view_index++; |
| } |
| } |
| } |
| |
| // Caller must ensure that lifespan of this is less than the lifespan of from |
| void AccessContext::ImportAsyncContexts(const AccessContext& from) { |
| async_.insert(async_.end(), from.async_.begin(), from.async_.end()); |
| } |
| |
| void AccessContext::AddAsyncContext(const AccessContext& access_context, ResourceUsageTag tag, QueueId queue_id) { |
| async_.emplace_back(access_context, tag, queue_id); |
| } |
| |
| // For RenderPass time validation this is "start tag", for QueueSubmit, this is the earliest |
| // unsynchronized tag for the Queue being tested against (max synchrononous + 1, perhaps) |
| ResourceUsageTag AccessContext::AsyncReference::StartTag() const { return (tag_ == kInvalidTag) ? context_->StartTag() : tag_; } |
| |
| AttachmentViewGen::AttachmentViewGen(const vvl::ImageView& image_view, const VkOffset3D& offset, const VkExtent3D& extent, |
| bool feedback_enabled, VkImageAspectFlags use_full_extent_aspects, |
| VkImageAspectFlags try_full_extent_aspects) |
| : view_(&image_view), |
| feedback_enabled_(feedback_enabled), |
| use_full_extent_aspects_(use_full_extent_aspects), |
| try_full_extent_aspects_(try_full_extent_aspects) { |
| assert((use_full_extent_aspects & try_full_extent_aspects) == 0); |
| |
| const bool has_depth = vkuFormatHasDepth(image_view.create_info.format); |
| const bool has_stencil = vkuFormatHasStencil(image_view.create_info.format); |
| |
| // Attachment operations ignore the view's aspect mask for depth-stencil formats. |
| // MakeImageRangeGen uses the view's aspect mask by default, but accepts an override. |
| VkImageAspectFlags override_aspect_flags = 0; |
| if (has_depth || has_stencil) { |
| override_aspect_flags |= has_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0; |
| override_aspect_flags |= has_stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0; |
| } |
| |
| gen_store_[Gen::kViewSubresource].emplace(MakeImageRangeGen(image_view)); |
| gen_store_[Gen::kRenderArea].emplace(MakeImageRangeGen(image_view, offset, extent, override_aspect_flags)); |
| |
| if (has_depth) { |
| gen_store_[Gen::kDepthOnlyRenderArea].emplace(MakeImageRangeGen(image_view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT)); |
| gen_store_[Gen::kDepthOnlySubresource].emplace(MakeImageRangeGen(image_view, 0, VK_IMAGE_ASPECT_DEPTH_BIT)); |
| } |
| if (has_stencil) { |
| gen_store_[Gen::kStencilOnlyRenderArea].emplace(MakeImageRangeGen(image_view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT)); |
| gen_store_[Gen::kStencilOnlySubresource].emplace(MakeImageRangeGen(image_view, 0, VK_IMAGE_ASPECT_STENCIL_BIT)); |
| } |
| } |
| |
| ImageRangeGen AttachmentViewGen::GetRangeGen(AttachmentViewGen::Gen type, uint32_t view_index) const { |
| // Restrict image view's subresource range to a specific multiview layer |
| if (view_index != vvl::kNoIndex32) { |
| // TODO: Use type to select the aspects and whether to restrict this layer to the render area |
| VkImageSubresourceRange subresource = view_->normalized_subresource_range; |
| if (view_index >= subresource.layerCount) { |
| return {}; // invalid view index |
| } |
| subresource.baseArrayLayer += view_index; |
| subresource.layerCount = 1; |
| auto range_gen = SubState(*view_->image_state).MakeImageRangeGen(subresource, view_->is_depth_sliced); |
| return range_gen; |
| } |
| |
| assert(gen_store_[type].has_value()); |
| return *gen_store_[type]; |
| } |
| |
| AttachmentViewGen::Gen AttachmentViewGen::GetLoadGen(VkImageAspectFlags aspect_mask, VkAttachmentLoadOp load_op) const { |
| const bool full_extent = !feedback_enabled_ && LoadOpWrites(load_op); |
| return GetGen(aspect_mask, full_extent); |
| } |
| |
| AttachmentViewGen::Gen AttachmentViewGen::GetStoreGen(VkImageAspectFlags aspect_mask) const { |
| const bool full_extent = !feedback_enabled_; |
| return GetGen(aspect_mask, full_extent); |
| } |
| |
| AttachmentViewGen::Gen AttachmentViewGen::GetOptimizedDrawGen(Gen render_area_gen) const { |
| const VkImageAspectFlags aspect = GetDrawAspect(render_area_gen); |
| const bool full_extent = ((use_full_extent_aspects_ | try_full_extent_aspects_) & aspect) != 0; |
| return GetGen(aspect, full_extent); |
| } |
| |
| bool AttachmentViewGen::DrawOptimizationNeedsHazardCheck(Gen render_area_gen) const { |
| return (try_full_extent_aspects_ & GetDrawAspect(render_area_gen)) != 0; |
| } |
| |
| AttachmentViewGen::Gen AttachmentViewGen::GetGen(VkImageAspectFlags aspect_mask, bool full_extent) { |
| if (aspect_mask == VK_IMAGE_ASPECT_DEPTH_BIT) { |
| return full_extent ? kDepthOnlySubresource : kDepthOnlyRenderArea; |
| } |
| if (aspect_mask == VK_IMAGE_ASPECT_STENCIL_BIT) { |
| return full_extent ? kStencilOnlySubresource : kStencilOnlyRenderArea; |
| } |
| return full_extent ? kViewSubresource : kRenderArea; |
| } |
| |
| VkImageAspectFlags AttachmentViewGen::GetDrawAspect(Gen render_area_gen) { |
| switch (render_area_gen) { |
| case kRenderArea: |
| return VK_IMAGE_ASPECT_COLOR_BIT; |
| case kDepthOnlyRenderArea: |
| return VK_IMAGE_ASPECT_DEPTH_BIT; |
| case kStencilOnlyRenderArea: |
| return VK_IMAGE_ASPECT_STENCIL_BIT; |
| default: |
| // expect a generator returned by GetDrawGen() |
| assert(false); |
| return 0; |
| } |
| } |
| |
| SubpassBarrier::SubpassBarrier(const AccessContext& src_subpass_context, VkQueueFlags queue_flags, |
| const std::vector<const VkSubpassDependency2*>& subpass_dependencies, QueueId queue_id) |
| : src_subpass_context(&src_subpass_context), queue_id(queue_id) { |
| barriers.reserve(subpass_dependencies.size()); |
| for (const VkSubpassDependency2* dependency : subpass_dependencies) { |
| barriers.emplace_back(queue_flags, *dependency); |
| } |
| } |
| |
| } // namespace syncval |