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 | |
| 64 | static const MemoryAccessRange full_range(std::numeric_limits<VkDeviceSize>::min(), std::numeric_limits<VkDeviceSize>::max()); |
| 65 | static MemoryAccessRange MakeMemoryAccessRange(const BUFFER_STATE &buffer, VkDeviceSize offset, VkDeviceSize size) { |
| 66 | assert(!buffer.sparse); |
| 67 | const auto base = offset + buffer.binding.offset; |
| 68 | return MemoryAccessRange(base, base + size); |
| 69 | } |
| 70 | |
| 71 | template <typename Flags, typename Map> |
| 72 | SyncStageAccessFlags AccessScopeImpl(Flags flag_mask, const Map &map) { |
| 73 | SyncStageAccessFlags scope = 0; |
| 74 | for (const auto &bit_scope : map) { |
| 75 | if (flag_mask < bit_scope.first) break; |
| 76 | |
| 77 | if (flag_mask & bit_scope.first) { |
| 78 | scope |= bit_scope.second; |
| 79 | } |
| 80 | } |
| 81 | return scope; |
| 82 | } |
| 83 | |
| 84 | SyncStageAccessFlags SyncStageAccess::AccessScopeByStage(VkPipelineStageFlags stages) { |
| 85 | return AccessScopeImpl(stages, syncStageAccessMaskByStageBit); |
| 86 | } |
| 87 | |
| 88 | SyncStageAccessFlags SyncStageAccess::AccessScopeByAccess(VkAccessFlags accesses) { |
| 89 | return AccessScopeImpl(accesses, syncStageAccessMaskByAccessBit); |
| 90 | } |
| 91 | |
| 92 | // Getting from stage mask and access mask to stage/acess masks is something we need to be good at... |
| 93 | SyncStageAccessFlags SyncStageAccess::AccessScope(VkPipelineStageFlags stages, VkAccessFlags accesses) { |
| 94 | // The access scope is the intersection of all stage/access types possible for the enabled stages and the enables accesses |
| 95 | // (after doing a couple factoring of common terms the union of stage/access intersections is the intersections of the |
| 96 | // union of all stage/access types for all the stages and the same unions for the access mask... |
| 97 | return AccessScopeByStage(stages) & AccessScopeByAccess(accesses); |
| 98 | } |
| 99 | |
| 100 | template <typename Action> |
| 101 | void UpdateMemoryAccessState(MemoryAccessRangeMap *accesses, const MemoryAccessRange &range, const Action &action) { |
| 102 | // TODO -- region/mem-range accuracte update |
| 103 | auto pos = accesses->lower_bound(range); |
| 104 | if (pos == accesses->end() || !pos->first.intersects(range)) { |
| 105 | // The range is empty, fill it with a default value. |
| 106 | pos = action.Infill(accesses, pos, range); |
| 107 | } else if (range.begin < pos->first.begin) { |
| 108 | // Leading empty space, infill |
| 109 | pos = action.Infill(accesses, pos, MemoryAccessRange(range.begin, pos->first.begin)); |
| 110 | } else if (pos->first.begin < range.begin) { |
| 111 | // Trim the beginning if needed |
| 112 | pos = accesses->split(pos, range.begin, sparse_container::split_op_keep_both()); |
| 113 | ++pos; |
| 114 | } |
| 115 | |
| 116 | const auto the_end = accesses->end(); |
| 117 | while ((pos != the_end) && pos->first.intersects(range)) { |
| 118 | if (pos->first.end > range.end) { |
| 119 | pos = accesses->split(pos, range.end, sparse_container::split_op_keep_both()); |
| 120 | } |
| 121 | |
| 122 | pos = action(accesses, pos); |
| 123 | if (pos == the_end) break; |
| 124 | |
| 125 | auto next = pos; |
| 126 | ++next; |
| 127 | if ((pos->first.end < range.end) && (next != the_end) && !next->first.is_subsequent_to(pos->first)) { |
| 128 | // Need to infill if next is disjoint |
| 129 | VkDeviceSize limit = (next == the_end) ? range.end : std::min(range.end, next->first.begin); |
| 130 | MemoryAccessRange new_range(pos->first.end, limit); |
| 131 | next = action.Infill(accesses, next, new_range); |
| 132 | } |
| 133 | pos = next; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | struct UpdateMemoryAccessStateFunctor { |
| 138 | using Iterator = MemoryAccessRangeMap::iterator; |
| 139 | Iterator Infill(MemoryAccessRangeMap *accesses, Iterator pos, MemoryAccessRange range) const { |
| 140 | return accesses->insert(pos, std::make_pair(range, ResourceAccessState())); |
| 141 | } |
| 142 | Iterator operator()(MemoryAccessRangeMap *accesses, Iterator pos) const { |
| 143 | auto &access_state = pos->second; |
| 144 | access_state.Update(usage, tag); |
| 145 | return pos; |
| 146 | } |
| 147 | |
| 148 | UpdateMemoryAccessStateFunctor(SyncStageAccessIndex usage_, const ResourceUsageTag &tag_) : usage(usage_), tag(tag_) {} |
| 149 | SyncStageAccessIndex usage; |
| 150 | const ResourceUsageTag &tag; |
| 151 | }; |
| 152 | |
| 153 | struct ApplyMemoryAccessBarrierFunctor { |
| 154 | using Iterator = MemoryAccessRangeMap::iterator; |
| 155 | inline Iterator Infill(MemoryAccessRangeMap *accesses, Iterator pos, MemoryAccessRange range) const { return pos; } |
| 156 | |
| 157 | Iterator operator()(MemoryAccessRangeMap *accesses, Iterator pos) const { |
| 158 | auto &access_state = pos->second; |
| 159 | access_state.ApplyMemoryAccessBarrier(src_stage_mask, src_scope, dst_stage_mask, dst_scope); |
| 160 | return pos; |
| 161 | } |
| 162 | |
| 163 | ApplyMemoryAccessBarrierFunctor(VkPipelineStageFlags src_stage_mask_, SyncStageAccessFlags src_scope_, |
| 164 | VkPipelineStageFlags dst_stage_mask_, SyncStageAccessFlags dst_scope_) |
| 165 | : src_stage_mask(src_stage_mask_), src_scope(src_scope_), dst_stage_mask(dst_stage_mask_), dst_scope(dst_scope_) {} |
| 166 | |
| 167 | VkPipelineStageFlags src_stage_mask; |
| 168 | SyncStageAccessFlags src_scope; |
| 169 | VkPipelineStageFlags dst_stage_mask; |
| 170 | SyncStageAccessFlags dst_scope; |
| 171 | }; |
| 172 | |
| 173 | struct ApplyGlobalBarrierFunctor { |
| 174 | using Iterator = MemoryAccessRangeMap::iterator; |
| 175 | inline Iterator Infill(MemoryAccessRangeMap *accesses, Iterator pos, MemoryAccessRange range) const { return pos; } |
| 176 | |
| 177 | Iterator operator()(MemoryAccessRangeMap *accesses, Iterator pos) const { |
| 178 | auto &access_state = pos->second; |
| 179 | access_state.ApplyExecutionBarrier(src_stage_mask, dst_stage_mask); |
| 180 | |
| 181 | for (const auto &functor : barrier_functor) { |
| 182 | functor(accesses, pos); |
| 183 | } |
| 184 | return pos; |
| 185 | } |
| 186 | |
| 187 | ApplyGlobalBarrierFunctor(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, |
| 188 | SyncStageAccessFlags src_stage_scope, SyncStageAccessFlags dst_stage_scope, |
| 189 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers) |
| 190 | : src_stage_mask(srcStageMask), dst_stage_mask(dstStageMask) { |
| 191 | // Don't want to create this per tracked item, but don't want to loop through all tracked items per barrier... |
| 192 | barrier_functor.reserve(memoryBarrierCount); |
| 193 | for (uint32_t barrier_index = 0; barrier_index < memoryBarrierCount; barrier_index++) { |
| 194 | const auto &barrier = pMemoryBarriers[barrier_index]; |
| 195 | barrier_functor.emplace_back(srcStageMask, SyncStageAccess::AccessScope(src_stage_scope, barrier.srcAccessMask), |
| 196 | dstStageMask, SyncStageAccess::AccessScope(dst_stage_scope, barrier.dstAccessMask)); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | const VkPipelineStageFlags src_stage_mask; |
| 201 | const VkPipelineStageFlags dst_stage_mask; |
| 202 | std::vector<ApplyMemoryAccessBarrierFunctor> barrier_functor; |
| 203 | }; |
| 204 | |
| 205 | HazardResult ResourceAccessState::DetectHazard(SyncStageAccessIndex usage_index) const { |
| 206 | HazardResult hazard; |
| 207 | auto usage = FlagBit(usage_index); |
| 208 | if (IsRead(usage)) { |
| 209 | if (IsWriteHazard(usage)) { |
| 210 | hazard.Set(READ_AFTER_WRITE, write_tag); |
| 211 | } |
| 212 | } else { |
| 213 | // Assume write |
| 214 | // TODO determine what to do with READ-WRITE usage states if any |
| 215 | // Write-After-Write check -- if we have a previous write to test against |
| 216 | if (last_write && IsWriteHazard(usage)) { |
| 217 | hazard.Set(WRITE_AFTER_WRITE, write_tag); |
| 218 | } else { |
| 219 | // Only look for casus belli for WAR |
| 220 | const auto usage_stage = PipelineStageBit(usage_index); |
| 221 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 222 | if (IsReadHazard(usage_stage, last_reads[read_index])) { |
| 223 | hazard.Set(WRITE_AFTER_READ, last_reads[read_index].tag); |
| 224 | break; |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | return hazard; |
| 230 | } |
| 231 | |
| 232 | void ResourceAccessState::Update(SyncStageAccessIndex usage_index, const ResourceUsageTag &tag) { |
| 233 | // Move this logic in the ResourceStateTracker as methods, thereof (or we'll repeat it for every flavor of resource... |
| 234 | const auto usage_bit = FlagBit(usage_index); |
| 235 | if (IsRead(usage_index)) { |
| 236 | // Mulitple outstanding reads may be of interest and do dependency chains independently |
| 237 | // However, for purposes of barrier tracking, only one read per pipeline stage matters |
| 238 | const auto usage_stage = PipelineStageBit(usage_index); |
| 239 | if (usage_stage & last_read_stages) { |
| 240 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 241 | ReadState &access = last_reads[read_index]; |
| 242 | if (access.stage == usage_stage) { |
| 243 | access.barriers = 0; |
| 244 | access.tag = tag; |
| 245 | break; |
| 246 | } |
| 247 | } |
| 248 | } else { |
| 249 | // We don't have this stage in the list yet... |
| 250 | assert(last_read_count < last_reads.size()); |
| 251 | ReadState &access = last_reads[last_read_count++]; |
| 252 | access.stage = usage_stage; |
| 253 | access.barriers = 0; |
| 254 | access.tag = tag; |
| 255 | last_read_stages |= usage_stage; |
| 256 | } |
| 257 | } else { |
| 258 | // Assume write |
| 259 | // TODO determine what to do with READ-WRITE operations if any |
| 260 | // Clobber last read and both sets of barriers... because all we have is DANGER, DANGER, WILL ROBINSON!!! |
| 261 | // if the last_reads/last_write were unsafe, we've reported them, |
| 262 | // in either case the prior access is irrelevant, we can overwrite them as *this* write is now after them |
| 263 | last_read_count = 0; |
| 264 | last_read_stages = 0; |
| 265 | |
| 266 | write_barriers = 0; |
| 267 | write_dependency_chain = 0; |
| 268 | write_tag = tag; |
| 269 | last_write = usage_bit; |
| 270 | } |
| 271 | } |
| 272 | void ResourceAccessState::ApplyExecutionBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) { |
| 273 | // Execution Barriers only protect read operations |
| 274 | for (uint32_t read_index = 0; read_index < last_read_count; read_index++) { |
| 275 | ReadState &access = last_reads[read_index]; |
| 276 | // The | implements the "dependency chain" logic for this access, as the barriers field stores the second sync scope |
| 277 | if (srcStageMask & (access.stage | access.barriers)) { |
| 278 | access.barriers |= dstStageMask; |
| 279 | } |
| 280 | } |
| 281 | if (write_dependency_chain & srcStageMask) write_dependency_chain |= dstStageMask; |
| 282 | } |
| 283 | |
| 284 | void ResourceAccessState::ApplyMemoryAccessBarrier(VkPipelineStageFlags src_stage_mask, SyncStageAccessFlags src_scope, |
| 285 | VkPipelineStageFlags dst_stage_mask, SyncStageAccessFlags dst_scope) { |
| 286 | // Assuming we've applied the execution side of this barrier, we update just the write |
| 287 | // The || implements the "dependency chain" logic for this barrier |
| 288 | if ((src_scope & last_write) || (write_dependency_chain & src_stage_mask)) { |
| 289 | write_barriers |= dst_scope; |
| 290 | write_dependency_chain |= dst_stage_mask; |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | void SyncValidator::ResetCommandBuffer(VkCommandBuffer command_buffer) { |
| 295 | auto *tracker = GetAccessTrackerNoInsert(command_buffer); |
| 296 | if (tracker) { |
| 297 | tracker->Reset(); |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | void SyncValidator::ApplyGlobalBarriers(MemoryAccessTracker *tracker, VkPipelineStageFlags srcStageMask, |
| 302 | VkPipelineStageFlags dstStageMask, SyncStageAccessFlags src_stage_scope, |
| 303 | SyncStageAccessFlags dst_stage_scope, uint32_t memoryBarrierCount, |
| 304 | const VkMemoryBarrier *pMemoryBarriers) { |
| 305 | // TODO: Implement this better (maybe some delayed/on-demand integration). |
| 306 | ApplyGlobalBarrierFunctor barriers_functor(srcStageMask, dstStageMask, src_stage_scope, dst_stage_scope, memoryBarrierCount, |
| 307 | pMemoryBarriers); |
| 308 | for (auto &mem_access_pair : tracker->map) { // TODO hide the tracker details |
| 309 | UpdateMemoryAccessState(&mem_access_pair.second, full_range, barriers_functor); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | void SyncValidator::ApplyBufferBarriers(MemoryAccessTracker *tracker, VkPipelineStageFlags src_stage_mask, |
| 314 | SyncStageAccessFlags src_stage_scope, VkPipelineStageFlags dst_stage_mask, |
| 315 | SyncStageAccessFlags dst_stage_scope, uint32_t barrier_count, |
| 316 | const VkBufferMemoryBarrier *barriers) { |
| 317 | // TODO Implement this at subresource/memory_range accuracy |
| 318 | for (uint32_t index = 0; index < barrier_count; index++) { |
| 319 | const auto &barrier = barriers[index]; |
| 320 | const auto *buffer = Get<BUFFER_STATE>(barrier.buffer); |
| 321 | if (!buffer) continue; |
| 322 | auto *accesses = tracker->GetNoInsert(buffer->binding.mem_state->mem); |
| 323 | if (!accesses) continue; |
| 324 | MemoryAccessRange range = MakeMemoryAccessRange(*buffer, barrier.offset, barrier.size); |
| 325 | UpdateMemoryAccessState( |
| 326 | accesses, range, |
| 327 | ApplyMemoryAccessBarrierFunctor(src_stage_mask, AccessScope(src_stage_scope, barrier.srcAccessMask), dst_stage_mask, |
| 328 | AccessScope(dst_stage_scope, barrier.dstAccessMask))); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | void SyncValidator::ApplyImageBarriers(MemoryAccessTracker *tracker, SyncStageAccessFlags src_stage_scope, |
| 333 | SyncStageAccessFlags dst_stage_scope, uint32_t imageMemoryBarrierCount, |
| 334 | const VkImageMemoryBarrier *pImageMemoryBarriers) { |
| 335 | // TODO: Implement this. First pass a sub-resource (not-memory) accuracy |
| 336 | } |
| 337 | |
| 338 | HazardResult SyncValidator::DetectHazard(const MemoryAccessRangeMap &accesses, SyncStageAccessIndex current_usage, |
| 339 | const MemoryAccessRange &range) const { |
| 340 | const auto from = accesses.lower_bound(range); |
| 341 | const auto to = accesses.upper_bound(range); |
| 342 | for (auto pos = from; pos != to; ++pos) { |
| 343 | const auto &access_state = pos->second; |
| 344 | HazardResult hazard = access_state.DetectHazard(current_usage); |
| 345 | if (hazard.hazard) return hazard; |
| 346 | } |
| 347 | return HazardResult(); |
| 348 | } |
| 349 | |
| 350 | void SyncValidator::UpdateAccessState(MemoryAccessRangeMap *accesses, SyncStageAccessIndex current_usage, |
| 351 | const MemoryAccessRange &range) { |
| 352 | UpdateMemoryAccessStateFunctor action(current_usage, tag); |
| 353 | UpdateMemoryAccessState(accesses, range, action); |
| 354 | } |
| 355 | |
| 356 | bool SyncValidator::PreCallValidateCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, |
| 357 | uint32_t regionCount, const VkBufferCopy *pRegions) const { |
| 358 | bool skip = false; |
| 359 | const auto *const const_this = this; |
| 360 | const auto *tracker = const_this->GetAccessTracker(commandBuffer); |
| 361 | if (tracker) { |
| 362 | // If we have no previous accesses, we have no hazards |
| 363 | // TODO: make this sub-resource capable |
| 364 | // TODO: make this general, and stuff it into templates/utility functions |
| 365 | const auto *src_buffer = Get<BUFFER_STATE>(srcBuffer); |
| 366 | const auto src_access = (src_buffer && !src_buffer->sparse) ? tracker->Get(src_buffer->binding.mem_state->mem) : nullptr; |
| 367 | const auto *dst_buffer = Get<BUFFER_STATE>(dstBuffer); |
| 368 | const auto dst_access = (dst_buffer && !dst_buffer->sparse) ? tracker->Get(dst_buffer->binding.mem_state->mem) : nullptr; |
| 369 | |
| 370 | for (uint32_t region = 0; region < regionCount; region++) { |
| 371 | const auto ©_region = pRegions[region]; |
| 372 | if (src_access) { |
| 373 | MemoryAccessRange src_range = MakeMemoryAccessRange(*src_buffer, copy_region.srcOffset, copy_region.size); |
| 374 | auto hazard = DetectHazard(*src_access, SYNC_TRANSFER_TRANSFER_READ, src_range); |
| 375 | if (hazard.hazard) { |
| 376 | // TODO -- add tag information to log msg when useful. |
| 377 | skip |= LogError(srcBuffer, string_SyncHazardVUID(hazard.hazard), "Hazard %s for srcBuffer %s, region %" PRIu32, |
| 378 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(srcBuffer).c_str(), region); |
| 379 | } |
| 380 | } |
| 381 | if (dst_access && !skip) { |
| 382 | MemoryAccessRange dst_range = MakeMemoryAccessRange(*dst_buffer, copy_region.dstOffset, copy_region.size); |
| 383 | auto hazard = DetectHazard(*dst_access, SYNC_TRANSFER_TRANSFER_WRITE, dst_range); |
| 384 | if (hazard.hazard) { |
| 385 | skip |= LogError(dstBuffer, string_SyncHazardVUID(hazard.hazard), "Hazard %s for dstBuffer %s, region %" PRIu32, |
| 386 | string_SyncHazard(hazard.hazard), report_data->FormatHandle(dstBuffer).c_str(), region); |
| 387 | } |
| 388 | } |
| 389 | if (skip) break; |
| 390 | } |
| 391 | } |
| 392 | return skip; |
| 393 | } |
| 394 | |
| 395 | void SyncValidator::PreCallRecordCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, |
| 396 | uint32_t regionCount, const VkBufferCopy *pRegions) { |
| 397 | auto *tracker = GetAccessTracker(commandBuffer); |
| 398 | assert(tracker); |
| 399 | const auto *src_buffer = Get<BUFFER_STATE>(srcBuffer); |
| 400 | const auto src_access = (src_buffer && !src_buffer->sparse) ? tracker->Get(src_buffer->binding.mem_state->mem) : nullptr; |
| 401 | const auto *dst_buffer = Get<BUFFER_STATE>(dstBuffer); |
| 402 | const auto dst_access = (dst_buffer && !dst_buffer->sparse) ? tracker->Get(dst_buffer->binding.mem_state->mem) : nullptr; |
| 403 | |
| 404 | for (uint32_t region = 0; region < regionCount; region++) { |
| 405 | const auto ©_region = pRegions[region]; |
| 406 | if (src_access) { |
| 407 | MemoryAccessRange src_range = MakeMemoryAccessRange(*src_buffer, copy_region.srcOffset, copy_region.size); |
| 408 | UpdateAccessState(src_access, SYNC_TRANSFER_TRANSFER_READ, src_range); |
| 409 | } |
| 410 | if (dst_access) { |
| 411 | MemoryAccessRange dst_range = MakeMemoryAccessRange(*dst_buffer, copy_region.dstOffset, copy_region.size); |
| 412 | UpdateAccessState(dst_access, SYNC_TRANSFER_TRANSFER_WRITE, dst_range); |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | bool SyncValidator::PreCallValidateCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, |
| 418 | VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, |
| 419 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, |
| 420 | uint32_t bufferMemoryBarrierCount, |
| 421 | const VkBufferMemoryBarrier *pBufferMemoryBarriers, |
| 422 | uint32_t imageMemoryBarrierCount, |
| 423 | const VkImageMemoryBarrier *pImageMemoryBarriers) const { |
| 424 | bool skip = false; |
| 425 | |
| 426 | return skip; |
| 427 | } |
| 428 | |
| 429 | void SyncValidator::PreCallRecordCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, |
| 430 | VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, |
| 431 | uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, |
| 432 | uint32_t bufferMemoryBarrierCount, |
| 433 | const VkBufferMemoryBarrier *pBufferMemoryBarriers, |
| 434 | uint32_t imageMemoryBarrierCount, |
| 435 | const VkImageMemoryBarrier *pImageMemoryBarriers) { |
| 436 | // Just implement the buffer barrier for now |
| 437 | auto *tracker = GetAccessTracker(commandBuffer); |
| 438 | assert(tracker); |
| 439 | auto src_stage_scope = AccessScopeByStage(srcStageMask); |
| 440 | auto dst_stage_scope = AccessScopeByStage(dstStageMask); |
| 441 | |
| 442 | ApplyBufferBarriers(tracker, srcStageMask, src_stage_scope, dstStageMask, dst_stage_scope, bufferMemoryBarrierCount, |
| 443 | pBufferMemoryBarriers); |
| 444 | ApplyImageBarriers(tracker, src_stage_scope, dst_stage_scope, imageMemoryBarrierCount, pImageMemoryBarriers); |
| 445 | |
| 446 | // Apply these last in-case there operation is a superset of the other two and would clean them up... |
| 447 | ApplyGlobalBarriers(tracker, srcStageMask, dstStageMask, src_stage_scope, dst_stage_scope, memoryBarrierCount, pMemoryBarriers); |
| 448 | } |
| 449 | |
| 450 | void SyncValidator::PostCallRecordCreateDevice(VkPhysicalDevice gpu, const VkDeviceCreateInfo *pCreateInfo, |
| 451 | const VkAllocationCallbacks *pAllocator, VkDevice *pDevice, VkResult result) { |
| 452 | // The state tracker sets up the device state |
| 453 | StateTracker::PostCallRecordCreateDevice(gpu, pCreateInfo, pAllocator, pDevice, result); |
| 454 | |
| 455 | // Add the callback hooks for the functions that are either broadly or deeply used and that the ValidationStateTracker refactor |
| 456 | // would be messier without. |
| 457 | // TODO: Find a good way to do this hooklessly. |
| 458 | ValidationObject *device_object = GetLayerDataPtr(get_dispatch_key(*pDevice), layer_data_map); |
| 459 | ValidationObject *validation_data = GetValidationObject(device_object->object_dispatch, LayerObjectTypeSyncValidation); |
| 460 | SyncValidator *sync_device_state = static_cast<SyncValidator *>(validation_data); |
| 461 | |
| 462 | sync_device_state->SetCommandBufferResetCallback( |
| 463 | [sync_device_state](VkCommandBuffer command_buffer) -> void { sync_device_state->ResetCommandBuffer(command_buffer); }); |
| 464 | } |