John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 1 | /* Copyright (c) 2019 The Khronos Group Inc. |
| 2 | * Copyright (c) 2019 Valve Corporation |
| 3 | * Copyright (c) 2019 LunarG, Inc. |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | * |
| 17 | * Author: John Zulauf <jzulauf@lunarg.com> |
| 18 | */ |
| 19 | |
| 20 | #include <limits> |
| 21 | #include <vector> |
| 22 | #include "synchronization_validation.h" |
| 23 | |
| 24 | static const char *string_SyncHazardVUID(SyncHazard hazard) { |
| 25 | switch (hazard) { |
| 26 | case SyncHazard::NONE: |
| 27 | return "SYNC-NONE"; |
| 28 | break; |
| 29 | case SyncHazard::READ_AFTER_WRITE: |
| 30 | return "SYNC-HAZARD-READ_AFTER_WRITE"; |
| 31 | break; |
| 32 | case SyncHazard::WRITE_AFTER_READ: |
| 33 | return "SYNC-HAZARD-WRITE_AFTER_READ"; |
| 34 | break; |
| 35 | case SyncHazard::WRITE_AFTER_WRITE: |
| 36 | return "SYNC-HAZARD-WRITE_AFTER_WRITE"; |
| 37 | break; |
| 38 | default: |
| 39 | assert(0); |
| 40 | } |
| 41 | return "SYNC-HAZARD-INVALID"; |
| 42 | } |
| 43 | |
| 44 | static const char *string_SyncHazard(SyncHazard hazard) { |
| 45 | switch (hazard) { |
| 46 | case SyncHazard::NONE: |
| 47 | return "NONR"; |
| 48 | break; |
| 49 | case SyncHazard::READ_AFTER_WRITE: |
| 50 | return "READ_AFTER_WRITE"; |
| 51 | break; |
| 52 | case SyncHazard::WRITE_AFTER_READ: |
| 53 | return "WRITE_AFTER_READ"; |
| 54 | break; |
| 55 | case SyncHazard::WRITE_AFTER_WRITE: |
| 56 | return "WRITE_AFTER_WRITE"; |
| 57 | break; |
| 58 | default: |
| 59 | assert(0); |
| 60 | } |
| 61 | return "INVALID HAZARD"; |
| 62 | } |
| 63 | |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 64 | // Expand the pipeline stage without regard to whether the are valid w.r.t. queue or extension |
| 65 | VkPipelineStageFlags ExpandPipelineStages(VkQueueFlags queue_flags, VkPipelineStageFlags stage_mask) { |
| 66 | VkPipelineStageFlags expanded = stage_mask; |
| 67 | if (VK_PIPELINE_STAGE_ALL_COMMANDS_BIT & stage_mask) { |
| 68 | expanded = expanded & ~VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| 69 | for (const auto &all_commands : syncAllCommandStagesByQueueFlags) { |
| 70 | if (all_commands.first & queue_flags) { |
| 71 | expanded |= all_commands.second; |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | if (VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT & stage_mask) { |
| 76 | expanded = expanded & ~VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; |
| 77 | expanded |= syncAllCommandStagesByQueueFlags.at(VK_QUEUE_GRAPHICS_BIT) & ~VK_PIPELINE_STAGE_HOST_BIT; |
| 78 | } |
| 79 | return expanded; |
| 80 | } |
| 81 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 82 | static const ResourceAccessRange full_range(std::numeric_limits<VkDeviceSize>::min(), std::numeric_limits<VkDeviceSize>::max()); |
| 83 | static ResourceAccessRange MakeMemoryAccessRange(const BUFFER_STATE &buffer, VkDeviceSize offset, VkDeviceSize size) { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 84 | assert(!buffer.sparse); |
| 85 | const auto base = offset + buffer.binding.offset; |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 86 | return ResourceAccessRange(base, base + size); |
| 87 | } |
| 88 | |
| 89 | HazardResult DetectHazard(const ResourceAccessRangeMap &accesses, SyncStageAccessIndex current_usage, |
| 90 | const ResourceAccessRange &range) { |
| 91 | const auto from = accesses.lower_bound(range); |
| 92 | const auto to = accesses.upper_bound(range); |
| 93 | for (auto pos = from; pos != to; ++pos) { |
| 94 | const auto &access_state = pos->second; |
| 95 | HazardResult hazard = access_state.DetectHazard(current_usage); |
| 96 | if (hazard.hazard) return hazard; |
| 97 | } |
| 98 | return HazardResult(); |
| 99 | } |
| 100 | |
| 101 | HazardResult DetectHazard(const IMAGE_STATE &image, const ResourceAccessRangeMap &accesses, SyncStageAccessIndex current_usage, |
| 102 | const VkImageSubresourceLayers &subresource, const VkOffset3D &offset, const VkExtent3D &extent) { |
| 103 | // TODO: replace the encoder/generator with offset3D/extent3D aware versions |
| 104 | VkImageSubresourceRange subresource_range = {subresource.aspectMask, subresource.mipLevel, 1, subresource.baseArrayLayer, |
| 105 | subresource.layerCount}; |
| 106 | subresource_adapter::RangeGenerator range_gen(image.range_encoder, subresource_range); |
| 107 | for (; range_gen->non_empty(); ++range_gen) { |
| 108 | HazardResult hazard = DetectHazard(accesses, current_usage, *range_gen); |
| 109 | if (hazard.hazard) return hazard; |
| 110 | } |
| 111 | return HazardResult(); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 112 | } |
| 113 | |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 114 | HazardResult DetectBarrierHazard(const ResourceAccessRangeMap &accesses, SyncStageAccessIndex current_usage, |
| 115 | VkPipelineStageFlags src_stage_mask, SyncStageAccessFlags src_scope, |
| 116 | const ResourceAccessRange &range) { |
| 117 | const auto from = accesses.lower_bound(range); |
| 118 | const auto to = accesses.upper_bound(range); |
| 119 | for (auto pos = from; pos != to; ++pos) { |
| 120 | const auto &access_state = pos->second; |
| 121 | HazardResult hazard = access_state.DetectBarrierHazard(current_usage, src_stage_mask, src_scope); |
| 122 | if (hazard.hazard) return hazard; |
| 123 | } |
| 124 | return HazardResult(); |
| 125 | } |
| 126 | |
| 127 | HazardResult DetectImageBarrierHazard(const ResourceAccessTracker &tracker, const IMAGE_STATE &image, |
| 128 | VkPipelineStageFlags src_stage_mask, SyncStageAccessFlags src_stage_scope, |
| 129 | const VkImageMemoryBarrier &barrier) { |
| 130 | auto *accesses = tracker.GetImageAccesses(image.image); |
| 131 | if (!accesses) return HazardResult(); |
| 132 | |
| 133 | auto subresource_range = NormalizeSubresourceRange(image.createInfo, barrier.subresourceRange); |
| 134 | subresource_adapter::RangeGenerator range_gen(image.range_encoder, subresource_range); |
| 135 | const auto src_scope = SyncStageAccess::AccessScope(src_stage_scope, barrier.srcAccessMask); |
| 136 | for (; range_gen->non_empty(); ++range_gen) { |
| 137 | HazardResult hazard = DetectBarrierHazard(*accesses, SyncStageAccessIndex::SYNC_IMAGE_LAYOUT_TRANSITION, src_stage_mask, |
| 138 | src_scope, *range_gen); |
| 139 | if (hazard.hazard) return hazard; |
| 140 | } |
| 141 | return HazardResult(); |
| 142 | } |
| 143 | |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 144 | template <typename Flags, typename Map> |
| 145 | SyncStageAccessFlags AccessScopeImpl(Flags flag_mask, const Map &map) { |
| 146 | SyncStageAccessFlags scope = 0; |
| 147 | for (const auto &bit_scope : map) { |
| 148 | if (flag_mask < bit_scope.first) break; |
| 149 | |
| 150 | if (flag_mask & bit_scope.first) { |
| 151 | scope |= bit_scope.second; |
| 152 | } |
| 153 | } |
| 154 | return scope; |
| 155 | } |
| 156 | |
| 157 | SyncStageAccessFlags SyncStageAccess::AccessScopeByStage(VkPipelineStageFlags stages) { |
| 158 | return AccessScopeImpl(stages, syncStageAccessMaskByStageBit); |
| 159 | } |
| 160 | |
| 161 | SyncStageAccessFlags SyncStageAccess::AccessScopeByAccess(VkAccessFlags accesses) { |
| 162 | return AccessScopeImpl(accesses, syncStageAccessMaskByAccessBit); |
| 163 | } |
| 164 | |
| 165 | // Getting from stage mask and access mask to stage/acess masks is something we need to be good at... |
| 166 | SyncStageAccessFlags SyncStageAccess::AccessScope(VkPipelineStageFlags stages, VkAccessFlags accesses) { |
| 167 | // The access scope is the intersection of all stage/access types possible for the enabled stages and the enables accesses |
| 168 | // (after doing a couple factoring of common terms the union of stage/access intersections is the intersections of the |
| 169 | // union of all stage/access types for all the stages and the same unions for the access mask... |
| 170 | return AccessScopeByStage(stages) & AccessScopeByAccess(accesses); |
| 171 | } |
| 172 | |
| 173 | template <typename Action> |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 174 | void UpdateMemoryAccessState(ResourceAccessRangeMap *accesses, const ResourceAccessRange &range, const Action &action) { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 175 | // TODO -- region/mem-range accuracte update |
| 176 | auto pos = accesses->lower_bound(range); |
| 177 | if (pos == accesses->end() || !pos->first.intersects(range)) { |
| 178 | // The range is empty, fill it with a default value. |
| 179 | pos = action.Infill(accesses, pos, range); |
| 180 | } else if (range.begin < pos->first.begin) { |
| 181 | // Leading empty space, infill |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 182 | pos = action.Infill(accesses, pos, ResourceAccessRange(range.begin, pos->first.begin)); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 183 | } else if (pos->first.begin < range.begin) { |
| 184 | // Trim the beginning if needed |
| 185 | pos = accesses->split(pos, range.begin, sparse_container::split_op_keep_both()); |
| 186 | ++pos; |
| 187 | } |
| 188 | |
| 189 | const auto the_end = accesses->end(); |
| 190 | while ((pos != the_end) && pos->first.intersects(range)) { |
| 191 | if (pos->first.end > range.end) { |
| 192 | pos = accesses->split(pos, range.end, sparse_container::split_op_keep_both()); |
| 193 | } |
| 194 | |
| 195 | pos = action(accesses, pos); |
| 196 | if (pos == the_end) break; |
| 197 | |
| 198 | auto next = pos; |
| 199 | ++next; |
| 200 | if ((pos->first.end < range.end) && (next != the_end) && !next->first.is_subsequent_to(pos->first)) { |
| 201 | // Need to infill if next is disjoint |
| 202 | VkDeviceSize limit = (next == the_end) ? range.end : std::min(range.end, next->first.begin); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 203 | ResourceAccessRange new_range(pos->first.end, limit); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 204 | next = action.Infill(accesses, next, new_range); |
| 205 | } |
| 206 | pos = next; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | struct UpdateMemoryAccessStateFunctor { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 211 | using Iterator = ResourceAccessRangeMap::iterator; |
| 212 | Iterator Infill(ResourceAccessRangeMap *accesses, Iterator pos, ResourceAccessRange range) const { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 213 | return accesses->insert(pos, std::make_pair(range, ResourceAccessState())); |
| 214 | } |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 215 | Iterator operator()(ResourceAccessRangeMap *accesses, Iterator pos) const { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 216 | auto &access_state = pos->second; |
| 217 | access_state.Update(usage, tag); |
| 218 | return pos; |
| 219 | } |
| 220 | |
| 221 | UpdateMemoryAccessStateFunctor(SyncStageAccessIndex usage_, const ResourceUsageTag &tag_) : usage(usage_), tag(tag_) {} |
| 222 | SyncStageAccessIndex usage; |
| 223 | const ResourceUsageTag &tag; |
| 224 | }; |
| 225 | |
| 226 | struct ApplyMemoryAccessBarrierFunctor { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 227 | using Iterator = ResourceAccessRangeMap::iterator; |
| 228 | inline Iterator Infill(ResourceAccessRangeMap *accesses, Iterator pos, ResourceAccessRange range) const { return pos; } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 229 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 230 | Iterator operator()(ResourceAccessRangeMap *accesses, Iterator pos) const { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 231 | auto &access_state = pos->second; |
| 232 | access_state.ApplyMemoryAccessBarrier(src_stage_mask, src_scope, dst_stage_mask, dst_scope); |
| 233 | return pos; |
| 234 | } |
| 235 | |
| 236 | ApplyMemoryAccessBarrierFunctor(VkPipelineStageFlags src_stage_mask_, SyncStageAccessFlags src_scope_, |
| 237 | VkPipelineStageFlags dst_stage_mask_, SyncStageAccessFlags dst_scope_) |
| 238 | : src_stage_mask(src_stage_mask_), src_scope(src_scope_), dst_stage_mask(dst_stage_mask_), dst_scope(dst_scope_) {} |
| 239 | |
| 240 | VkPipelineStageFlags src_stage_mask; |
| 241 | SyncStageAccessFlags src_scope; |
| 242 | VkPipelineStageFlags dst_stage_mask; |
| 243 | SyncStageAccessFlags dst_scope; |
| 244 | }; |
| 245 | |
| 246 | struct ApplyGlobalBarrierFunctor { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 247 | using Iterator = ResourceAccessRangeMap::iterator; |
| 248 | inline Iterator Infill(ResourceAccessRangeMap *accesses, Iterator pos, ResourceAccessRange range) const { return pos; } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 249 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 250 | Iterator operator()(ResourceAccessRangeMap *accesses, Iterator pos) const { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 251 | auto &access_state = pos->second; |
| 252 | access_state.ApplyExecutionBarrier(src_stage_mask, dst_stage_mask); |
| 253 | |
| 254 | for (const auto &functor : barrier_functor) { |
| 255 | functor(accesses, pos); |
| 256 | } |
| 257 | return pos; |
| 258 | } |
| 259 | |
| 260 | ApplyGlobalBarrierFunctor(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, |
| 261 | SyncStageAccessFlags src_stage_scope, SyncStageAccessFlags dst_stage_scope, |
| 262 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers) |
| 263 | : src_stage_mask(srcStageMask), dst_stage_mask(dstStageMask) { |
| 264 | // Don't want to create this per tracked item, but don't want to loop through all tracked items per barrier... |
| 265 | barrier_functor.reserve(memoryBarrierCount); |
| 266 | for (uint32_t barrier_index = 0; barrier_index < memoryBarrierCount; barrier_index++) { |
| 267 | const auto &barrier = pMemoryBarriers[barrier_index]; |
| 268 | barrier_functor.emplace_back(srcStageMask, SyncStageAccess::AccessScope(src_stage_scope, barrier.srcAccessMask), |
| 269 | dstStageMask, SyncStageAccess::AccessScope(dst_stage_scope, barrier.dstAccessMask)); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | const VkPipelineStageFlags src_stage_mask; |
| 274 | const VkPipelineStageFlags dst_stage_mask; |
| 275 | std::vector<ApplyMemoryAccessBarrierFunctor> barrier_functor; |
| 276 | }; |
| 277 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 278 | void UpdateAccessState(ResourceAccessRangeMap *accesses, SyncStageAccessIndex current_usage, const ResourceAccessRange &range, |
| 279 | const ResourceUsageTag &tag) { |
| 280 | UpdateMemoryAccessStateFunctor action(current_usage, tag); |
| 281 | UpdateMemoryAccessState(accesses, range, action); |
| 282 | } |
| 283 | |
| 284 | void UpdateAccessState(const IMAGE_STATE &image, ResourceAccessRangeMap *accesses, SyncStageAccessIndex current_usage, |
| 285 | const VkImageSubresourceLayers &subresource, const VkOffset3D &offset, const VkExtent3D &extent, |
| 286 | const ResourceUsageTag &tag) { |
| 287 | // TODO: replace the encoder/generator with offset3D aware versions |
| 288 | VkImageSubresourceRange subresource_range = {subresource.aspectMask, subresource.mipLevel, 1, subresource.baseArrayLayer, |
| 289 | subresource.layerCount}; |
| 290 | subresource_adapter::RangeGenerator range_gen(image.range_encoder, subresource_range); |
| 291 | for (; range_gen->non_empty(); ++range_gen) { |
| 292 | UpdateAccessState(accesses, current_usage, *range_gen, tag); |
| 293 | } |
| 294 | } |
| 295 | |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 296 | HazardResult ResourceAccessState::DetectHazard(SyncStageAccessIndex usage_index) const { |
| 297 | HazardResult hazard; |
| 298 | auto usage = FlagBit(usage_index); |
| 299 | if (IsRead(usage)) { |
| 300 | if (IsWriteHazard(usage)) { |
| 301 | hazard.Set(READ_AFTER_WRITE, write_tag); |
| 302 | } |
| 303 | } else { |
| 304 | // Assume write |
| 305 | // TODO determine what to do with READ-WRITE usage states if any |
| 306 | // Write-After-Write check -- if we have a previous write to test against |
| 307 | if (last_write && IsWriteHazard(usage)) { |
| 308 | hazard.Set(WRITE_AFTER_WRITE, write_tag); |
| 309 | } else { |
| 310 | // Only look for casus belli for WAR |
| 311 | const auto usage_stage = PipelineStageBit(usage_index); |
| 312 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 313 | if (IsReadHazard(usage_stage, last_reads[read_index])) { |
| 314 | hazard.Set(WRITE_AFTER_READ, last_reads[read_index].tag); |
| 315 | break; |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | return hazard; |
| 321 | } |
| 322 | |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 323 | HazardResult ResourceAccessState::DetectBarrierHazard(SyncStageAccessIndex usage_index, VkPipelineStageFlags src_stage_mask, |
| 324 | SyncStageAccessFlags src_scope) const { |
| 325 | // Only supporting image layout transitions for now |
| 326 | assert(usage_index == SyncStageAccessIndex::SYNC_IMAGE_LAYOUT_TRANSITION); |
| 327 | HazardResult hazard; |
| 328 | if (last_write) { |
| 329 | // If the previous write is *not* in the 1st access scope |
| 330 | // *AND* the current barrier is not in the dependency chain |
| 331 | // *AND* the there is no prior memory barrier for the previous write in the dependency chain |
| 332 | // then the barrier access is unsafe (R/W after W) |
| 333 | if (((last_write & src_scope) == 0) && ((src_stage_mask & write_dependency_chain) == 0) && (write_barriers == 0)) { |
| 334 | // TODO: Do we need a difference hazard name for this? |
| 335 | hazard.Set(WRITE_AFTER_WRITE, write_tag); |
| 336 | } |
| 337 | } else { |
| 338 | // Look at the reads |
| 339 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 340 | if (IsReadHazard(src_stage_mask, last_reads[read_index])) { |
| 341 | hazard.Set(WRITE_AFTER_READ, last_reads[read_index].tag); |
| 342 | break; |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | return hazard; |
| 347 | } |
| 348 | |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 349 | void ResourceAccessState::Update(SyncStageAccessIndex usage_index, const ResourceUsageTag &tag) { |
| 350 | // Move this logic in the ResourceStateTracker as methods, thereof (or we'll repeat it for every flavor of resource... |
| 351 | const auto usage_bit = FlagBit(usage_index); |
| 352 | if (IsRead(usage_index)) { |
| 353 | // Mulitple outstanding reads may be of interest and do dependency chains independently |
| 354 | // However, for purposes of barrier tracking, only one read per pipeline stage matters |
| 355 | const auto usage_stage = PipelineStageBit(usage_index); |
| 356 | if (usage_stage & last_read_stages) { |
| 357 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 358 | ReadState &access = last_reads[read_index]; |
| 359 | if (access.stage == usage_stage) { |
| 360 | access.barriers = 0; |
| 361 | access.tag = tag; |
| 362 | break; |
| 363 | } |
| 364 | } |
| 365 | } else { |
| 366 | // We don't have this stage in the list yet... |
| 367 | assert(last_read_count < last_reads.size()); |
| 368 | ReadState &access = last_reads[last_read_count++]; |
| 369 | access.stage = usage_stage; |
| 370 | access.barriers = 0; |
| 371 | access.tag = tag; |
| 372 | last_read_stages |= usage_stage; |
| 373 | } |
| 374 | } else { |
| 375 | // Assume write |
| 376 | // TODO determine what to do with READ-WRITE operations if any |
| 377 | // Clobber last read and both sets of barriers... because all we have is DANGER, DANGER, WILL ROBINSON!!! |
| 378 | // if the last_reads/last_write were unsafe, we've reported them, |
| 379 | // in either case the prior access is irrelevant, we can overwrite them as *this* write is now after them |
| 380 | last_read_count = 0; |
| 381 | last_read_stages = 0; |
| 382 | |
| 383 | write_barriers = 0; |
| 384 | write_dependency_chain = 0; |
| 385 | write_tag = tag; |
| 386 | last_write = usage_bit; |
| 387 | } |
| 388 | } |
| 389 | void ResourceAccessState::ApplyExecutionBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) { |
| 390 | // Execution Barriers only protect read operations |
| 391 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 392 | ReadState &access = last_reads[read_index]; |
| 393 | // The | implements the "dependency chain" logic for this access, as the barriers field stores the second sync scope |
| 394 | if (srcStageMask & (access.stage | access.barriers)) { |
| 395 | access.barriers |= dstStageMask; |
| 396 | } |
| 397 | } |
| 398 | if (write_dependency_chain & srcStageMask) write_dependency_chain |= dstStageMask; |
| 399 | } |
| 400 | |
| 401 | void ResourceAccessState::ApplyMemoryAccessBarrier(VkPipelineStageFlags src_stage_mask, SyncStageAccessFlags src_scope, |
| 402 | VkPipelineStageFlags dst_stage_mask, SyncStageAccessFlags dst_scope) { |
| 403 | // Assuming we've applied the execution side of this barrier, we update just the write |
| 404 | // The || implements the "dependency chain" logic for this barrier |
| 405 | if ((src_scope & last_write) || (write_dependency_chain & src_stage_mask)) { |
| 406 | write_barriers |= dst_scope; |
| 407 | write_dependency_chain |= dst_stage_mask; |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | void SyncValidator::ResetCommandBuffer(VkCommandBuffer command_buffer) { |
| 412 | auto *tracker = GetAccessTrackerNoInsert(command_buffer); |
| 413 | if (tracker) { |
| 414 | tracker->Reset(); |
| 415 | } |
| 416 | } |
| 417 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 418 | void SyncValidator::ApplyGlobalBarriers(ResourceAccessTracker *tracker, VkPipelineStageFlags srcStageMask, |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 419 | VkPipelineStageFlags dstStageMask, SyncStageAccessFlags src_stage_scope, |
| 420 | SyncStageAccessFlags dst_stage_scope, uint32_t memoryBarrierCount, |
| 421 | const VkMemoryBarrier *pMemoryBarriers) { |
| 422 | // TODO: Implement this better (maybe some delayed/on-demand integration). |
| 423 | ApplyGlobalBarrierFunctor barriers_functor(srcStageMask, dstStageMask, src_stage_scope, dst_stage_scope, memoryBarrierCount, |
| 424 | pMemoryBarriers); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 425 | for (auto &mem_access_pair : tracker->GetMemoryAccessMap()) { |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 426 | UpdateMemoryAccessState(&mem_access_pair.second, full_range, barriers_functor); |
| 427 | } |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 428 | for (auto &image_access_pair : tracker->GetImageAccessMap()) { |
| 429 | UpdateMemoryAccessState(&image_access_pair.second, full_range, barriers_functor); |
| 430 | } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 431 | } |
| 432 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 433 | void SyncValidator::ApplyBufferBarriers(ResourceAccessTracker *tracker, VkPipelineStageFlags src_stage_mask, |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 434 | SyncStageAccessFlags src_stage_scope, VkPipelineStageFlags dst_stage_mask, |
| 435 | SyncStageAccessFlags dst_stage_scope, uint32_t barrier_count, |
| 436 | const VkBufferMemoryBarrier *barriers) { |
| 437 | // TODO Implement this at subresource/memory_range accuracy |
| 438 | for (uint32_t index = 0; index < barrier_count; index++) { |
| 439 | const auto &barrier = barriers[index]; |
| 440 | const auto *buffer = Get<BUFFER_STATE>(barrier.buffer); |
| 441 | if (!buffer) continue; |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 442 | auto *accesses = tracker->GetMemoryAccessesNoInsert(buffer->binding.mem_state->mem); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 443 | if (!accesses) continue; |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 444 | ResourceAccessRange range = MakeMemoryAccessRange(*buffer, barrier.offset, barrier.size); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 445 | UpdateMemoryAccessState( |
| 446 | accesses, range, |
| 447 | ApplyMemoryAccessBarrierFunctor(src_stage_mask, AccessScope(src_stage_scope, barrier.srcAccessMask), dst_stage_mask, |
| 448 | AccessScope(dst_stage_scope, barrier.dstAccessMask))); |
| 449 | } |
| 450 | } |
| 451 | |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 452 | void SyncValidator::ApplyImageBarriers(ResourceAccessTracker *tracker, VkPipelineStageFlags src_stage_mask, |
| 453 | SyncStageAccessFlags src_stage_scope, VkPipelineStageFlags dst_stage_mask, |
| 454 | SyncStageAccessFlags dst_stage_scope, uint32_t barrier_count, |
| 455 | const VkImageMemoryBarrier *barriers) { |
| 456 | for (uint32_t index = 0; index < barrier_count; index++) { |
| 457 | const auto &barrier = barriers[index]; |
| 458 | const auto *image = Get<IMAGE_STATE>(barrier.image); |
| 459 | if (!image) continue; |
| 460 | auto *accesses = tracker->GetImageAccessesNoInsert(image->image); |
| 461 | if (!accesses) continue; |
| 462 | auto subresource_range = NormalizeSubresourceRange(image->createInfo, barrier.subresourceRange); |
| 463 | subresource_adapter::RangeGenerator range_gen(image->range_encoder, subresource_range); |
| 464 | const ApplyMemoryAccessBarrierFunctor barrier_action(src_stage_mask, AccessScope(src_stage_scope, barrier.srcAccessMask), |
| 465 | dst_stage_mask, AccessScope(dst_stage_scope, barrier.dstAccessMask)); |
| 466 | for (; range_gen->non_empty(); ++range_gen) { |
| 467 | UpdateMemoryAccessState(accesses, *range_gen, barrier_action); |
| 468 | } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 469 | } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | bool SyncValidator::PreCallValidateCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, |
| 473 | uint32_t regionCount, const VkBufferCopy *pRegions) const { |
| 474 | bool skip = false; |
| 475 | const auto *const const_this = this; |
| 476 | const auto *tracker = const_this->GetAccessTracker(commandBuffer); |
| 477 | if (tracker) { |
| 478 | // If we have no previous accesses, we have no hazards |
| 479 | // TODO: make this sub-resource capable |
| 480 | // TODO: make this general, and stuff it into templates/utility functions |
| 481 | const auto *src_buffer = Get<BUFFER_STATE>(srcBuffer); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 482 | const auto src_access = |
| 483 | (src_buffer && !src_buffer->sparse) ? tracker->GetMemoryAccesses(src_buffer->binding.mem_state->mem) : nullptr; |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 484 | const auto *dst_buffer = Get<BUFFER_STATE>(dstBuffer); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 485 | const auto dst_access = |
| 486 | (dst_buffer && !dst_buffer->sparse) ? tracker->GetMemoryAccesses(dst_buffer->binding.mem_state->mem) : nullptr; |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 487 | |
| 488 | for (uint32_t region = 0; region < regionCount; region++) { |
| 489 | const auto ©_region = pRegions[region]; |
| 490 | if (src_access) { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 491 | ResourceAccessRange src_range = MakeMemoryAccessRange(*src_buffer, copy_region.srcOffset, copy_region.size); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 492 | auto hazard = DetectHazard(*src_access, SYNC_TRANSFER_TRANSFER_READ, src_range); |
| 493 | if (hazard.hazard) { |
| 494 | // TODO -- add tag information to log msg when useful. |
| 495 | skip |= LogError(srcBuffer, string_SyncHazardVUID(hazard.hazard), "Hazard %s for srcBuffer %s, region %" PRIu32, |
| 496 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(srcBuffer).c_str(), region); |
| 497 | } |
| 498 | } |
| 499 | if (dst_access && !skip) { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 500 | ResourceAccessRange dst_range = MakeMemoryAccessRange(*dst_buffer, copy_region.dstOffset, copy_region.size); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 501 | auto hazard = DetectHazard(*dst_access, SYNC_TRANSFER_TRANSFER_WRITE, dst_range); |
| 502 | if (hazard.hazard) { |
| 503 | skip |= LogError(dstBuffer, string_SyncHazardVUID(hazard.hazard), "Hazard %s for dstBuffer %s, region %" PRIu32, |
| 504 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(dstBuffer).c_str(), region); |
| 505 | } |
| 506 | } |
| 507 | if (skip) break; |
| 508 | } |
| 509 | } |
| 510 | return skip; |
| 511 | } |
| 512 | |
| 513 | void SyncValidator::PreCallRecordCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, |
| 514 | uint32_t regionCount, const VkBufferCopy *pRegions) { |
| 515 | auto *tracker = GetAccessTracker(commandBuffer); |
| 516 | assert(tracker); |
| 517 | const auto *src_buffer = Get<BUFFER_STATE>(srcBuffer); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 518 | const auto src_access = |
| 519 | (src_buffer && !src_buffer->sparse) ? tracker->GetMemoryAccesses(src_buffer->binding.mem_state->mem) : nullptr; |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 520 | const auto *dst_buffer = Get<BUFFER_STATE>(dstBuffer); |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 521 | const auto dst_access = |
| 522 | (dst_buffer && !dst_buffer->sparse) ? tracker->GetMemoryAccesses(dst_buffer->binding.mem_state->mem) : nullptr; |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 523 | |
| 524 | for (uint32_t region = 0; region < regionCount; region++) { |
| 525 | const auto ©_region = pRegions[region]; |
| 526 | if (src_access) { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 527 | ResourceAccessRange src_range = MakeMemoryAccessRange(*src_buffer, copy_region.srcOffset, copy_region.size); |
| 528 | UpdateAccessState(src_access, SYNC_TRANSFER_TRANSFER_READ, src_range, tag); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 529 | } |
| 530 | if (dst_access) { |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 531 | ResourceAccessRange dst_range = MakeMemoryAccessRange(*dst_buffer, copy_region.dstOffset, copy_region.size); |
| 532 | UpdateAccessState(dst_access, SYNC_TRANSFER_TRANSFER_WRITE, dst_range, tag); |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | bool SyncValidator::PreCallValidateCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, |
| 538 | VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, |
| 539 | const VkImageCopy *pRegions) const { |
| 540 | bool skip = false; |
| 541 | auto *tracker = GetAccessTracker(commandBuffer); |
| 542 | if (tracker) { |
| 543 | const auto *src_image = Get<IMAGE_STATE>(srcImage); |
| 544 | const auto src_access = tracker->GetImageAccesses(srcImage); |
| 545 | const auto *dst_image = Get<IMAGE_STATE>(dstImage); |
| 546 | const auto dst_access = tracker->GetImageAccesses(dstImage); |
| 547 | |
| 548 | for (uint32_t region = 0; region < regionCount; region++) { |
| 549 | const auto ©_region = pRegions[region]; |
| 550 | if (src_access) { |
| 551 | auto hazard = DetectHazard(*src_image, *src_access, SYNC_TRANSFER_TRANSFER_READ, copy_region.srcSubresource, |
| 552 | copy_region.srcOffset, copy_region.extent); |
| 553 | if (hazard.hazard) { |
| 554 | skip |= LogError(srcImage, string_SyncHazardVUID(hazard.hazard), "Hazard %s for srcImage %s, region %" PRIu32, |
| 555 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(srcImage).c_str(), region); |
| 556 | } |
| 557 | } |
| 558 | if (dst_access) { |
| 559 | auto hazard = DetectHazard(*dst_image, *dst_access, SYNC_TRANSFER_TRANSFER_WRITE, copy_region.dstSubresource, |
| 560 | copy_region.dstOffset, copy_region.extent); |
| 561 | if (hazard.hazard) { |
| 562 | skip |= LogError(dstImage, string_SyncHazardVUID(hazard.hazard), "Hazard %s for dstImage %s, region %" PRIu32, |
| 563 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(dstImage).c_str(), region); |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | } |
| 568 | return skip; |
| 569 | } |
| 570 | |
| 571 | void SyncValidator::PreCallRecordCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, |
| 572 | VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, |
| 573 | const VkImageCopy *pRegions) { |
| 574 | auto *tracker = GetAccessTracker(commandBuffer); |
| 575 | assert(tracker); |
| 576 | auto *src_image = Get<IMAGE_STATE>(srcImage); |
| 577 | auto src_access = tracker->GetImageAccesses(srcImage); |
| 578 | auto *dst_image = Get<IMAGE_STATE>(dstImage); |
| 579 | auto dst_access = tracker->GetImageAccesses(dstImage); |
| 580 | |
| 581 | for (uint32_t region = 0; region < regionCount; region++) { |
| 582 | const auto ©_region = pRegions[region]; |
| 583 | if (src_access) { |
| 584 | UpdateAccessState(*src_image, src_access, SYNC_TRANSFER_TRANSFER_READ, copy_region.srcSubresource, |
| 585 | copy_region.srcOffset, copy_region.extent, tag); |
| 586 | } |
| 587 | if (dst_access) { |
| 588 | UpdateAccessState(*dst_image, dst_access, SYNC_TRANSFER_TRANSFER_WRITE, copy_region.dstSubresource, |
| 589 | copy_region.dstOffset, copy_region.extent, tag); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 590 | } |
| 591 | } |
| 592 | } |
| 593 | |
| 594 | bool SyncValidator::PreCallValidateCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, |
| 595 | VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, |
| 596 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, |
| 597 | uint32_t bufferMemoryBarrierCount, |
| 598 | const VkBufferMemoryBarrier *pBufferMemoryBarriers, |
| 599 | uint32_t imageMemoryBarrierCount, |
| 600 | const VkImageMemoryBarrier *pImageMemoryBarriers) const { |
| 601 | bool skip = false; |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 602 | const auto *tracker = GetAccessTracker(commandBuffer); |
| 603 | if (!tracker) return skip; |
| 604 | |
| 605 | const auto cb_state = Get<CMD_BUFFER_STATE>(commandBuffer); |
| 606 | assert(cb_state); |
| 607 | if (!cb_state) return skip; |
| 608 | |
| 609 | const auto src_stage_mask = ExpandPipelineStages(GetQueueFlags(*cb_state), srcStageMask); |
| 610 | auto src_stage_scope = AccessScopeByStage(src_stage_mask); |
| 611 | // Validate Image Layout transitions |
| 612 | for (uint32_t index = 0; index < imageMemoryBarrierCount; index++) { |
| 613 | const auto &barrier = pImageMemoryBarriers[index]; |
| 614 | if (barrier.newLayout == barrier.oldLayout) continue; // Only interested in layout transitions at this point. |
| 615 | const auto *image_state = Get<IMAGE_STATE>(barrier.image); |
| 616 | if (!image_state) continue; |
| 617 | const auto hazard = DetectImageBarrierHazard(*tracker, *image_state, src_stage_mask, src_stage_scope, barrier); |
| 618 | if (hazard.hazard) { |
| 619 | // TODO -- add tag information to log msg when useful. |
| 620 | skip |= LogError(barrier.image, string_SyncHazardVUID(hazard.hazard), "Hazard %s for image barrier %" PRIu32 " %s", |
| 621 | string_SyncHazard(hazard.hazard), index, report_data->FormatHandle(barrier.image).c_str()); |
| 622 | } |
| 623 | } |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 624 | |
| 625 | return skip; |
| 626 | } |
| 627 | |
| 628 | void SyncValidator::PreCallRecordCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, |
| 629 | VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, |
| 630 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, |
| 631 | uint32_t bufferMemoryBarrierCount, |
| 632 | const VkBufferMemoryBarrier *pBufferMemoryBarriers, |
| 633 | uint32_t imageMemoryBarrierCount, |
| 634 | const VkImageMemoryBarrier *pImageMemoryBarriers) { |
| 635 | // Just implement the buffer barrier for now |
| 636 | auto *tracker = GetAccessTracker(commandBuffer); |
| 637 | assert(tracker); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 638 | |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 639 | const auto cb_state = Get<CMD_BUFFER_STATE>(commandBuffer); |
| 640 | assert(cb_state); |
| 641 | if (!cb_state) return; |
| 642 | |
| 643 | const auto src_stage_mask = ExpandPipelineStages(GetQueueFlags(*cb_state), srcStageMask); |
| 644 | auto src_stage_scope = AccessScopeByStage(src_stage_mask); |
| 645 | const auto dst_stage_mask = ExpandPipelineStages(GetQueueFlags(*cb_state), dstStageMask); |
| 646 | auto dst_stage_scope = AccessScopeByStage(dst_stage_mask); |
| 647 | |
| 648 | ApplyBufferBarriers(tracker, src_stage_mask, src_stage_scope, dst_stage_mask, dst_stage_scope, bufferMemoryBarrierCount, |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 649 | pBufferMemoryBarriers); |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 650 | ApplyImageBarriers(tracker, src_stage_mask, src_stage_scope, dst_stage_mask, dst_stage_scope, imageMemoryBarrierCount, |
John Zulauf | 5c5e88d | 2019-12-26 11:22:02 -0700 | [diff] [blame] | 651 | pImageMemoryBarriers); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 652 | |
| 653 | // Apply these last in-case there operation is a superset of the other two and would clean them up... |
John Zulauf | 0cb5be2 | 2020-01-23 12:18:22 -0700 | [diff] [blame^] | 654 | ApplyGlobalBarriers(tracker, src_stage_mask, dst_stage_mask, src_stage_scope, dst_stage_scope, memoryBarrierCount, |
| 655 | pMemoryBarriers); |
John Zulauf | 9cb530d | 2019-09-30 14:14:10 -0600 | [diff] [blame] | 656 | } |
| 657 | |
| 658 | void SyncValidator::PostCallRecordCreateDevice(VkPhysicalDevice gpu, const VkDeviceCreateInfo *pCreateInfo, |
| 659 | const VkAllocationCallbacks *pAllocator, VkDevice *pDevice, VkResult result) { |
| 660 | // The state tracker sets up the device state |
| 661 | StateTracker::PostCallRecordCreateDevice(gpu, pCreateInfo, pAllocator, pDevice, result); |
| 662 | |
| 663 | // Add the callback hooks for the functions that are either broadly or deeply used and that the ValidationStateTracker refactor |
| 664 | // would be messier without. |
| 665 | // TODO: Find a good way to do this hooklessly. |
| 666 | ValidationObject *device_object = GetLayerDataPtr(get_dispatch_key(*pDevice), layer_data_map); |
| 667 | ValidationObject *validation_data = GetValidationObject(device_object->object_dispatch, LayerObjectTypeSyncValidation); |
| 668 | SyncValidator *sync_device_state = static_cast<SyncValidator *>(validation_data); |
| 669 | |
| 670 | sync_device_state->SetCommandBufferResetCallback( |
| 671 | [sync_device_state](VkCommandBuffer command_buffer) -> void { sync_device_state->ResetCommandBuffer(command_buffer); }); |
| 672 | } |