Repository: KhronosGroup/Vulkan-ValidationLayers
Pull request #9803: layers: Fix image barrier layout validation
Author: login: artem-lunarg
Created: 2025-03-27T21:13:46Z | Merged: 2025-03-27T22:29:24Z

Closes https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9585

Moved some code, the fix is the best visible in its separate commit.


Diff:
diff --git a/layers/core_checks/cc_image_layout.cpp b/layers/core_checks/cc_image_layout.cpp
index 4a3f0ef3f43..30a8a0148a7 100644
--- a/layers/core_checks/cc_image_layout.cpp
+++ b/layers/core_checks/cc_image_layout.cpp
@@ -893,6 +893,80 @@ bool CoreChecks::VerifyImageBarrierLayouts(const vvl::CommandBuffer &cb_state, c
     return skip;
 }
 
+static std::vector<uint32_t> GetUsedAttachments(const vvl::CommandBuffer &cb_state) {
+    vvl::unordered_set<uint32_t> unique;
+
+    for (size_t i = 0; i < cb_state.rendering_attachments.color_locations.size(); ++i) {
+        const uint32_t unmapped_color_attachment = cb_state.rendering_attachments.color_locations[i];
+        if (unmapped_color_attachment != VK_ATTACHMENT_UNUSED) {
+            unique.insert(unmapped_color_attachment);
+        }
+    }
+
+    for (size_t i = 0; i < cb_state.rendering_attachments.color_indexes.size(); ++i) {
+        const uint32_t unmapped_color_index = cb_state.rendering_attachments.color_indexes[i];
+        if (unmapped_color_index != VK_ATTACHMENT_UNUSED) {
+            unique.insert(unmapped_color_index);
+        }
+    }
+
+    if (cb_state.rendering_attachments.depth_index) {
+        unique.insert(*cb_state.rendering_attachments.depth_index);
+    }
+    if (cb_state.rendering_attachments.stencil_index) {
+        unique.insert(*cb_state.rendering_attachments.stencil_index);
+    }
+
+    std::vector<uint32_t> attachments;
+    for (auto x : unique) {
+        attachments.push_back(x);
+    }
+    return attachments;
+}
+
+bool CoreChecks::VerifyDynamicRenderingImageBarrierLayouts(const vvl::CommandBuffer &cb_state, const vvl::Image &image_state,
+                                                           const VkRenderingInfo &rendering_info,
+                                                           const Location &barrier_loc) const {
+    bool skip = false;
+    std::vector<uint32_t> used_attachments(GetUsedAttachments(cb_state));
+
+    const vvl::CommandBuffer::ImageLayoutMap &cb_layout_map = cb_state.GetImageLayoutMap();
+    auto it = cb_layout_map.find(image_state.VkHandle());
+    if (it == cb_layout_map.end()) {
+        return skip;
+    }
+    ImageLayoutRegistry &cb_image_layouts = *it->second;
+
+    for (auto color_attachment_idx : used_attachments) {
+        if (color_attachment_idx >= rendering_info.colorAttachmentCount) {
+            continue;
+        }
+        const auto &color_attachment = rendering_info.pColorAttachments[color_attachment_idx];
+        if (color_attachment.imageView == VK_NULL_HANDLE) {
+            continue;
+        }
+        const auto image_view_state = Get<vvl::ImageView>(color_attachment.imageView);
+        ASSERT_AND_CONTINUE(image_view_state);
+
+        if (image_state.VkHandle() == image_view_state->image_state->VkHandle()) {
+            skip |= cb_image_layouts.AnyInRange(
+                image_view_state->normalized_subresource_range,
+                [this, &image_state, &barrier_loc](const LayoutRange &range, const LayoutEntry &state) {
+                    bool local_skip = false;
+                    if (state.current_layout != VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ &&
+                        state.current_layout != VK_IMAGE_LAYOUT_GENERAL) {
+                        const auto &vuid = sync_vuid_maps::GetShaderTileImageVUID(
+                            barrier_loc, sync_vuid_maps::ShaderTileImageError::kShaderTileImageLayout);
+                        local_skip |= LogError(vuid, image_state.VkHandle(), barrier_loc, "image layout is %s.",
+                                               string_VkImageLayout(state.current_layout));
+                    }
+                    return local_skip;
+                });
+        }
+    }
+    return skip;
+}
+
 bool CoreChecks::FindLayouts(const vvl::Image &image_state, std::vector<VkImageLayout> &layouts) const {
     const auto *layout_range_map = image_state.layout_range_map.get();
     if (!layout_range_map) return false;
diff --git a/layers/core_checks/cc_synchronization.cpp b/layers/core_checks/cc_synchronization.cpp
index 638c2f51878..c97a825deb2 100644
--- a/layers/core_checks/cc_synchronization.cpp
+++ b/layers/core_checks/cc_synchronization.cpp
@@ -1655,37 +1655,6 @@ bool CoreChecks::ValidateImageLayoutAgainstImageUsage(const Location &layout_loc
     return skip;
 }
 
-std::vector<uint32_t> GetUsedAttachments(const vvl::CommandBuffer &cb_state) {
-    std::set<uint32_t> unique;
-
-    for (size_t i = 0; i < cb_state.rendering_attachments.color_locations.size(); ++i) {
-        const uint32_t unmapped_color_attachment = cb_state.rendering_attachments.color_locations[i];
-        if (unmapped_color_attachment != VK_ATTACHMENT_UNUSED) {
-            unique.insert(unmapped_color_attachment);
-        }
-    }
-
-    for (size_t i = 0; i < cb_state.rendering_attachments.color_indexes.size(); ++i) {
-        const uint32_t unmapped_color_index = cb_state.rendering_attachments.color_indexes[i];
-        if (unmapped_color_index != VK_ATTACHMENT_UNUSED) {
-            unique.insert(unmapped_color_index);
-        }
-    }
-
-    if (cb_state.rendering_attachments.depth_index) {
-        unique.insert(*cb_state.rendering_attachments.depth_index);
-    }
-    if (cb_state.rendering_attachments.stencil_index) {
-        unique.insert(*cb_state.rendering_attachments.stencil_index);
-    }
-
-    std::vector<uint32_t> attachments;
-    for (auto x : unique) {
-        attachments.push_back(x);
-    }
-    return attachments;
-}
-
 // Verify image barrier is compatible with the image it references.
 bool CoreChecks::ValidateImageBarrierAgainstImage(const vvl::CommandBuffer &cb_state, const ImageBarrier &barrier,
                                                   const Location &barrier_loc, const vvl::Image &image_state,
@@ -1783,41 +1752,19 @@ bool CoreChecks::ValidateImageBarrierAgainstImage(const vvl::CommandBuffer &cb_s
         }
     }
 
-    if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
-        // TODO: Set memory invalid which is in mem_tracker currently
-    } else if (!IsQueueFamilyExternal(barrier.srcQueueFamilyIndex)) {
+    const bool validate_barrier_layouts =
+        (old_layout != new_layout)                               // barrier defines layout transtion
+        && (old_layout != VK_IMAGE_LAYOUT_UNDEFINED)             // undefined allows any layout
+        && !IsQueueFamilyExternal(barrier.srcQueueFamilyIndex);  // do not validate layouts of external resources
+
+    if (validate_barrier_layouts) {
         skip |= VerifyImageBarrierLayouts(cb_state, image_state, image_loc, barrier, layout_updates_state);
     }
 
     const vvl::RenderPass *rp_state = cb_state.active_render_pass.get();
-    if (enabled_features.dynamicRenderingLocalRead && rp_state) {
-        const auto &rendering_info = rp_state->dynamic_rendering_begin_rendering_info;
-        std::vector<uint32_t> used_attachments(GetUsedAttachments(cb_state));
-
-        for (auto color_attachment_idx : used_attachments) {
-            if (color_attachment_idx >= rendering_info.colorAttachmentCount) {
-                continue;
-            }
-            const auto &color_attachment = rendering_info.pColorAttachments[color_attachment_idx];
-            if (color_attachment.imageView == VK_NULL_HANDLE) {
-                continue;
-            }
-            const auto image_view_state = Get<vvl::ImageView>(color_attachment.imageView);
-            ASSERT_AND_CONTINUE(image_view_state);
-            const auto &image_view_image_state = image_view_state->image_state;
-
-            if (image == image_view_image_state->VkHandle()) {
-                auto guard = image_view_image_state->layout_range_map->ReadLock();
-
-                for (const auto &entry : *image_view_image_state->layout_range_map) {
-                    if (entry.second != VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ && entry.second != VK_IMAGE_LAYOUT_GENERAL) {
-                        const auto &vuid = sync_vuid_maps::GetShaderTileImageVUID(
-                            barrier_loc, sync_vuid_maps::ShaderTileImageError::kShaderTileImageLayout);
-                        skip |= LogError(vuid, image, barrier_loc, "image layout is %s.", string_VkImageLayout(entry.second));
-                    }
-                }
-            }
-        }
+    if (rp_state && rp_state->UsesDynamicRendering()) {
+        skip |= VerifyDynamicRenderingImageBarrierLayouts(cb_state, image_state,
+                                                          *rp_state->dynamic_rendering_begin_rendering_info.ptr(), barrier_loc);
     }
 
     // checks color format and (single-plane or non-disjoint)
diff --git a/layers/core_checks/core_validation.h b/layers/core_checks/core_validation.h
index 6b541741b64..21835868a8a 100644
--- a/layers/core_checks/core_validation.h
+++ b/layers/core_checks/core_validation.h
@@ -1048,6 +1048,9 @@ class CoreChecks : public vvl::Device {
     bool VerifyImageBarrierLayouts(const vvl::CommandBuffer& cb_state, const vvl::Image& image_state, const Location& image_loc,
                                    const ImageBarrier& image_barrier, vvl::CommandBuffer::ImageLayoutMap& local_layout_map) const;
 
+    bool VerifyDynamicRenderingImageBarrierLayouts(const vvl::CommandBuffer& cb_state, const vvl::Image& image_state,
+                                                   const VkRenderingInfo& rendering_info, const Location& barrier_loc) const;
+
     bool ValidateImageLayoutAgainstImageUsage(const Location& layout_loc, VkImage image, VkImageLayout layout,
                                               VkImageUsageFlags usage) const;
 
diff --git a/layers/state_tracker/image_state.h b/layers/state_tracker/image_state.h
index 112484d57a9..664f1df3aad 100644
--- a/layers/state_tracker/image_state.h
+++ b/layers/state_tracker/image_state.h
@@ -119,6 +119,10 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
     const image_layout_map::Encoder subresource_encoder;                             // Subresource resolution encoder
     const VkDevice store_device_as_workaround;                                       // TODO REMOVE WHEN encoder can be const
 
+    // This map is used to validate/update image layouts during submit time processing.
+    // Record time validation can't use this. At the beginning of the command buffer
+    // the global image layout can't be determined because it depends on the previously
+    // submitted command buffers.
     std::shared_ptr<GlobalImageLayoutRangeMap> layout_range_map;
 
     vvl::unordered_set<std::shared_ptr<const vvl::VideoProfileDesc>> supported_video_profiles;
diff --git a/tests/unit/dynamic_rendering_local_read.cpp b/tests/unit/dynamic_rendering_local_read.cpp
index 2a93e831f16..3e0f9bb0a99 100644
--- a/tests/unit/dynamic_rendering_local_read.cpp
+++ b/tests/unit/dynamic_rendering_local_read.cpp
@@ -477,7 +477,10 @@ TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierRequireFeature) {
     m_command_buffer.End();
 }
 
-TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayout) {
+// TODO: temporary disabled: the first layout transition in the command buffer can be validation only when submitted to the queue.
+// The original validation code was incorrect: ccan't use global image layout map (updated during submit time) to validate
+// record-time state.
+TEST_F(NegativeDynamicRenderingLocalRead, DISABLED_ImageBarrierInProperLayout) {
     TEST_DESCRIPTION(
         "Barrier within a render pass instance started with vkCmdBeginRendering, then the image must be in the "
         "VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ or VK_IMAGE_LAYOUT_GENERAL layout");
@@ -533,6 +536,61 @@ TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayout) {
 
     m_command_buffer.EndRendering();
     m_command_buffer.End();
+
+    m_default_queue->Submit(m_command_buffer);
+    m_default_queue->Wait();
+}
+
+TEST_F(NegativeDynamicRenderingLocalRead, PipelineBarrierWrongAttachmentLayout) {
+    TEST_DESCRIPTION("Using image barrier inside dynamic render pass instance with invalid attachment layout");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
+
+    VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
+    layout_transition.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    layout_transition.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    // This layout is supported by the pipelien barrier inside dynamic render pass instance
+    layout_transition.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    layout_transition.image = image;
+    layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+    color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {32, 32}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
+
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    m_command_buffer.Begin();
+
+    // Transition attachment to COLOR_ATTACHMENT_OPTIMAL layout
+    m_command_buffer.Barrier(layout_transition);
+
+    m_command_buffer.BeginRendering(rendering_info);
+    // Wrong layout: should be GENERAL or LOCAL_READ
+    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier2-image-09555");
+    m_command_buffer.Barrier(local_read_barrier);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
 }
 
 TEST_F(NegativeDynamicRenderingLocalRead, BeginWithinRenderPass) {
diff --git a/tests/unit/dynamic_rendering_local_read_positive.cpp b/tests/unit/dynamic_rendering_local_read_positive.cpp
index 8a896f304bd..e117e932ef2 100644
--- a/tests/unit/dynamic_rendering_local_read_positive.cpp
+++ b/tests/unit/dynamic_rendering_local_read_positive.cpp
@@ -228,3 +228,107 @@ TEST_F(PositiveDynamicRenderingLocalRead, CmdClearDepthAttachment) {
     m_command_buffer.EndRendering();
     m_command_buffer.End();
 }
+
+TEST_F(PositiveDynamicRenderingLocalRead, PipelineBarrierAllowedImageLayouts) {
+    TEST_DESCRIPTION("Test restriction on image layouts when using image barrier inside dynamic render pass instance");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
+
+    VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
+    layout_transition.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    layout_transition.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    layout_transition.newLayout = VK_IMAGE_LAYOUT_GENERAL;  // GENERAL is one of the allowed layouts
+    layout_transition.image = image;
+    layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+    color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {32, 32}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
+
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    m_command_buffer.Begin();
+
+    // Transition attachment to GENERAL layout
+    m_command_buffer.Barrier(layout_transition);
+
+    m_command_buffer.BeginRendering(rendering_info);
+    // Image barrier inside dynamic render pass instance
+    m_command_buffer.Barrier(local_read_barrier);
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
+}
+
+TEST_F(PositiveDynamicRenderingLocalRead, PipelineBarrierAllowedImageLayouts2) {
+    TEST_DESCRIPTION("Test restriction on image layouts when using image barrier inside dynamic render pass instance");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM,
+                     VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
+
+    VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
+    layout_transition.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    layout_transition.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    layout_transition.newLayout = VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ;  // LOCAL_READ is one of the allowed layouts
+    layout_transition.image = image;
+    layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ;
+    color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {32, 32}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
+
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+    // Layout transition is not allowed inside render pass instance.
+    // Set old and new layout to be equal to disable layout transition.
+    // Additionally use very spefic values (COLOR_ATTACHMENT_OPTIMAL) to test they are ignored.
+    local_read_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    local_read_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    m_command_buffer.Begin();
+
+    // Transition attachment to LOCAL_READ layout
+    m_command_buffer.Barrier(layout_transition);
+
+    m_command_buffer.BeginRendering(rendering_info);
+    // Image barrier inside dynamic render pass instance
+    m_command_buffer.Barrier(local_read_barrier);
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
+}


Reviews:
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T21:16:30Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T21:23:43Z
user: login: spencer-lunarg
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T22:20:06Z

Review comments:
diff_hunk: @@ -1783,41 +1752,19 @@ bool CoreChecks::ValidateImageBarrierAgainstImage(const vvl::CommandBuffer &cb_s
         }
     }
 
-    if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
-        // TODO: Set memory invalid which is in mem_tracker currently
-    } else if (!IsQueueFamilyExternal(barrier.srcQueueFamilyIndex)) {
+    const bool validate_barrier_layouts =
+        (old_layout != new_layout)                               // barrier defines layout transtion
+        && (old_layout != VK_IMAGE_LAYOUT_UNDEFINED)             // undefined allows any layout
+        && !IsQueueFamilyExternal(barrier.srcQueueFamilyIndex);  // do not validate layouts of external resources
+
+    if (validate_barrier_layouts) {
         skip |= VerifyImageBarrierLayouts(cb_state, image_state, image_loc, barrier, layout_updates_state);
     }
 
     const vvl::RenderPass *rp_state = cb_state.active_render_pass.get();
-    if (enabled_features.dynamicRenderingLocalRead && rp_state) {
-        const auto &rendering_info = rp_state->dynamic_rendering_begin_rendering_info;
-        std::vector<uint32_t> used_attachments(GetUsedAttachments(cb_state));
-
-        for (auto color_attachment_idx : used_attachments) {
-            if (color_attachment_idx >= rendering_info.colorAttachmentCount) {
-                continue;
-            }
-            const auto &color_attachment = rendering_info.pColorAttachments[color_attachment_idx];
-            if (color_attachment.imageView == VK_NULL_HANDLE) {
-                continue;
-            }
-            const auto image_view_state = Get<vvl::ImageView>(color_attachment.imageView);
-            ASSERT_AND_CONTINUE(image_view_state);
-            const auto &image_view_image_state = image_view_state->image_state;
-
-            if (image == image_view_image_state->VkHandle()) {
-                auto guard = image_view_image_state->layout_range_map->ReadLock();
path: layers/core_checks/cc_synchronization.cpp
user: login: artem-lunarg
body: The usage of the global layout map was the issue. It tracks queue submit state and can't be used to validate command buffer recording. Command buffer validation should use command buffer layout map.
created_at: 2025-03-27T21:16:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -477,7 +477,10 @@ TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierRequireFeature) {
     m_command_buffer.End();
 }
 
-TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayout) {
+// TODO: temporary disabled: the first layout transition in the command buffer can be validation only when submitted to the queue.
+// The original validation code was incorrect: ccan't use global image layout map (updated during submit time) to validate
+// record-time state.
+TEST_F(NegativeDynamicRenderingLocalRead, DISABLED_ImageBarrierInProperLayout) {
path: tests/unit/dynamic_rendering_local_read.cpp
user: login: artem-lunarg
body: This test should be rewritten. It's not possible to validate the first layout transition in the command buffer against current image layout state because such type of validation can be done only during submit time. Submit time validation is not implemented yet, but going to add it later. This test worked because it relied on the bug that was fixed.
created_at: 2025-03-27T21:23:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: ci-tester-lunarg
created_at: 2025-03-27T21:13:49Z
body: CI Vulkan-ValidationLayers build queued with queue ID 406912.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-03-27T21:15:04Z
body: CI Vulkan-ValidationLayers build # 19599 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-03-27T21:46:09Z
body: CI Vulkan-ValidationLayers build # 19599 passed.
author_association: COLLABORATOR