blob: 49276aaf5f73b1641f99f118b44c1df35af21e9c [file] [log] [blame]
John Zulauf9cb530d2019-09-30 14:14:10 -06001/* 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
24static 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
44static 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
64static const MemoryAccessRange full_range(std::numeric_limits<VkDeviceSize>::min(), std::numeric_limits<VkDeviceSize>::max());
65static 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
71template <typename Flags, typename Map>
72SyncStageAccessFlags 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
84SyncStageAccessFlags SyncStageAccess::AccessScopeByStage(VkPipelineStageFlags stages) {
85 return AccessScopeImpl(stages, syncStageAccessMaskByStageBit);
86}
87
88SyncStageAccessFlags 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...
93SyncStageAccessFlags 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
100template <typename Action>
101void 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
137struct 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
153struct 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
173struct 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
205HazardResult 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
232void 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}
272void 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
284void 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
294void SyncValidator::ResetCommandBuffer(VkCommandBuffer command_buffer) {
295 auto *tracker = GetAccessTrackerNoInsert(command_buffer);
296 if (tracker) {
297 tracker->Reset();
298 }
299}
300
301void 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
313void 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
332void 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
338HazardResult 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
350void SyncValidator::UpdateAccessState(MemoryAccessRangeMap *accesses, SyncStageAccessIndex current_usage,
351 const MemoryAccessRange &range) {
352 UpdateMemoryAccessStateFunctor action(current_usage, tag);
353 UpdateMemoryAccessState(accesses, range, action);
354}
355
356bool 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 &copy_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
395void 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 &copy_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
417bool 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
429void 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
450void 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}