layers: Add validation of ownership transfers

Added tracking and validation of queue family ownership transfer
barriers.  Validation will warn on duplicated transfers and error on
acquire operations without matching release operations.

Change-Id: I922def2f9b9f8b984759030179c49f1561358977
diff --git a/layers/buffer_validation.cpp b/layers/buffer_validation.cpp
index e72ed1b..edbd9b4 100644
--- a/layers/buffer_validation.cpp
+++ b/layers/buffer_validation.cpp
@@ -590,6 +590,206 @@
     return pool && IsReleaseOp<VkImageMemoryBarrier, true>(pool, barrier);
 }
 
+template <typename Barrier>
+bool ValidateQFOTransferBarrierUniqueness(layer_data *device_data, const char *func_name, GLOBAL_CB_NODE *cb_state,
+                                          uint32_t barrier_count, const Barrier *barriers) {
+    using BarrierRecord = QFOTransferBarrier<Barrier>;
+    bool skip = false;
+    const auto report_data = core_validation::GetReportData(device_data);
+    auto pool = GetCommandPoolNode(device_data, cb_state->createInfo.commandPool);
+    auto &barrier_sets = GetQFOBarrierSets(cb_state, typename BarrierRecord::Tag());
+    const char *barrier_name = BarrierRecord::BarrierName();
+    const char *handle_name = BarrierRecord::HandleName();
+    const char *transfer_type = nullptr;
+    for (uint32_t b = 0; b < barrier_count; b++) {
+        if (!IsTransferOp(&barriers[b])) continue;
+        const BarrierRecord *barrier_record = nullptr;
+        if (IsReleaseOp<Barrier, true /* Assume IsTransfer */>(pool, &barriers[b])) {
+            const auto found = barrier_sets.release.find(barriers[b]);
+            if (found != barrier_sets.release.cend()) {
+                barrier_record = &(*found);
+                transfer_type = "releasing";
+            }
+        } else if (IsAcquireOp<Barrier, true /*Assume IsTransfer */>(pool, &barriers[b])) {
+            const auto found = barrier_sets.acquire.find(barriers[b]);
+            if (found != barrier_sets.acquire.cend()) {
+                barrier_record = &(*found);
+                transfer_type = "acquiring";
+            }
+        }
+        if (barrier_record != nullptr) {
+            skip |=
+                log_msg(report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                        HandleToUint64(cb_state->commandBuffer), BarrierRecord::ErrMsgDuplicateQFOInCB(),
+                        "%s: %s at index %" PRIu32 " %s queue ownership of %s (0x%" PRIx64 "), from srcQueueFamilyIndex %" PRIu32
+                        " to dstQueueFamilyIndex %" PRIu32 " duplicates existing barrier recorded in this command buffer.",
+                        func_name, barrier_name, b, transfer_type, handle_name, HandleToUint64(barrier_record->handle),
+                        barrier_record->srcQueueFamilyIndex, barrier_record->dstQueueFamilyIndex);
+        }
+    }
+    return skip;
+}
+
+template <typename Barrier>
+void RecordQFOTransferBarriers(layer_data *device_data, GLOBAL_CB_NODE *cb_state, uint32_t barrier_count, const Barrier *barriers) {
+    auto pool = GetCommandPoolNode(device_data, cb_state->createInfo.commandPool);
+    auto &barrier_sets = GetQFOBarrierSets(cb_state, typename QFOTransferBarrier<Barrier>::Tag());
+    for (uint32_t b = 0; b < barrier_count; b++) {
+        if (!IsTransferOp(&barriers[b])) continue;
+        if (IsReleaseOp<Barrier, true /* Assume IsTransfer*/>(pool, &barriers[b])) {
+            barrier_sets.release.emplace(barriers[b]);
+        } else if (IsAcquireOp<Barrier, true /*Assume IsTransfer */>(pool, &barriers[b])) {
+            barrier_sets.acquire.emplace(barriers[b]);
+        }
+    }
+}
+
+bool ValidateBarriersQFOTransferUniqueness(layer_data *device_data, const char *func_name, GLOBAL_CB_NODE *cb_state,
+                                           uint32_t bufferBarrierCount, const VkBufferMemoryBarrier *pBufferMemBarriers,
+                                           uint32_t imageMemBarrierCount, const VkImageMemoryBarrier *pImageMemBarriers) {
+    bool skip = false;
+    skip |= ValidateQFOTransferBarrierUniqueness(device_data, func_name, cb_state, bufferBarrierCount, pBufferMemBarriers);
+    skip |= ValidateQFOTransferBarrierUniqueness(device_data, func_name, cb_state, imageMemBarrierCount, pImageMemBarriers);
+    return skip;
+}
+
+void RecordBarriersQFOTransfers(layer_data *device_data, const char *func_name, GLOBAL_CB_NODE *cb_state,
+                                uint32_t bufferBarrierCount, const VkBufferMemoryBarrier *pBufferMemBarriers,
+                                uint32_t imageMemBarrierCount, const VkImageMemoryBarrier *pImageMemBarriers) {
+    RecordQFOTransferBarriers(device_data, cb_state, bufferBarrierCount, pBufferMemBarriers);
+    RecordQFOTransferBarriers(device_data, cb_state, imageMemBarrierCount, pImageMemBarriers);
+}
+
+template <typename BarrierRecord, typename Scoreboard>
+static bool ValidateAndUpdateQFOScoreboard(const debug_report_data *report_data, const GLOBAL_CB_NODE *cb_state,
+                                           const char *operation, const BarrierRecord &barrier, Scoreboard *scoreboard) {
+    // Record to the scoreboard or report that we have a duplication
+    bool skip = false;
+    auto inserted = scoreboard->insert(std::make_pair(barrier, cb_state));
+    if (!inserted.second && inserted.first->second != cb_state) {
+        // This is a duplication (but don't report duplicates from the same CB, as we do that at record time
+        skip = log_msg(
+            report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+            HandleToUint64(cb_state->commandBuffer), BarrierRecord::ErrMsgDuplicateQFOInSubmit(),
+            "%s: %s %s queue ownership of %s (0x%" PRIx64 "), from srcQueueFamilyIndex %" PRIu32 " to dstQueueFamilyIndex %" PRIu32
+            " duplicates existing barrier submitted in this batch from command buffer 0x%" PRIx64 ".",
+            "vkQueueSubmit()", BarrierRecord::BarrierName(), operation, BarrierRecord::HandleName(), HandleToUint64(barrier.handle),
+            barrier.srcQueueFamilyIndex, barrier.dstQueueFamilyIndex, HandleToUint64(inserted.first->second));
+    }
+    return skip;
+}
+
+template <typename Barrier>
+static bool ValidateQueuedQFOTransferBarriers(layer_data *device_data, GLOBAL_CB_NODE *cb_state,
+                                              QFOTransferCBScoreboards<Barrier> *scoreboards) {
+    using BarrierRecord = QFOTransferBarrier<Barrier>;
+    using TypeTag = typename BarrierRecord::Tag;
+    bool skip = false;
+    const auto report_data = core_validation::GetReportData(device_data);
+    const auto &cb_barriers = GetQFOBarrierSets(cb_state, TypeTag());
+    const GlobalQFOTransferBarrierMap<Barrier> &global_release_barriers =
+        core_validation::GetGlobalQFOReleaseBarrierMap(device_data, TypeTag());
+    const char *barrier_name = BarrierRecord::BarrierName();
+    const char *handle_name = BarrierRecord::HandleName();
+    // No release should have an extant duplicate (WARNING)
+    for (const auto &release : cb_barriers.release) {
+        // Check the global pending release barriers
+        const auto set_it = global_release_barriers.find(release.handle);
+        if (set_it != global_release_barriers.cend()) {
+            const QFOTransferBarrierSet<Barrier> &set_for_handle = set_it->second;
+            const auto found = set_for_handle.find(release);
+            if (found != set_for_handle.cend()) {
+                skip |= log_msg(report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                                HandleToUint64(cb_state->commandBuffer), BarrierRecord::ErrMsgDuplicateQFOSubmitted(),
+                                "%s: %s releasing queue ownership of %s (0x%" PRIx64 "), from srcQueueFamilyIndex %" PRIu32
+                                " to dstQueueFamilyIndex %" PRIu32
+                                " duplicates existing barrier queued for execution, without intervening acquire operation.",
+                                "vkQueueSubmit()", barrier_name, handle_name, HandleToUint64(found->handle),
+                                found->srcQueueFamilyIndex, found->dstQueueFamilyIndex);
+            }
+        }
+        skip |= ValidateAndUpdateQFOScoreboard(report_data, cb_state, "releasing", release, &scoreboards->release);
+    }
+    // Each acquire must have a matching release (ERROR)
+    for (const auto &acquire : cb_barriers.acquire) {
+        const auto set_it = global_release_barriers.find(acquire.handle);
+        bool matching_release_found = false;
+        if (set_it != global_release_barriers.cend()) {
+            const QFOTransferBarrierSet<Barrier> &set_for_handle = set_it->second;
+            matching_release_found = set_for_handle.find(acquire) != set_for_handle.cend();
+        }
+        if (!matching_release_found) {
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(cb_state->commandBuffer), BarrierRecord::ErrMsgMissingQFOReleaseInSubmit(),
+                            "%s: in submitted command buffer %s aquiring ownership of %s (0x%" PRIx64
+                            "), from srcQueueFamilyIndex %" PRIu32 " to dstQueueFamilyIndex %" PRIu32
+                            " has no matching release barrier queued for execution.",
+                            "vkQueueSubmit()", barrier_name, handle_name, HandleToUint64(acquire.handle),
+                            acquire.srcQueueFamilyIndex, acquire.dstQueueFamilyIndex);
+        }
+        skip |= ValidateAndUpdateQFOScoreboard(report_data, cb_state, "acquiring", acquire, &scoreboards->acquire);
+    }
+    return skip;
+}
+
+bool ValidateQueuedQFOTransfers(layer_data *device_data, GLOBAL_CB_NODE *cb_state,
+                                QFOTransferCBScoreboards<VkImageMemoryBarrier> *qfo_image_scoreboards,
+                                QFOTransferCBScoreboards<VkBufferMemoryBarrier> *qfo_buffer_scoreboards) {
+    bool skip = false;
+    skip |= ValidateQueuedQFOTransferBarriers<VkImageMemoryBarrier>(device_data, cb_state, qfo_image_scoreboards);
+    skip |= ValidateQueuedQFOTransferBarriers<VkBufferMemoryBarrier>(device_data, cb_state, qfo_buffer_scoreboards);
+    return skip;
+}
+
+template <typename Barrier>
+static void RecordQueuedQFOTransferBarriers(layer_data *device_data, GLOBAL_CB_NODE *cb_state) {
+    using BarrierRecord = QFOTransferBarrier<Barrier>;
+    using TypeTag = typename BarrierRecord::Tag;
+    const auto &cb_barriers = GetQFOBarrierSets(cb_state, TypeTag());
+    GlobalQFOTransferBarrierMap<Barrier> &global_release_barriers =
+        core_validation::GetGlobalQFOReleaseBarrierMap(device_data, TypeTag());
+
+    // Add release barriers from this submit to the global map
+    for (const auto &release : cb_barriers.release) {
+        // the global barrier list is mapped by resource handle to allow cleanup on resource destruction
+        // NOTE: We're using [] because creation of a Set is a needed side effect for new handles
+        global_release_barriers[release.handle].insert(release);
+    }
+
+    // Erase acquired barriers from this submit from the global map -- essentially marking releases as consumed
+    for (const auto &acquire : cb_barriers.acquire) {
+        // NOTE: We're not using [] because we don't want to create entries for missing releases
+        auto set_it = global_release_barriers.find(acquire.handle);
+        if (set_it != global_release_barriers.end()) {
+            QFOTransferBarrierSet<Barrier> &set_for_handle = set_it->second;
+            set_for_handle.erase(acquire);
+            if (set_for_handle.size() == 0) {  // Clean up empty sets
+                global_release_barriers.erase(set_it);
+            }
+        }
+    }
+}
+
+void RecordQueuedQFOTransfers(layer_data *device_data, GLOBAL_CB_NODE *cb_state) {
+    RecordQueuedQFOTransferBarriers<VkImageMemoryBarrier>(device_data, cb_state);
+    RecordQueuedQFOTransferBarriers<VkBufferMemoryBarrier>(device_data, cb_state);
+}
+
+// Remove the pending QFO release records from the global set
+// Note that the type of the handle argument constrained to match Barrier type
+// The defaulted BarrierRecord argument allows use to declare the type once, but is not intended to be specified by the caller
+template <typename Barrier, typename BarrierRecord = QFOTransferBarrier<Barrier>>
+static void EraseQFOReleaseBarriers(layer_data *device_data, const typename BarrierRecord::HandleType &handle) {
+    GlobalQFOTransferBarrierMap<Barrier> &global_release_barriers =
+        core_validation::GetGlobalQFOReleaseBarrierMap(device_data, typename BarrierRecord::Tag());
+    global_release_barriers.erase(handle);
+}
+
+// Avoid making the template globally visible by exporting the one instance of it we need.
+void EraseQFOImageRelaseBarriers(layer_data *device_data, const VkImage &image) {
+    EraseQFOReleaseBarriers<VkImageMemoryBarrier>(device_data, image);
+}
+
 void TransitionImageLayouts(layer_data *device_data, GLOBAL_CB_NODE *cb_state, uint32_t memBarrierCount,
                             const VkImageMemoryBarrier *pImgMemBarriers) {
     for (uint32_t i = 0; i < memBarrierCount; ++i) {
@@ -907,6 +1107,7 @@
         }
     }
     core_validation::ClearMemoryObjectBindings(device_data, obj_struct.handle, kVulkanObjectTypeImage);
+    EraseQFOReleaseBarriers<VkImageMemoryBarrier>(device_data, image);
     // Remove image from imageMap
     core_validation::GetImageMap(device_data)->erase(image);
     std::unordered_map<VkImage, std::vector<ImageSubresourcePair>> *imageSubresourceMap =
@@ -3662,6 +3863,7 @@
         }
     }
     ClearMemoryObjectBindings(device_data, HandleToUint64(buffer), kVulkanObjectTypeBuffer);
+    EraseQFOReleaseBarriers<VkBufferMemoryBarrier>(device_data, buffer);
     GetBufferMap(device_data)->erase(buffer_state->buffer);
 }
 
diff --git a/layers/buffer_validation.h b/layers/buffer_validation.h
index c41eeb6..d9f5e6c 100644
--- a/layers/buffer_validation.h
+++ b/layers/buffer_validation.h
@@ -121,6 +121,21 @@
 bool ValidateBarriersToImages(layer_data *device_data, GLOBAL_CB_NODE const *cb_state, uint32_t imageMemoryBarrierCount,
                               const VkImageMemoryBarrier *pImageMemoryBarriers, const char *func_name);
 
+bool ValidateBarriersQFOTransferUniqueness(layer_data *device_data, const char *func_name, GLOBAL_CB_NODE *cb_state,
+                                           uint32_t bufferBarrierCount, const VkBufferMemoryBarrier *pBufferMemBarriers,
+                                           uint32_t imageMemBarrierCount, const VkImageMemoryBarrier *pImageMemBarriers);
+
+void RecordBarriersQFOTransfers(layer_data *device_data, const char *func_name, GLOBAL_CB_NODE *cb_state,
+                                uint32_t bufferBarrierCount, const VkBufferMemoryBarrier *pBufferMemBarriers,
+                                uint32_t imageMemBarrierCount, const VkImageMemoryBarrier *pImageMemBarriers);
+
+bool ValidateQueuedQFOTransfers(layer_data *dev_data, GLOBAL_CB_NODE *pCB,
+                                QFOTransferCBScoreboards<VkImageMemoryBarrier> *qfo_image_scoreboards,
+                                QFOTransferCBScoreboards<VkBufferMemoryBarrier> *qfo_buffer_scoreboards);
+
+void RecordQueuedQFOTransfers(layer_data *dev_data, GLOBAL_CB_NODE *pCB);
+void EraseQFOImageRelaseBarriers(layer_data *device_data, const VkImage &image);
+
 void TransitionImageLayouts(layer_data *device_data, GLOBAL_CB_NODE *cb_state, uint32_t memBarrierCount,
                             const VkImageMemoryBarrier *pImgMemBarriers);
 
diff --git a/layers/core_validation.cpp b/layers/core_validation.cpp
index 41aa195..8f102c4 100644
--- a/layers/core_validation.cpp
+++ b/layers/core_validation.cpp
@@ -193,6 +193,8 @@
     unordered_map<VkShaderModule, unique_ptr<shader_module>> shaderModuleMap;
     unordered_map<VkDescriptorUpdateTemplateKHR, unique_ptr<TEMPLATE_STATE>> desc_template_map;
     unordered_map<VkSwapchainKHR, std::unique_ptr<SWAPCHAIN_NODE>> swapchainMap;
+    GlobalQFOTransferBarrierMap<VkImageMemoryBarrier> qfo_release_image_barrier_map;
+    GlobalQFOTransferBarrierMap<VkBufferMemoryBarrier> qfo_release_buffer_barrier_map;
 
     VkDevice device = VK_NULL_HANDLE;
     VkPhysicalDevice physical_device = VK_NULL_HANDLE;
@@ -249,6 +251,16 @@
 // TODO : This can be much smarter, using separate locks for separate global data
 static mutex_t global_lock;
 
+// Get the global map of pending releases
+GlobalQFOTransferBarrierMap<VkImageMemoryBarrier> &GetGlobalQFOReleaseBarrierMap(
+    layer_data *dev_data, const QFOTransferBarrier<VkImageMemoryBarrier>::Tag &type_tag) {
+    return dev_data->qfo_release_image_barrier_map;
+}
+GlobalQFOTransferBarrierMap<VkBufferMemoryBarrier> &GetGlobalQFOReleaseBarrierMap(
+    layer_data *dev_data, const QFOTransferBarrier<VkBufferMemoryBarrier>::Tag &type_tag) {
+    return dev_data->qfo_release_buffer_barrier_map;
+}
+
 // Return IMAGE_VIEW_STATE ptr for specified imageView or else NULL
 IMAGE_VIEW_STATE *GetImageViewState(const layer_data *dev_data, VkImageView image_view) {
     auto iv_it = dev_data->imageViewMap.find(image_view);
@@ -1954,6 +1966,9 @@
         pCB->framebuffers.clear();
         pCB->activeFramebuffer = VK_NULL_HANDLE;
         memset(&pCB->index_buffer_binding, 0, sizeof(pCB->index_buffer_binding));
+
+        pCB->qfo_transfer_image_barriers.Reset();
+        pCB->qfo_transfer_buffer_barriers.Reset();
     }
 }
 
@@ -2731,7 +2746,9 @@
     return skip;
 }
 
-static bool validatePrimaryCommandBufferState(layer_data *dev_data, GLOBAL_CB_NODE *pCB, int current_submit_count) {
+static bool validatePrimaryCommandBufferState(layer_data *dev_data, GLOBAL_CB_NODE *pCB, int current_submit_count,
+                                              QFOTransferCBScoreboards<VkImageMemoryBarrier> *qfo_image_scoreboards,
+                                              QFOTransferCBScoreboards<VkBufferMemoryBarrier> *qfo_buffer_scoreboards) {
     // Track in-use for resources off of primary and any secondary CBs
     bool skip = false;
 
@@ -2740,9 +2757,11 @@
     skip |= validateCommandBufferSimultaneousUse(dev_data, pCB, current_submit_count);
 
     skip |= validateResources(dev_data, pCB);
+    skip |= ValidateQueuedQFOTransfers(dev_data, pCB, qfo_image_scoreboards, qfo_buffer_scoreboards);
 
     for (auto pSubCB : pCB->linkedCommandBuffers) {
         skip |= validateResources(dev_data, pSubCB);
+        skip |= ValidateQueuedQFOTransfers(dev_data, pSubCB, qfo_image_scoreboards, qfo_buffer_scoreboards);
         // TODO: replace with invalidateCommandBuffers() at recording.
         if ((pSubCB->primaryCommandBuffer != pCB->commandBuffer) &&
             !(pSubCB->beginInfo.flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) {
@@ -2879,9 +2898,11 @@
                     cbs.push_back(secondaryCmdBuffer->commandBuffer);
                     UpdateCmdBufImageLayouts(dev_data, secondaryCmdBuffer);
                     incrementResources(dev_data, secondaryCmdBuffer);
+                    RecordQueuedQFOTransfers(dev_data, secondaryCmdBuffer);
                 }
                 UpdateCmdBufImageLayouts(dev_data, cb_node);
                 incrementResources(dev_data, cb_node);
+                RecordQueuedQFOTransfers(dev_data, cb_node);
             }
         }
         pQueue->submissions.emplace_back(cbs, semaphore_waits, semaphore_signals, semaphore_externals,
@@ -2947,13 +2968,17 @@
                 }
             }
         }
+        QFOTransferCBScoreboards<VkImageMemoryBarrier> qfo_image_scoreboards;
+        QFOTransferCBScoreboards<VkBufferMemoryBarrier> qfo_buffer_scoreboards;
+
         for (uint32_t i = 0; i < submit->commandBufferCount; i++) {
             auto cb_node = GetCBNode(dev_data, submit->pCommandBuffers[i]);
             if (cb_node) {
                 skip |= ValidateCmdBufImageLayouts(dev_data, cb_node, dev_data->imageLayoutMap, localImageLayoutMap);
                 current_cmds.push_back(submit->pCommandBuffers[i]);
                 skip |= validatePrimaryCommandBufferState(
-                    dev_data, cb_node, (int)std::count(current_cmds.begin(), current_cmds.end(), submit->pCommandBuffers[i]));
+                    dev_data, cb_node, (int)std::count(current_cmds.begin(), current_cmds.end(), submit->pCommandBuffers[i]),
+                    &qfo_image_scoreboards, &qfo_buffer_scoreboards);
                 skip |= validateQueueFamilyIndices(dev_data, cb_node, queue);
 
                 // Potential early exit here as bad object state may crash in delayed function calls
@@ -8014,6 +8039,10 @@
             }
         }
     }
+
+    skip |= ValidateBarriersQFOTransferUniqueness(device_data, funcName, cb_state, bufferBarrierCount, pBufferMemBarriers,
+                                                  imageMemBarrierCount, pImageMemBarriers);
+
     return skip;
 }
 
@@ -8219,6 +8248,10 @@
         dev_data->dispatch_table.CmdWaitEvents(commandBuffer, eventCount, pEvents, sourceStageMask, dstStageMask,
                                                memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers,
                                                imageMemoryBarrierCount, pImageMemoryBarriers);
+    lock.lock();
+
+    RecordBarriersQFOTransfers(dev_data, "vkCmdWaitEvents()", cb_state, bufferMemoryBarrierCount, pBufferMemoryBarriers,
+                               imageMemoryBarrierCount, pImageMemoryBarriers);
 }
 
 static bool PreCallValidateCmdPipelineBarrier(layer_data *device_data, GLOBAL_CB_NODE *cb_state, VkPipelineStageFlags srcStageMask,
@@ -8256,7 +8289,10 @@
 }
 
 static void PreCallRecordCmdPipelineBarrier(layer_data *device_data, GLOBAL_CB_NODE *cb_state, VkCommandBuffer commandBuffer,
+                                            uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers,
                                             uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers) {
+    RecordBarriersQFOTransfers(device_data, "vkCmdPipelineBarrier()", cb_state, bufferMemoryBarrierCount, pBufferMemoryBarriers,
+                               imageMemoryBarrierCount, pImageMemoryBarriers);
     TransitionImageLayouts(device_data, cb_state, imageMemoryBarrierCount, pImageMemoryBarriers);
 }
 
@@ -8274,7 +8310,8 @@
                                                   memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount,
                                                   pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
         if (!skip) {
-            PreCallRecordCmdPipelineBarrier(device_data, cb_state, commandBuffer, imageMemoryBarrierCount, pImageMemoryBarriers);
+            PreCallRecordCmdPipelineBarrier(device_data, cb_state, commandBuffer, bufferMemoryBarrierCount, pBufferMemoryBarriers,
+                                            imageMemoryBarrierCount, pImageMemoryBarriers);
         }
     } else {
         assert(0);
@@ -10857,6 +10894,7 @@
                     dev_data->imageSubresourceMap.erase(image_sub);
                 }
                 skip = ClearMemoryObjectBindings(dev_data, HandleToUint64(swapchain_image), kVulkanObjectTypeSwapchainKHR);
+                EraseQFOImageRelaseBarriers(dev_data, swapchain_image);
                 dev_data->imageMap.erase(swapchain_image);
             }
         }
diff --git a/layers/core_validation_types.h b/layers/core_validation_types.h
index bfb8f96..ecc3dca 100644
--- a/layers/core_validation_types.h
+++ b/layers/core_validation_types.h
@@ -727,6 +727,135 @@
         dynamicOffsets.clear();
     }
 };
+
+// Types to store queue family ownership (QFO) Transfers
+
+// Common to image and buffer memory barriers
+template <typename Handle, typename Barrier>
+struct QFOTransferBarrierBase {
+    using HandleType = Handle;
+    using BarrierType = Barrier;
+    struct Tag {};
+    HandleType handle = VK_NULL_HANDLE;
+    uint32_t srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    uint32_t dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+    QFOTransferBarrierBase() = default;
+    QFOTransferBarrierBase(const BarrierType &barrier, const HandleType &resource_handle)
+        : handle(resource_handle),
+          srcQueueFamilyIndex(barrier.srcQueueFamilyIndex),
+          dstQueueFamilyIndex(barrier.dstQueueFamilyIndex) {}
+
+    hash_util::HashCombiner base_hash_combiner() const {
+        hash_util::HashCombiner hc;
+        hc << srcQueueFamilyIndex << dstQueueFamilyIndex << handle;
+        return hc;
+    }
+
+    bool operator==(const QFOTransferBarrierBase &rhs) const {
+        return (srcQueueFamilyIndex == rhs.srcQueueFamilyIndex) && (dstQueueFamilyIndex == rhs.dstQueueFamilyIndex) &&
+               (handle == rhs.handle);
+    }
+};
+
+template <typename Barrier>
+struct QFOTransferBarrier {};
+
+// Image barrier specific implementation
+template <>
+struct QFOTransferBarrier<VkImageMemoryBarrier> : public QFOTransferBarrierBase<VkImage, VkImageMemoryBarrier> {
+    using BaseType = QFOTransferBarrierBase<VkImage, VkImageMemoryBarrier>;
+    VkImageLayout oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    VkImageLayout newLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    VkImageSubresourceRange subresourceRange;
+
+    QFOTransferBarrier() = default;
+    QFOTransferBarrier(const BarrierType &barrier)
+        : BaseType(barrier, barrier.image),
+          oldLayout(barrier.oldLayout),
+          newLayout(barrier.newLayout),
+          subresourceRange(barrier.subresourceRange) {}
+    size_t hash() const {
+        // Ignoring the layout information for the purpose of the hash, as we're interested in QFO release/acquisition w.r.t.
+        // the subresource affected, an layout transitions are current validated on another path
+        auto hc = base_hash_combiner() << subresourceRange;
+        return hc.Value();
+    }
+    bool operator==(const QFOTransferBarrier<BarrierType> &rhs) const {
+        // Ignoring layout w.r.t. equality. See comment in hash above.
+        return (static_cast<BaseType>(*this) == static_cast<BaseType>(rhs)) && (subresourceRange == rhs.subresourceRange);
+    }
+    // TODO: codegen a comprehensive complie time type -> string (and or other traits) template family
+    static const char *BarrierName() { return "VkImageMemoryBarrier"; }
+    static const char *HandleName() { return "VkImage"; }
+    // UNASSIGNED-VkImageMemoryBarrier-image-00001 QFO transfer image barrier must not duplicate QFO recorded in command buffer
+    static const char *ErrMsgDuplicateQFOInCB() { return "UNASSIGNED-VkImageMemoryBarrier-image-00001"; }
+    // UNASSIGNED-VkImageMemoryBarrier-image-00002 QFO transfer image barrier must not duplicate QFO submitted in batch
+    static const char *ErrMsgDuplicateQFOInSubmit() { return "UNASSIGNED-VkImageMemoryBarrier-image-00002"; }
+    // UNASSIGNED-VkImageMemoryBarrier-image-00003 QFO transfer image barrier must not duplicate QFO submitted previously
+    static const char *ErrMsgDuplicateQFOSubmitted() { return "UNASSIGNED-VkImageMemoryBarrier-image-00003"; }
+    // UNASSIGNED-VkImageMemoryBarrier-image-00004 QFO acquire image barrier must have matching QFO release submitted previously
+    static const char *ErrMsgMissingQFOReleaseInSubmit() { return "UNASSIGNED-VkImageMemoryBarrier-image-00004"; }
+};
+
+// Buffer barrier specific implementation
+template <>
+struct QFOTransferBarrier<VkBufferMemoryBarrier> : public QFOTransferBarrierBase<VkBuffer, VkBufferMemoryBarrier> {
+    using BaseType = QFOTransferBarrierBase<VkBuffer, VkBufferMemoryBarrier>;
+    VkDeviceSize offset = 0;
+    VkDeviceSize size = 0;
+    QFOTransferBarrier(const VkBufferMemoryBarrier &barrier)
+        : BaseType(barrier, barrier.buffer), offset(barrier.offset), size(barrier.size) {}
+    size_t hash() const {
+        auto hc = base_hash_combiner() << offset << size;
+        return hc.Value();
+    }
+    bool operator==(const QFOTransferBarrier<BarrierType> &rhs) const {
+        return (static_cast<BaseType>(*this) == static_cast<BaseType>(rhs)) && (offset == rhs.offset) && (size == rhs.size);
+    }
+    static const char *BarrierName() { return "VkBufferMemoryBarrier"; }
+    static const char *HandleName() { return "VkBuffer"; }
+    // UNASSIGNED-VkImageMemoryBarrier-buffer-00001 QFO transfer buffer barrier must not duplicate QFO recorded in command buffer
+    static const char *ErrMsgDuplicateQFOInCB() { return "UNASSIGNED-VkBufferMemoryBarrier-buffer-00001"; }
+    // UNASSIGNED-VkBufferMemoryBarrier-buffer-00002 QFO transfer buffer barrier must not duplicate QFO submitted in batch
+    static const char *ErrMsgDuplicateQFOInSubmit() { return "UNASSIGNED-VkBufferMemoryBarrier-buffer-00002"; }
+    // UNASSIGNED-VkBufferMemoryBarrier-buffer-00003 QFO transfer buffer barrier must not duplicate QFO submitted previously
+    static const char *ErrMsgDuplicateQFOSubmitted() { return "UNASSIGNED-VkBufferMemoryBarrier-buffer-00003"; }
+    // UNASSIGNED-VkBufferMemoryBarrier-buffer-00004 QFO acquire buffer barrier must have matching QFO release submitted previously
+    static const char *ErrMsgMissingQFOReleaseInSubmit() { return "UNASSIGNED-VkBufferMemoryBarrier-buffer-00004"; }
+};
+
+template <typename Barrier>
+using QFOTransferBarrierHash = hash_util::HasHashMember<QFOTransferBarrier<Barrier>>;
+
+// Command buffers store the set of barriers recorded
+template <typename Barrier>
+using QFOTransferBarrierSet = std::unordered_set<QFOTransferBarrier<Barrier>, QFOTransferBarrierHash<Barrier>>;
+template <typename Barrier>
+struct QFOTransferBarrierSets {
+    QFOTransferBarrierSet<Barrier> release;
+    QFOTransferBarrierSet<Barrier> acquire;
+    void Reset() {
+        acquire.clear();
+        release.clear();
+    }
+};
+
+// The layer_data stores the map of pending release barriers
+template <typename Barrier>
+using GlobalQFOTransferBarrierMap =
+    std::unordered_map<typename QFOTransferBarrier<Barrier>::HandleType, QFOTransferBarrierSet<Barrier>>;
+
+// Submit queue uses the Scoreboard to track all release/acquire operations in a batch.
+template <typename Barrier>
+using QFOTransferCBScoreboard =
+    std::unordered_map<QFOTransferBarrier<Barrier>, const GLOBAL_CB_NODE *, QFOTransferBarrierHash<Barrier>>;
+template <typename Barrier>
+struct QFOTransferCBScoreboards {
+    QFOTransferCBScoreboard<Barrier> acquire;
+    QFOTransferCBScoreboard<Barrier> release;
+};
+
 // Cmd Buffer Wrapper Struct - TODO : This desperately needs its own class
 struct GLOBAL_CB_NODE : public BASE_NODE {
     VkCommandBuffer commandBuffer;
@@ -761,6 +890,9 @@
     std::unordered_set<VK_OBJECT> object_bindings;
     std::vector<VK_OBJECT> broken_bindings;
 
+    QFOTransferBarrierSets<VkBufferMemoryBarrier> qfo_transfer_buffer_barriers;
+    QFOTransferBarrierSets<VkImageMemoryBarrier> qfo_transfer_image_barriers;
+
     std::unordered_set<VkEvent> waitedEvents;
     std::vector<VkEvent> writeEventsBeforeWait;
     std::vector<VkEvent> events;
@@ -792,6 +924,15 @@
     INDEX_BUFFER_BINDING index_buffer_binding;
 };
 
+static QFOTransferBarrierSets<VkImageMemoryBarrier> &GetQFOBarrierSets(
+    GLOBAL_CB_NODE *cb, const QFOTransferBarrier<VkImageMemoryBarrier>::Tag &type_tag) {
+    return cb->qfo_transfer_image_barriers;
+}
+static QFOTransferBarrierSets<VkBufferMemoryBarrier> &GetQFOBarrierSets(
+    GLOBAL_CB_NODE *cb, const QFOTransferBarrier<VkBufferMemoryBarrier>::Tag &type_tag) {
+    return cb->qfo_transfer_buffer_barriers;
+}
+
 struct SEMAPHORE_WAIT {
     VkSemaphore semaphore;
     VkQueue queue;
@@ -947,6 +1088,11 @@
 std::unordered_map<VkImageView, std::unique_ptr<IMAGE_VIEW_STATE>> *GetImageViewMap(layer_data *device_data);
 const DeviceExtensions *GetDeviceExtensions(const layer_data *);
 uint32_t GetApiVersion(const layer_data *);
+
+GlobalQFOTransferBarrierMap<VkImageMemoryBarrier> &GetGlobalQFOReleaseBarrierMap(
+    layer_data *dev_data, const QFOTransferBarrier<VkImageMemoryBarrier>::Tag &type_tag);
+GlobalQFOTransferBarrierMap<VkBufferMemoryBarrier> &GetGlobalQFOReleaseBarrierMap(
+    layer_data *dev_data, const QFOTransferBarrier<VkBufferMemoryBarrier>::Tag &type_tag);
 }  // namespace core_validation
 
 #endif  // CORE_VALIDATION_TYPES_H_
diff --git a/layers/hash_vk_types.h b/layers/hash_vk_types.h
index b6d6713..5dff5d6 100644
--- a/layers/hash_vk_types.h
+++ b/layers/hash_vk_types.h
@@ -83,4 +83,20 @@
 struct hash<PushConstantRanges> : public hash_util::IsOrderedContainer<PushConstantRanges> {};
 }  // namespace std
 
+// VkImageSubresourceRange
+static bool operator==(const VkImageSubresourceRange &lhs, const VkImageSubresourceRange &rhs) {
+    return (lhs.aspectMask == rhs.aspectMask) && (lhs.baseMipLevel == rhs.baseMipLevel) && (lhs.levelCount == rhs.levelCount) &&
+           (lhs.baseArrayLayer == lhs.baseArrayLayer) && (lhs.layerCount == lhs.layerCount);
+}
+namespace std {
+template <>
+struct hash<VkImageSubresourceRange> {
+    size_t operator()(const VkImageSubresourceRange &value) const {
+        hash_util::HashCombiner hc;
+        hc << value.aspectMask << value.baseMipLevel << value.levelCount << value.baseArrayLayer << value.layerCount;
+        return hc.Value();
+    }
+};
+}  // namespace std
+
 #endif  // HASH_VK_TYPES_H_