Repository: KhronosGroup/Vulkan-ValidationLayers
Pull request #10083: layers: Add submit validation of dynamic rendering barriers
Author: login: artem-lunarg
Created: 2025-05-08T22:26:57Z | Merged: 2025-05-09T10:41:43Z

This does submit time validation of VUIDs like `VUID-vkCmdPipelineBarrier2-image-09555` when it's not possible to do this at record time (command buffer did not specify layout earlier).



Diff:
diff --git a/layers/core_checks/cc_image_layout.cpp b/layers/core_checks/cc_image_layout.cpp
index 684ba4c2a64..7a843d9601d 100644
--- a/layers/core_checks/cc_image_layout.cpp
+++ b/layers/core_checks/cc_image_layout.cpp
@@ -21,8 +21,9 @@
 #include <vector>
 
 #include <vulkan/vk_enum_string_helper.h>
-#include "core_validation.h"
-#include "cc_vuid_maps.h"
+#include "core_checks/cc_state_tracker.h"
+#include "core_checks/cc_vuid_maps.h"
+#include "core_checks/core_validation.h"
 #include "error_message/error_strings.h"
 #include "generated/error_location_helper.h"
 #include "sync/sync_vuid_maps.h"
@@ -886,34 +887,16 @@ 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;
-
+static std::vector<uint32_t> GetUsedColorAttachments(const vvl::CommandBuffer &cb_state) {
+    std::vector<uint32_t> attachments;
+    attachments.reserve(cb_state.rendering_attachments.color_locations.size());
     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);
+        // VkRenderingAttachmentLocationInfo can make color attachment unused
+        // by setting output location value as VK_ATTACHMENT_UNUSED
+        if (cb_state.rendering_attachments.color_locations[i] != VK_ATTACHMENT_UNUSED) {
+            attachments.emplace_back(uint32_t(i));
         }
     }
-
-    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;
 }
 
@@ -921,7 +904,6 @@ bool CoreChecks::VerifyDynamicRenderingImageBarrierLayouts(const vvl::CommandBuf
                                                            const VkRenderingInfo &rendering_info,
                                                            const Location &barrier_loc) const {
     bool skip = false;
-    std::vector<uint32_t> used_attachments(GetUsedAttachments(cb_state));
 
     const CommandBufferImageLayoutMap &cb_layout_map = cb_state.GetImageLayoutMap();
     auto it = cb_layout_map.find(image_state.VkHandle());
@@ -930,16 +912,15 @@ bool CoreChecks::VerifyDynamicRenderingImageBarrierLayouts(const vvl::CommandBuf
     }
     ImageLayoutRegistry &cb_image_layouts = *it->second;
 
-    for (auto color_attachment_idx : used_attachments) {
+    for (auto color_attachment_idx : GetUsedColorAttachments(cb_state)) {
         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) {
+        const auto image_view_state = Get<vvl::ImageView>(color_attachment.imageView);
+        if (!image_view_state) {
             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(
@@ -981,6 +962,68 @@ bool CoreChecks::FindLayouts(const vvl::Image &image_state, std::vector<VkImageL
     return true;
 }
 
+void CoreChecks::EnqueueValidateDynamicRenderingImageBarrierLayouts(const Location barrier_loc, vvl::CommandBuffer &cb_state,
+                                                                    const ImageBarrier &image_barrier) {
+    if (!cb_state.active_render_pass || !cb_state.active_render_pass->UsesDynamicRendering()) {
+        return;
+    }
+    const VkRenderingInfo &rendering_info = *cb_state.active_render_pass->dynamic_rendering_begin_rendering_info.ptr();
+
+    const CommandBufferImageLayoutMap &cb_image_layout_map = cb_state.GetImageLayoutMap();
+    auto it = cb_image_layout_map.find(image_barrier.image);
+    std::shared_ptr<image_layout_map::ImageLayoutRegistry> image_layout_map =
+        (it != cb_image_layout_map.end()) ? it->second : nullptr;
+
+    auto &cb_sub_state = core::SubState(cb_state);
+
+    auto process_image_view = [&image_barrier, &image_layout_map, &cb_sub_state,
+                               &barrier_loc](const vvl::ImageView &image_view_state) {
+        // Skip attachments that use different image than a barrier
+        if (image_barrier.image != image_view_state.image_state->VkHandle()) {
+            return;
+        }
+        // Skip images that already have image layout specified so layout validation was done at record time
+        auto any_range_pred = [](const LayoutRange &, const LayoutEntry &) { return true; };
+        if (image_layout_map && image_layout_map->AnyInRange(image_view_state.normalized_subresource_range, any_range_pred)) {
+            return;
+        }
+        // Enqueue distinct subresource ranges for this image.
+        // Then during submit time the layouts of these subresources are validated against allowed values
+        auto &enqueued_subresources = cb_sub_state.submit_validate_dynamic_rendering_barrier_subresources[image_barrier.image];
+        auto it = std::find_if(enqueued_subresources.begin(), enqueued_subresources.end(), [&image_view_state](const auto &entry) {
+            return entry.first == image_view_state.normalized_subresource_range;
+        });
+        if (it == enqueued_subresources.end()) {
+            enqueued_subresources.emplace_back(
+                std::make_pair(image_view_state.normalized_subresource_range, vvl::LocationCapture(barrier_loc)));
+        }
+    };
+
+    for (auto color_attachment_idx : GetUsedColorAttachments(cb_state)) {
+        if (color_attachment_idx >= rendering_info.colorAttachmentCount) {
+            continue;
+        }
+        const auto &color_attachment = rendering_info.pColorAttachments[color_attachment_idx];
+        if (const auto image_view_state = Get<vvl::ImageView>(color_attachment.imageView)) {
+            process_image_view(*image_view_state);
+        }
+    }
+    if (rendering_info.pDepthAttachment) {
+        const AttachmentInfo &attachment =
+            cb_state.active_attachments[cb_state.GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Depth)];
+        if (attachment.image_view) {
+            process_image_view(*attachment.image_view);
+        }
+    }
+    if (rendering_info.pStencilAttachment) {
+        const AttachmentInfo &attachment =
+            cb_state.active_attachments[cb_state.GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Stencil)];
+        if (attachment.image_view) {
+            process_image_view(*attachment.image_view);
+        }
+    }
+}
+
 void CoreChecks::RecordTransitionImageLayout(vvl::CommandBuffer &cb_state, const ImageBarrier &mem_barrier) {
     if (enabled_features.synchronization2) {
         if (mem_barrier.oldLayout == mem_barrier.newLayout) {
diff --git a/layers/core_checks/cc_state_tracker.cpp b/layers/core_checks/cc_state_tracker.cpp
index 0167cbecfff..23addb8b9a3 100644
--- a/layers/core_checks/cc_state_tracker.cpp
+++ b/layers/core_checks/cc_state_tracker.cpp
@@ -20,13 +20,16 @@
 
 #include "cc_state_tracker.h"
 #include "core_validation.h"
+#include "error_message/error_strings.h"
+#include "state_tracker/image_state.h"
 #include "state_tracker/event_map.h"
+#include "sync/sync_vuid_maps.h"
 
 // Location to add per-queue submit debug info if built with -D DEBUG_CAPTURE_KEYBOARD=ON
 void CoreChecks::DebugCapture() {}
 
 void CoreChecks::Created(vvl::CommandBuffer& cb) {
-    cb.SetSubState(container_type, std::make_unique<core::CommandBufferSubState>(cb));
+    cb.SetSubState(container_type, std::make_unique<core::CommandBufferSubState>(cb, *this));
 }
 
 void CoreChecks::Created(vvl::Queue& queue) {
@@ -35,7 +38,10 @@ void CoreChecks::Created(vvl::Queue& queue) {
 
 namespace core {
 
-CommandBufferSubState::CommandBufferSubState(vvl::CommandBuffer& cb) : vvl::CommandBufferSubState(cb) { ResetCBState(); }
+CommandBufferSubState::CommandBufferSubState(vvl::CommandBuffer& cb, CoreChecks& validator)
+    : vvl::CommandBufferSubState(cb), validator(validator) {
+    ResetCBState();
+}
 
 void CommandBufferSubState::RecordWaitEvents(vvl::Func command, uint32_t eventCount, const VkEvent* pEvents,
                                              VkPipelineStageFlags2KHR srcStageMask) {
@@ -62,6 +68,9 @@ void CommandBufferSubState::ResetCBState() {
 
     // VK_EXT_nested_command_buffer
     nesting_level = 0;
+
+    // Submit time validation
+    submit_validate_dynamic_rendering_barrier_subresources.clear();
 }
 
 void CommandBufferSubState::ExecuteCommands(vvl::CommandBuffer& secondary_command_buffer) {
@@ -71,8 +80,51 @@ void CommandBufferSubState::ExecuteCommands(vvl::CommandBuffer& secondary_comman
     }
 }
 
+void CommandBufferSubState::SubmitTimeValidate() {
+    for (const auto& [image, subresources] : submit_validate_dynamic_rendering_barrier_subresources) {
+        const auto image_state = validator.Get<vvl::Image>(image);
+        if (!image_state) {
+            continue;
+        }
+        const auto global_layout_map = image_state->layout_range_map.get();
+        ASSERT_AND_CONTINUE(global_layout_map);
+        auto global_layout_map_guard = global_layout_map->ReadLock();
+
+        for (const std::pair<VkImageSubresourceRange, vvl::LocationCapture>& entry : subresources) {
+            const VkImageSubresourceRange& subresource = entry.first;
+            const Location& barrier_loc = entry.second.Get();
+            ImageLayoutRangeMap::RangeGenerator range_gen(image_state->subresource_encoder, subresource);
+            global_layout_map->AnyInRange(range_gen, [this, &barrier_loc, &image_state](const ImageLayoutRangeMap::key_type& range,
+                                                                                        const VkImageLayout& layout) {
+                if (layout != VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ && layout != VK_IMAGE_LAYOUT_GENERAL) {
+                    const auto& vuid = sync_vuid_maps::GetDynamicRenderingBarrierVUID(
+                        barrier_loc, sync_vuid_maps::DynamicRenderingBarrierError::kImageLayout);
+                    const LogObjectList objlist(base.Handle(), image_state->Handle());
+                    const Location& image_loc = barrier_loc.dot(vvl::Field::image);
+                    const VkImageSubresource subresource =
+                        static_cast<VkImageSubresource>(image_state->subresource_encoder.Decode(range.begin));
+                    return validator.LogError(vuid, objlist, image_loc, "(%s, %s) has layout %s.",
+                                              validator.FormatHandle(image_state->Handle()).c_str(),
+                                              string_VkImageSubresource(subresource).c_str(), string_VkImageLayout(layout));
+                }
+                return false;
+            });
+        }
+    }
+}
+
 QueueSubState::QueueSubState(Logger& logger, vvl::Queue& q) : vvl::QueueSubState(q), queue_submission_validator_(logger) {}
 
+void QueueSubState::PreSubmit(std::vector<vvl::QueueSubmission>& submissions) {
+    for (const auto& submission : submissions) {
+        for (auto& cb : submission.cb_submissions) {
+            auto guard = cb.cb->ReadLock();
+            CommandBufferSubState& cb_substate = SubState(*cb.cb);
+            cb_substate.SubmitTimeValidate();
+        }
+    }
+}
+
 void QueueSubState::Retire(vvl::QueueSubmission& submission) { queue_submission_validator_.Validate(submission); }
 
 }  // namespace core
\ No newline at end of file
diff --git a/layers/core_checks/cc_state_tracker.h b/layers/core_checks/cc_state_tracker.h
index bf93300fce1..083d0191dfd 100644
--- a/layers/core_checks/cc_state_tracker.h
+++ b/layers/core_checks/cc_state_tracker.h
@@ -23,6 +23,8 @@
 #include "state_tracker/cmd_buffer_state.h"
 #include "state_tracker/queue_state.h"
 
+class CoreChecks;
+
 namespace core {
 
 // CommandBuffer is over 3 times larger than the next largest state object struct, but the majority of the state is only used in
@@ -30,7 +32,7 @@ namespace core {
 // CommandBuffer object only for core and keep only the most basic items in the parent class
 class CommandBufferSubState : public vvl::CommandBufferSubState {
   public:
-    explicit CommandBufferSubState(vvl::CommandBuffer &cb);
+    CommandBufferSubState(vvl::CommandBuffer &cb, CoreChecks &validator);
 
     void RecordWaitEvents(vvl::Func command, uint32_t eventCount, const VkEvent* pEvents,
                           VkPipelineStageFlags2KHR src_stage_mask) override;
@@ -40,6 +42,10 @@ class CommandBufferSubState : public vvl::CommandBufferSubState {
 
     void ExecuteCommands(vvl::CommandBuffer &secondary_command_buffer) final;
 
+    void SubmitTimeValidate();
+
+    CoreChecks &validator;
+
     uint32_t nesting_level;  // VK_EXT_nested_command_buffer
 
     QFOTransferBarrierSets<QFOBufferTransferBarrier> qfo_transfer_buffer_barriers;
@@ -55,6 +61,10 @@ class CommandBufferSubState : public vvl::CommandBufferSubState {
     // currently need to hold in Command buffer because it can be a suspended renderpassss
     std::vector<VkOffset2D> fragment_density_offsets;
 
+    // The subresources from dynamic rendering barriers that can't be validated during record time.
+    vvl::unordered_map<VkImage, std::vector<std::pair<VkImageSubresourceRange, vvl::LocationCapture>>>
+        submit_validate_dynamic_rendering_barrier_subresources;
+
   private:
     void ResetCBState();
 };
@@ -66,11 +76,13 @@ static inline const CommandBufferSubState &SubState(const vvl::CommandBuffer &cb
     return *static_cast<const CommandBufferSubState *>(cb.SubState(LayerObjectTypeCoreValidation));
 }
 
-// Override Retire to validate submissions in the order defined by synchronization
 class QueueSubState : public vvl::QueueSubState {
   public:
     QueueSubState(Logger& logger, vvl::Queue& q);
 
+    void PreSubmit(std::vector<vvl::QueueSubmission> &submissions) override;
+
+    // Override Retire to validate submissions in the order defined by synchronization
     void Retire(vvl::QueueSubmission&) override;
 
   private:
diff --git a/layers/core_checks/cc_synchronization.cpp b/layers/core_checks/cc_synchronization.cpp
index 4cfc1c29519..b391893aae2 100644
--- a/layers/core_checks/cc_synchronization.cpp
+++ b/layers/core_checks/cc_synchronization.cpp
@@ -1966,8 +1966,8 @@ bool CoreChecks::ValidateImageBarrierAttachment(const Location &barrier_loc, con
     return skip;
 }
 
-void CoreChecks::EnqueueSubmitTimeValidateImageBarrierAttachment(const Location &loc, vvl::CommandBuffer &cb_state,
-                                                                 const ImageBarrier &barrier) {
+void CoreChecks::EnqueueValidateImageBarrierAttachment(const Location &loc, vvl::CommandBuffer &cb_state,
+                                                       const ImageBarrier &barrier) {
     // Secondary CBs can have null framebuffer so queue up validation in that case 'til FB is known
     const vvl::RenderPass *rp_state = cb_state.active_render_pass.get();
     if (rp_state && (VK_NULL_HANDLE == cb_state.active_framebuffer) && cb_state.IsSecondary()) {
@@ -2083,8 +2083,11 @@ void CoreChecks::RecordBarriers(Func func_name, vvl::CommandBuffer &cb_state, Vk
         Location barrier_loc(func_name, Struct::VkImageMemoryBarrier, Field::pImageMemoryBarriers, i);
         const ImageBarrier img_barrier(pImageMemBarriers[i], src_stage_mask, dst_stage_mask);
         RecordBarrierValidationInfo(barrier_loc, cb_state, img_barrier, cb_sub_state.qfo_transfer_image_barriers);
+        EnqueueValidateImageBarrierAttachment(barrier_loc, cb_state, img_barrier);
+        EnqueueValidateDynamicRenderingImageBarrierLayouts(barrier_loc, cb_state, img_barrier);
+
+        // Update layouts at the end. Submit time enqueuing logic above needs pre-update layout map.
         RecordTransitionImageLayout(cb_state, img_barrier);
-        EnqueueSubmitTimeValidateImageBarrierAttachment(barrier_loc, cb_state, img_barrier);
     }
 }
 
@@ -2099,8 +2102,11 @@ void CoreChecks::RecordBarriers(Func func_name, vvl::CommandBuffer &cb_state, co
         Location barrier_loc(func_name, Struct::VkImageMemoryBarrier2, Field::pImageMemoryBarriers, i);
         const ImageBarrier img_barrier(dep_info.pImageMemoryBarriers[i]);
         RecordBarrierValidationInfo(barrier_loc, cb_state, img_barrier, cb_sub_state.qfo_transfer_image_barriers);
+        EnqueueValidateImageBarrierAttachment(barrier_loc, cb_state, img_barrier);
+        EnqueueValidateDynamicRenderingImageBarrierLayouts(barrier_loc, cb_state, img_barrier);
+
+        // Update layouts at the end. Submit time enqueuing logic above needs pre-update layout map.
         RecordTransitionImageLayout(cb_state, img_barrier);
-        EnqueueSubmitTimeValidateImageBarrierAttachment(barrier_loc, cb_state, img_barrier);
     }
 }
 
diff --git a/layers/core_checks/core_validation.h b/layers/core_checks/core_validation.h
index 3e9e19a5480..cbf6f233503 100644
--- a/layers/core_checks/core_validation.h
+++ b/layers/core_checks/core_validation.h
@@ -321,8 +321,9 @@ class CoreChecks : public vvl::DeviceProxy {
     bool ValidatePipelineCacheControlFlags(VkPipelineCreateFlags2 flags, const Location& flags_loc, const char* vuid) const;
     bool ValidatePipelineIndirectBindableFlags(VkPipelineCreateFlags2 flags, const Location& flags_loc, const char* vuid) const;
     bool ValidatePipelineProtectedAccessFlags(VkPipelineCreateFlags2 flags, const Location& flags_loc) const;
-    void EnqueueSubmitTimeValidateImageBarrierAttachment(const Location& loc, vvl::CommandBuffer& cb_state,
-                                                         const ImageBarrier& barrier);
+    void EnqueueValidateImageBarrierAttachment(const Location& loc, vvl::CommandBuffer& cb_state, const ImageBarrier& barrier);
+    void EnqueueValidateDynamicRenderingImageBarrierLayouts(const Location barrier_loc, vvl::CommandBuffer& cb_state,
+                                                            const ImageBarrier& image_barrier);
     bool ValidateImageBarrierAttachment(const Location& barrier_loc, vvl::CommandBuffer const& cb_state,
                                         const vvl::Framebuffer& fb_state, uint32_t active_subpass,
                                         const vku::safe_VkSubpassDescription2& sub_desc, const VkRenderPass rp_handle,
diff --git a/tests/unit/dynamic_rendering_local_read.cpp b/tests/unit/dynamic_rendering_local_read.cpp
index 40a945f55cd..d54c7f4ff25 100644
--- a/tests/unit/dynamic_rendering_local_read.cpp
+++ b/tests/unit/dynamic_rendering_local_read.cpp
@@ -522,66 +522,234 @@ TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierLayoutMismatch) {
     m_command_buffer.End();
 }
 
-// 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");
+TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayout) {
+    TEST_DESCRIPTION("Barrier within a render pass instance started with vkCmdBeginRendering");
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    vkt::Image image(*m_device, 256, 256, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {256, 256}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
+
+    VkImageMemoryBarrier local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcAccessMask = VK_ACCESS_NONE;
+    local_read_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    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();
+    m_command_buffer.BeginRendering(rendering_info);
+    vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                           VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, nullptr, 1,
+                           &local_read_barrier);
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
+
+    image.SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier-image-09555");
+    m_default_queue->Submit(m_command_buffer);
+    m_errorMonitor->VerifyFound();
+    m_default_queue->Wait();
+}
+
+TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayout2) {
+    TEST_DESCRIPTION("Barrier within a render pass instance started with vkCmdBeginRendering");
     SetTargetApiVersion(VK_API_VERSION_1_3);
     AddRequiredFeature(vkt::Feature::synchronization2);
     RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
-    InitRenderTarget();
+
+    vkt::Image image(*m_device, 256, 256, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {256, 256}};
+    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_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
 
     m_command_buffer.Begin();
+    m_command_buffer.BeginRendering(rendering_info);
+    m_command_buffer.Barrier(local_read_barrier, VK_DEPENDENCY_BY_REGION_BIT);
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
 
-    vkt::Image image(*m_device, m_width, m_height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
-    vkt::ImageView imageView = image.CreateView();
+    image.SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier2-image-09555");
+    m_default_queue->Submit(m_command_buffer);
+    m_errorMonitor->VerifyFound();
+    m_default_queue->Wait();
+}
+
+TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayoutLateTransition) {
+    TEST_DESCRIPTION("Barrier within a render pass instance started with vkCmdBeginRendering");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    vkt::Image image(*m_device, 256, 256, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView();
 
     VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
     color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-    color_attachment.imageView = imageView;
 
-    VkRenderingInfo begin_rendering_info = vku::InitStructHelper();
-    VkClearRect clear_rect = {{{0, 0}, {m_width, m_height}}, 0, 1};
-    begin_rendering_info.renderArea = clear_rect.rect;
-    begin_rendering_info.layerCount = 1;
-    begin_rendering_info.colorAttachmentCount = 1;
-    begin_rendering_info.pColorAttachments = &color_attachment;
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {256, 256}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
 
-    VkImageMemoryBarrier img_barrier = vku::InitStructHelper();
-    img_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-    img_barrier.srcAccessMask = VK_ACCESS_NONE;
-    img_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-    img_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-    img_barrier.image = image;
-    img_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    img_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    img_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-    img_barrier.subresourceRange.baseArrayLayer = 0;
-    img_barrier.subresourceRange.baseMipLevel = 0;
-    img_barrier.subresourceRange.layerCount = 1;
-    img_barrier.subresourceRange.levelCount = 1;
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
 
-    m_command_buffer.BeginRendering(begin_rendering_info);
+    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;
+    layout_transition.image = image;
+    layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
 
-    auto img_barrier2 = ConvertVkImageMemoryBarrierToV2(img_barrier, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
-                                                        VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
+    m_command_buffer.Begin();
+    m_command_buffer.BeginRendering(rendering_info);
+    m_command_buffer.Barrier(local_read_barrier, VK_DEPENDENCY_BY_REGION_BIT);
+    m_command_buffer.EndRendering();
+
+    // Transition to allowed layout (VK_IMAGE_LAYOUT_GENERAL) but do this too late.
+    // This checks that state tracking is not confused by the final image layout which
+    // was set after rendering ended and not before.
+    m_command_buffer.Barrier(layout_transition);
+    m_command_buffer.End();
 
     m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier2-image-09555");
-    m_command_buffer.Barrier(*img_barrier2.ptr(), VK_DEPENDENCY_BY_REGION_BIT);
+    m_default_queue->Submit(m_command_buffer);
     m_errorMonitor->VerifyFound();
+    m_default_queue->Wait();
+}
 
-    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier-image-09555");
-    vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-                           VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, nullptr, 1,
-                           &img_barrier);
+TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayoutDepthImage) {
+    TEST_DESCRIPTION("Barrier within a render pass instance started with vkCmdBeginRendering");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    const VkFormat ds_format = FindSupportedDepthStencilFormat(Gpu());
+
+    vkt::Image image(*m_device, 256, 256, ds_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+    vkt::ImageView image_view = image.CreateView(VK_IMAGE_ASPECT_DEPTH_BIT);
+
+    VkRenderingAttachmentInfo depth_attachment = vku::InitStructHelper();
+    depth_attachment.imageView = image_view;
+    depth_attachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {256, 256}};
+    rendering_info.layerCount = 1;
+    rendering_info.pDepthAttachment = &depth_attachment;
+
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR;
+    local_read_barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR;
+    local_read_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
+
+    m_command_buffer.Begin();
+    m_command_buffer.BeginRendering(rendering_info);
+    m_command_buffer.Barrier(local_read_barrier, VK_DEPENDENCY_BY_REGION_BIT);
+    m_command_buffer.EndRendering();
+    m_command_buffer.End();
+
+    image.SetLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier2-image-09555");
+    m_default_queue->Submit(m_command_buffer);
     m_errorMonitor->VerifyFound();
+    m_default_queue->Wait();
+}
+
+TEST_F(NegativeDynamicRenderingLocalRead, ImageBarrierInProperLayoutMultipleSubresources) {
+    TEST_DESCRIPTION("Barrier within a render pass instance started with vkCmdBeginRendering");
+    SetTargetApiVersion(VK_API_VERSION_1_3);
+    AddRequiredFeature(vkt::Feature::synchronization2);
+    RETURN_IF_SKIP(InitBasicDynamicRenderingLocalRead());
+
+    const auto image_ci =
+        vkt::Image::ImageCreateInfo2D(256, 256, 2, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    vkt::Image image(*m_device, image_ci);
+    // Create view for mip 0
+    vkt::ImageView image_view = image.CreateView(VK_IMAGE_VIEW_TYPE_2D, 0, 1);
+
+    VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
+    color_attachment.imageView = image_view;
+    color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 
+    VkRenderingInfo rendering_info = vku::InitStructHelper();
+    rendering_info.renderArea = {{0, 0}, {256, 256}};
+    rendering_info.layerCount = 1;
+    rendering_info.colorAttachmentCount = 1;
+    rendering_info.pColorAttachments = &color_attachment;
+
+    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;
+    layout_transition.image = image;
+    // Select mip 1 for layout transition
+    layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, 0, 1};
+
+    VkImageMemoryBarrier2 local_read_barrier = vku::InitStructHelper();
+    local_read_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
+    local_read_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    local_read_barrier.image = image;
+    local_read_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+
+    m_command_buffer.Begin();
+    // Set VK_IMAGE_LAYOUT_GENERAL for mip 1
+    m_command_buffer.Barrier(layout_transition);
+    m_command_buffer.BeginRendering(rendering_info);
+    // mip 0 still has undefined layout
+    m_command_buffer.Barrier(local_read_barrier, VK_DEPENDENCY_BY_REGION_BIT);
     m_command_buffer.EndRendering();
     m_command_buffer.End();
-    m_default_queue->SubmitAndWait(m_command_buffer);
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdPipelineBarrier2-image-09555");
+    m_default_queue->Submit(m_command_buffer);
+    m_errorMonitor->VerifyFound();
+    m_default_queue->Wait();
 }
 
 TEST_F(NegativeDynamicRenderingLocalRead, PipelineBarrierWrongAttachmentLayout) {
@@ -597,7 +765,6 @@ TEST_F(NegativeDynamicRenderingLocalRead, PipelineBarrierWrongAttachmentLayout)
     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};


Reviews:
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T22:31:14Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T22:37:27Z
user: login: arno-lunarg
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T07:56:23Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T08:00:53Z

Review comments:
diff_hunk: @@ -886,42 +887,23 @@ bool CoreChecks::VerifyImageBarrierLayouts(const vvl::CommandBuffer &cb_state, c
     return skip;
 }
 
-static std::vector<uint32_t> GetUsedAttachments(const vvl::CommandBuffer &cb_state) {
path: layers/core_checks/cc_image_layout.cpp
user: login: artem-lunarg
body: Most of this function was broken. Shader  Output locations and Input attachment values are in different index space than attachment indices, they can't be mixed. Still the part that checks for VK_ATTACHMENT_UNUSED as part of `VkRenderingAttachmentLocationInfo` is useful.
created_at: 2025-05-08T22:31:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1972,8 +1972,8 @@ bool CoreChecks::ValidateImageBarrierAttachment(const Location &barrier_loc, con
     return skip;
 }
 
-void CoreChecks::EnqueueSubmitTimeValidateImageBarrierAttachment(const Location &loc, vvl::CommandBuffer &cb_state,
-                                                                 const ImageBarrier &barrier) {
+void CoreChecks::EnqueueValidateImageBarrierAttachment(const Location &loc, vvl::CommandBuffer &cb_state,
path: layers/core_checks/cc_synchronization.cpp
user: login: artem-lunarg
body: `EnqueueSubmitTimeValidate` prefix was a bit too long. Use `EnqueueValidate` assuming that the word `enqueue` suggests that validation is postponed (probably till submit time).
created_at: 2025-05-08T22:37:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,6 +61,10 @@ class CommandBufferSubState : public vvl::CommandBufferSubState {
     // currently need to hold in Command buffer because it can be a suspended renderpassss
     std::vector<VkOffset2D> fragment_density_offsets;
 
+    // The subresources from dynamic rendering barriers that can't be validated during record time.
+    vvl::unordered_map<VkImage, std::vector<std::pair<VkImageSubresourceRange, vvl::LocationCapture>>>
path: layers/core_checks/cc_state_tracker.h
user: login: arno-lunarg
body: Doesn't this belong more to `syncval_state::CommandBufferSubstate`?
created_at: 2025-05-09T07:54:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,6 +61,10 @@ class CommandBufferSubState : public vvl::CommandBufferSubState {
     // currently need to hold in Command buffer because it can be a suspended renderpassss
     std::vector<VkOffset2D> fragment_density_offsets;
 
+    // The subresources from dynamic rendering barriers that can't be validated during record time.
+    vvl::unordered_map<VkImage, std::vector<std::pair<VkImageSubresourceRange, vvl::LocationCapture>>>
path: layers/core_checks/cc_state_tracker.h
user: login: artem-lunarg
body: No, that's core check validation
created_at: 2025-05-09T08:00:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: ci-tester-lunarg
created_at: 2025-05-08T22:27:01Z
body: CI Vulkan-ValidationLayers build queued with queue ID 440031.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-08T22:30:01Z
body: CI Vulkan-ValidationLayers build # 20056 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-08T23:14:57Z
body: CI Vulkan-ValidationLayers build # 20056 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-09T09:52:51Z
body: CI Vulkan-ValidationLayers build queued with queue ID 440270.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-09T09:55:01Z
body: CI Vulkan-ValidationLayers build # 20061 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-09T10:41:35Z
body: CI Vulkan-ValidationLayers build # 20061 passed.
author_association: COLLABORATOR