Repository: KhronosGroup/Vulkan-ValidationLayers
Pull request #10059: layers: Improve 08600 error messages
Author: login: spencer-lunarg
Created: 2025-05-01T18:58:21Z | Merged: 2025-05-01T22:21:55Z

Tried to improve this VU in various ways

part of (maybe closes) https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9068


Diff:
diff --git a/layers/core_checks/cc_descriptor.cpp b/layers/core_checks/cc_descriptor.cpp
index 1257e654fad..60f607a0568 100644
--- a/layers/core_checks/cc_descriptor.cpp
+++ b/layers/core_checks/cc_descriptor.cpp
@@ -23,6 +23,8 @@
 
 #include "core_validation.h"
 #include "error_message/error_location.h"
+#include "generated/error_location_helper.h"
+#include "generated/vk_object_types.h"
 #include "state_tracker/descriptor_sets.h"
 #include "state_tracker/image_state.h"
 #include "state_tracker/buffer_state.h"
@@ -74,27 +76,29 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is also used, but we don't care which DSL is which
+bool CoreChecks::VerifyDescriptorSetLayoutIsCompatibile(const vvl::DescriptorSetLayout &reference_dsl,
+                                                        const vvl::DescriptorSetLayout &to_bind_dsl, std::string &error_msg) const {
     // Short circuit the detailed check.
-    if (layout_dsl.IsCompatible(&bound_dsl)) return true;
+    if (reference_dsl.IsCompatible(&to_bind_dsl)) {
+        return true;
+    }
 
     // Do a detailed compatibility check of this lhs def (referenced by layout_dsl), vs. the rhs (layout and def)
     // Should only be run if trivial accept has failed, and in that context should return false.
-    VkDescriptorSetLayout layout_dsl_handle = layout_dsl.VkHandle();
-    VkDescriptorSetLayout bound_dsl_handle = bound_dsl.VkHandle();
-    DescriptorSetLayoutDef const *layout_ds_layout_def = layout_dsl.GetLayoutDef();
-    DescriptorSetLayoutDef const *bound_ds_layout_def = bound_dsl.GetLayoutDef();
+    VkDescriptorSetLayout reference_dsl_handle = reference_dsl.VkHandle();
+    VkDescriptorSetLayout to_bind_dsl_handle = to_bind_dsl.VkHandle();
+    const DescriptorSetLayoutDef *reference_ds_layout_def = reference_dsl.GetLayoutDef();
+    const DescriptorSetLayoutDef *to_bind_ds_layout_def = to_bind_dsl.GetLayoutDef();
 
     // Check descriptor counts
-    const auto bound_total_count = bound_ds_layout_def->GetTotalDescriptorCount();
-    if (layout_ds_layout_def->GetTotalDescriptorCount() != bound_ds_layout_def->GetTotalDescriptorCount()) {
+    const auto bound_total_count = to_bind_ds_layout_def->GetTotalDescriptorCount();
+    if (reference_ds_layout_def->GetTotalDescriptorCount() != to_bind_ds_layout_def->GetTotalDescriptorCount()) {
         std::stringstream error_str;
-        error_str << FormatHandle(layout_dsl_handle) << " from pipeline layout has "
-                  << layout_ds_layout_def->GetTotalDescriptorCount() << " total descriptors, but " << FormatHandle(bound_dsl_handle)
-                  << ", which is bound, has " << bound_total_count << " total descriptors.";
+        error_str << FormatHandle(reference_dsl_handle) << " from pipeline layout has "
+                  << reference_ds_layout_def->GetTotalDescriptorCount() << " total descriptors, but "
+                  << FormatHandle(to_bind_dsl_handle) << ", trying to bind, has " << bound_total_count << " total descriptors";
         error_msg = error_str.str();
         return false;  // trivial fail case
     }
@@ -102,14 +106,14 @@ bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &la
     bool exception = false;
     // Descriptor counts match so need to go through bindings one-by-one
     //  and verify that type and stageFlags match
-    for (const auto &layout_binding : layout_ds_layout_def->GetBindings()) {
-        const auto bound_binding = bound_ds_layout_def->GetBindingInfoFromBinding(layout_binding.binding);
+    for (const auto &layout_binding : reference_ds_layout_def->GetBindings()) {
+        const auto bound_binding = to_bind_ds_layout_def->GetBindingInfoFromBinding(layout_binding.binding);
         if (layout_binding.descriptorCount != bound_binding->descriptorCount) {
             std::stringstream error_str;
-            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(layout_dsl_handle)
+            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(reference_dsl_handle)
                       << " from pipeline layout has a descriptorCount of " << layout_binding.descriptorCount << " but binding "
-                      << layout_binding.binding << " for " << FormatHandle(bound_dsl_handle)
-                      << ", which is bound, has a descriptorCount of " << bound_binding->descriptorCount;
+                      << layout_binding.binding << " for " << FormatHandle(to_bind_dsl_handle)
+                      << ", trying to bind, has a descriptorCount of " << bound_binding->descriptorCount;
             if (bound_binding->descriptorCount == 0 || layout_binding.descriptorCount == 0) {
                 error_str << " (descriptorCount value of zero likely means there is descriptor for binding "
                           << layout_binding.binding << ")";
@@ -118,30 +122,31 @@ bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &la
             return false;
         } else if (layout_binding.descriptorType != bound_binding->descriptorType) {
             std::stringstream error_str;
-            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(layout_dsl_handle)
+            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(reference_dsl_handle)
                       << " from pipeline layout is type " << string_VkDescriptorType(layout_binding.descriptorType)
-                      << " but binding " << layout_binding.binding << " for " << FormatHandle(bound_dsl_handle)
-                      << ", which is bound, is type " << string_VkDescriptorType(bound_binding->descriptorType) << "";
+                      << " but binding " << layout_binding.binding << " for " << FormatHandle(to_bind_dsl_handle)
+                      << ", trying to bind, is type " << string_VkDescriptorType(bound_binding->descriptorType) << "";
             error_msg = error_str.str();
             return false;
         } else if (layout_binding.stageFlags != bound_binding->stageFlags) {
             std::stringstream error_str;
-            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(layout_dsl_handle)
+            error_str << "Binding " << layout_binding.binding << " for " << FormatHandle(reference_dsl_handle)
                       << " from pipeline layout has stageFlags " << string_VkShaderStageFlags(layout_binding.stageFlags)
-                      << " but binding " << layout_binding.binding << " for " << FormatHandle(bound_dsl_handle)
-                      << ", which is bound, has stageFlags " << string_VkShaderStageFlags(bound_binding->stageFlags);
+                      << " but binding " << layout_binding.binding << " for " << FormatHandle(to_bind_dsl_handle)
+                      << ", trying to bind, has stageFlags " << string_VkShaderStageFlags(bound_binding->stageFlags);
             error_msg = error_str.str();
             return false;
         } else if (!ImmutableSamplersAreEqual(*layout_binding.ptr(), *bound_binding, exception)) {
             error_msg = "Immutable samplers from binding " + std::to_string(layout_binding.binding) + " in pipeline layout " +
-                        FormatHandle(layout_dsl_handle) + " do not match the immutable samplers in the layout currently bound (" +
-                        FormatHandle(bound_dsl_handle) + ")";
+                        FormatHandle(reference_dsl_handle) +
+                        " do not match the immutable samplers in the layout currently bound (" + FormatHandle(to_bind_dsl_handle) +
+                        ")";
             return false;
         }
     }
 
-    const auto &ds_layout_flags = layout_ds_layout_def->GetBindingFlags();
-    const auto &bound_layout_flags = bound_ds_layout_def->GetBindingFlags();
+    const auto &ds_layout_flags = reference_ds_layout_def->GetBindingFlags();
+    const auto &bound_layout_flags = to_bind_ds_layout_def->GetBindingFlags();
     if (bound_layout_flags != ds_layout_flags) {
         std::stringstream error_str;
         assert(ds_layout_flags.size() == bound_layout_flags.size());
@@ -149,9 +154,10 @@ bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &la
         for (i = 0; i < ds_layout_flags.size(); i++) {
             if (ds_layout_flags[i] != bound_layout_flags[i]) break;
         }
-        error_str << FormatHandle(layout_dsl_handle) << " from pipeline layout does not have the same binding flags at binding "
-                  << i << " ( " << string_VkDescriptorBindingFlags(ds_layout_flags[i]) << " ) as " << FormatHandle(bound_dsl_handle)
-                  << " ( " << string_VkDescriptorBindingFlags(bound_layout_flags[i]) << " ), which is bound";
+        error_str << FormatHandle(reference_dsl_handle) << " from pipeline layout does not have the same binding flags at binding "
+                  << i << " ( " << string_VkDescriptorBindingFlags(ds_layout_flags[i]) << " ) as "
+                  << FormatHandle(to_bind_dsl_handle) << " ( " << string_VkDescriptorBindingFlags(bound_layout_flags[i])
+                  << " ), which is bound";
         error_msg = error_str.str();
         return false;
     }
@@ -164,29 +170,23 @@ bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &la
     return true;
 }
 
-// For given vvl::DescriptorSet, verify that its Set is compatible w/ the setLayout corresponding to
-// pipelineLayout[layoutIndex]
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSet &descriptor_set,
-                                              const std::vector<std::shared_ptr<vvl::DescriptorSetLayout const>> &set_layouts,
-                                              const VulkanTypedHandle &handle, const uint32_t layoutIndex,
-                                              std::string &error_msg) const {
-    size_t num_sets = set_layouts.size();
-    if (layoutIndex >= num_sets) {
-        std::stringstream error_str;
-        error_str << FormatHandle(handle) << ") only contains ";
-        if (num_sets == 1) {
-            error_str << "1 setLayout, corresponding to index 0";
-        } else {
-            error_str << num_sets << " setLayouts, corresponding to index from 0 to " << num_sets - 1;
-        }
-        error_str << ", but you're attempting to bind set to index " << layoutIndex;
-        error_msg = error_str.str();
-        return false;
+// For a given vkDescriptorSet, we take the list of DescriptorSetLayouts (ex, from a pipeline layout) and check if the DSL at
+// |index| is compatibile
+bool CoreChecks::VerifyDescriptorSetIsCompatibile(
+    const vvl::DescriptorSet &to_bind_descriptor_set,
+    const std::vector<std::shared_ptr<vvl::DescriptorSetLayout const>> &descriptor_set_layouts, const uint32_t index,
+    std::string &error_msg) const {
+    if (to_bind_descriptor_set.IsPushDescriptor()) {
+        return true;
     }
-    if (descriptor_set.IsPushDescriptor()) return true;
-    const auto *layout_node = set_layouts[layoutIndex].get();
-    if (layout_node) {
-        return VerifySetLayoutCompatibility(*layout_node, *descriptor_set.GetLayout(), error_msg);
+
+    if (index >= descriptor_set_layouts.size()) {
+        // This means there is an active set in the SPIR-V that was not in
+        // VkPipelineLayoutCreateInfo/VkShaderCreateInfoEXT::pSetLayouts which would have broke way before
+        assert(false);
+        return true;
+    } else if (const auto *layout_node = descriptor_set_layouts[index].get()) {
+        return VerifyDescriptorSetLayoutIsCompatibile(*layout_node, *to_bind_descriptor_set.GetLayout(), error_msg);
     } else {
         // It's possible the DSL is null when creating a graphics pipeline library, in which case we can't verify compatibility
         // here.
@@ -201,7 +201,7 @@ bool CoreChecks::VerifyPipelineLayoutCompatibility(const vvl::PipelineLayout &la
         const auto ds_a = layout_a.set_layouts[i];
         const auto ds_b = layout_b.set_layouts[i];
         if (ds_a && ds_b) {
-            if (!VerifySetLayoutCompatibility(*ds_a, *ds_b, error_msg)) {
+            if (!VerifyDescriptorSetLayoutIsCompatibile(*ds_a, *ds_b, error_msg)) {
                 return false;
             }
         }
@@ -224,7 +224,7 @@ bool CoreChecks::VerifyPipeleinLayoutCompatibilityUnion(const vvl::PipelineLayou
             ds_b = fs_layout.set_layouts[i];
         }
         if (ds_a && ds_b) {
-            if (!VerifySetLayoutCompatibility(*ds_a, *ds_b, error_msg)) {
+            if (!VerifyDescriptorSetLayoutIsCompatibile(*ds_a, *ds_b, error_msg)) {
                 return false;
             }
         }
@@ -233,40 +233,52 @@ bool CoreChecks::VerifyPipeleinLayoutCompatibilityUnion(const vvl::PipelineLayou
 }
 
 bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_state, VkPipelineLayout layout, uint32_t firstSet,
-                                               uint32_t setCount, const VkDescriptorSet *pDescriptorSets,
+                                               uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets,
                                                uint32_t dynamicOffsetCount, const uint32_t *pDynamicOffsets,
                                                const Location &loc) const {
     bool skip = false;
     const bool is_2 = loc.function != Func::vkCmdBindDescriptorSets;
 
     auto pipeline_layout = Get<vvl::PipelineLayout>(layout);
-    if (!pipeline_layout) return skip;  // dynamicPipelineLayout feature
+    if (!pipeline_layout) {
+        return skip;  // dynamicPipelineLayout feature
+    }
 
     // Track total count of dynamic descriptor types to make sure we have an offset for each one
     uint32_t total_dynamic_descriptors = 0;
 
-    for (uint32_t set_idx = 0; set_idx < setCount; set_idx++) {
+    // If we detect we are binding to many sets, the extra sets will always be incompatible, so check first
+    if ((firstSet + descriptorSetCount) > static_cast<uint32_t>(pipeline_layout->set_layouts.size())) {
+        const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-firstSet-00360" : "VUID-vkCmdBindDescriptorSets-firstSet-00360";
+        const LogObjectList objlist(cb_state.Handle(), layout);
+        skip |= LogError(vuid, objlist, loc.dot(Field::firstSet),
+                         "(%" PRIu32 ") plus descriptorSetCount (%" PRIu32
+                         ") is greater than VkPipelineLayoutCreateInfo::setLayoutCount (%zu) when pipeline layout was created",
+                         firstSet, descriptorSetCount, pipeline_layout->set_layouts.size());
+        return skip;
+    }
+
+    for (uint32_t set_idx = 0; set_idx < descriptorSetCount; set_idx++) {
         const Location set_loc = loc.dot(Field::pDescriptorSets, set_idx);
-        if (auto descriptor_set = Get<vvl::DescriptorSet>(pDescriptorSets[set_idx])) {
+        const VkDescriptorSet set_handle = pDescriptorSets[set_idx];
+        if (auto descriptor_set = Get<vvl::DescriptorSet>(set_handle)) {
             // Verify that set being bound is compatible with overlapping setLayout of pipelineLayout
             std::string error_string = "";
-            if (!VerifySetLayoutCompatibility(*descriptor_set, pipeline_layout->set_layouts, pipeline_layout->Handle(),
-                                              set_idx + firstSet, error_string)) {
-                const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx]);
+            if (!VerifyDescriptorSetIsCompatibile(*descriptor_set, pipeline_layout->set_layouts, set_idx + firstSet,
+                                                  error_string)) {
+                const LogObjectList objlist(cb_state.Handle(), pipeline_layout->Handle(), set_handle);
                 const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-00358"
                                         : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358";
                 skip |= LogError(vuid, objlist, set_loc,
-                                 "(%s) being bound is not compatible with overlapping "
-                                 "descriptorSetLayout at index %" PRIu32
-                                 " of "
-                                 "%s due to: %s.",
-                                 FormatHandle(pDescriptorSets[set_idx]).c_str(), set_idx + firstSet, FormatHandle(layout).c_str(),
+                                 "(%s) being bound is not compatible with the corresponding "
+                                 "VkPipelineLayoutCreateInfo::pSetLayouts[%" PRIu32 "] of %s due to:\n%s.",
+                                 FormatHandle(set_handle).c_str(), set_idx + firstSet, FormatHandle(layout).c_str(),
                                  error_string.c_str());
             }
 
             const auto &dsl = descriptor_set->GetLayout();
             if (dsl->GetCreateFlags() & VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT) {
-                const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx], dsl->VkHandle());
+                const LogObjectList objlist(cb_state.Handle(), set_handle, dsl->VkHandle());
                 const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-08010"
                                         : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-08010";
                 skip |= LogError(vuid, objlist, set_loc, "was allocated from VkDescriptorSetLayout with %s flags.",
@@ -278,7 +290,7 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
                 // First make sure we won't overstep bounds of pDynamicOffsets array
                 if ((total_dynamic_descriptors + set_dynamic_descriptor_count) > dynamicOffsetCount) {
                     // Test/report this here, such that we don't run past the end of pDynamicOffsets in the else clause
-                    const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx]);
+                    const LogObjectList objlist(cb_state.Handle(), set_handle);
                     const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-dynamicOffsetCount-00359"
                                             : "VUID-vkCmdBindDescriptorSets-dynamicOffsetCount-00359";
                     skip |=
@@ -287,7 +299,7 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
                                  " "
                                  "dynamicOffsets are left in "
                                  "pDynamicOffsets array. There must be one dynamic offset for each dynamic descriptor being bound.",
-                                 FormatHandle(pDescriptorSets[set_idx]).c_str(), descriptor_set->GetDynamicDescriptorCount(),
+                                 FormatHandle(set_handle).c_str(), descriptor_set->GetDynamicDescriptorCount(),
                                  (dynamicOffsetCount - total_dynamic_descriptors));
                     // Set the number found to the maximum to prevent duplicate messages, or subsquent descriptor sets from
                     // testing against the "short tail" we're skipping below.
@@ -355,8 +367,7 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
 
                                 // Validate offset didn't go over buffer
                                 if ((bound_range == VK_WHOLE_SIZE) && (offset > 0)) {
-                                    const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx],
-                                                                buffer_descriptor->GetBuffer());
+                                    const LogObjectList objlist(cb_state.Handle(), set_handle, buffer_descriptor->GetBuffer());
                                     const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-06715"
                                                             : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-06715";
                                     skip |= LogError(vuid, objlist, loc.dot(Field::pDynamicOffsets, cur_dyn_offset),
@@ -370,8 +381,7 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
 
                                 } else if (buffer_state && (bound_range != VK_WHOLE_SIZE) &&
                                            ((offset + bound_range + bound_offset) > buffer_state->create_info.size)) {
-                                    const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx],
-                                                                buffer_descriptor->GetBuffer());
+                                    const LogObjectList objlist(cb_state.Handle(), set_handle, buffer_descriptor->GetBuffer());
                                     const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-01979"
                                                             : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-01979";
                                     skip |=
@@ -393,20 +403,20 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
             }
             auto ds_pool_state = descriptor_set->GetPoolState();
             if (ds_pool_state && ds_pool_state->create_info.flags & VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT) {
-                const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx], ds_pool_state->Handle());
+                const LogObjectList objlist(cb_state.Handle(), set_handle, ds_pool_state->Handle());
                 const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-04616"
                                         : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-04616";
                 skip |= LogError(vuid, objlist, set_loc,
                                  "was allocated from a pool that was created with VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT.");
             }
         } else if (!enabled_features.graphicsPipelineLibrary) {
-            const LogObjectList objlist(cb_state.Handle(), pDescriptorSets[set_idx]);
+            const LogObjectList objlist(cb_state.Handle(), set_handle);
             const char *vuid =
                 is_2 ? "VUID-VkBindDescriptorSetsInfo-pDescriptorSets-06563" : "VUID-vkCmdBindDescriptorSets-pDescriptorSets-06563";
             skip |= LogError(vuid, objlist, set_loc,
                              "(%s) does not exist, and the pipeline layout was not created "
                              "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT.",
-                             FormatHandle(pDescriptorSets[set_idx]).c_str());
+                             FormatHandle(set_handle).c_str());
         }
     }
     //  dynamicOffsetCount must equal the total number of dynamic descriptors in the sets being bound
@@ -415,34 +425,22 @@ bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_stat
                                 : "VUID-vkCmdBindDescriptorSets-dynamicOffsetCount-00359";
         skip |= LogError(vuid, cb_state.Handle(), loc,
                          "Attempting to bind %" PRIu32 " descriptorSets with %" PRIu32
-                         " dynamic descriptors, but "
-                         "dynamicOffsetCount is %" PRIu32
-                         ". It should "
-                         "exactly match the number of dynamic descriptors.",
-                         setCount, total_dynamic_descriptors, dynamicOffsetCount);
-    }
-    // firstSet and descriptorSetCount sum must be less than setLayoutCount
-    if ((firstSet + setCount) > static_cast<uint32_t>(pipeline_layout->set_layouts.size())) {
-        const char *vuid = is_2 ? "VUID-VkBindDescriptorSetsInfo-firstSet-00360" : "VUID-vkCmdBindDescriptorSets-firstSet-00360";
-        skip |= LogError(vuid, cb_state.Handle(), loc,
-                         "Sum of firstSet (%" PRIu32 ") and descriptorSetCount (%" PRIu32
-                         ") is greater than "
-                         "VkPipelineLayoutCreateInfo::setLayoutCount "
-                         "(%zu) when pipeline layout was created",
-                         firstSet, setCount, pipeline_layout->set_layouts.size());
+                         " dynamic descriptors, but dynamicOffsetCount is %" PRIu32
+                         ". It should exactly match the number of dynamic descriptors.",
+                         descriptorSetCount, total_dynamic_descriptors, dynamicOffsetCount);
     }
 
     return skip;
 }
 
 bool CoreChecks::PreCallValidateCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
-                                                      VkPipelineLayout layout, uint32_t firstSet, uint32_t setCount,
+                                                      VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount,
                                                       const VkDescriptorSet *pDescriptorSets, uint32_t dynamicOffsetCount,
                                                       const uint32_t *pDynamicOffsets, const ErrorObject &error_obj) const {
     auto cb_state = GetRead<vvl::CommandBuffer>(commandBuffer);
     bool skip = false;
     skip |= ValidateCmd(*cb_state, error_obj.location);
-    skip |= ValidateCmdBindDescriptorSets(*cb_state, layout, firstSet, setCount, pDescriptorSets, dynamicOffsetCount,
+    skip |= ValidateCmdBindDescriptorSets(*cb_state, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount,
                                           pDynamicOffsets, error_obj.location);
     skip |= ValidatePipelineBindPoint(*cb_state, pipelineBindPoint, error_obj.location);
 
diff --git a/layers/core_checks/cc_drawdispatch.cpp b/layers/core_checks/cc_drawdispatch.cpp
index 1ddb8443391..79a8130f9a3 100644
--- a/layers/core_checks/cc_drawdispatch.cpp
+++ b/layers/core_checks/cc_drawdispatch.cpp
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+#include <vulkan/vk_enum_string_helper.h>
 #include "drawdispatch/drawdispatch_vuids.h"
 #include "core_validation.h"
 #include "generated/vk_extension_helper.h"
@@ -27,6 +28,7 @@
 #include "state_tracker/shader_module.h"
 #include "state_tracker/cmd_buffer_state.h"
 #include "state_tracker/pipeline_state.h"
+#include "utils/vk_layer_utils.h"
 
 using vvl::DrawDispatchVuid;
 using vvl::GetDrawDispatchVuid;
@@ -1374,12 +1376,26 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
     }
 
     // Check if the current pipeline is compatible for the maximum used set with the bound sets.
-    if (pipeline.descriptor_buffer_mode) return skip;
+    if (pipeline.descriptor_buffer_mode) {
+        return skip;
+    }
 
     const auto pipeline_layout = pipeline.PipelineLayoutState();
     if (!pipeline.active_slots.empty() && !last_bound_state.IsBoundSetCompatible(pipeline.max_active_slot, *pipeline_layout)) {
+        // If they never bound any descriptors
+        if (!last_bound_state.desc_set_pipeline_layout) {
+            skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                             "The %s statically uses descriptor set %" PRIu32
+                             ", but because a descriptor was never bound, the pipeline layouts are not compatible.\nIf using a "
+                             "descriptor, make sure to call one of vkCmdBindDescriptorSets, vkCmdPushDescriptorSet, "
+                             "vkCmdSetDescriptorBufferOffset, etc for %s",
+                             FormatHandle(pipeline).c_str(), pipeline.max_active_slot, string_VkPipelineBindPoint(bind_point));
+            return skip;
+        }
+
         LogObjectList objlist(pipeline.Handle());
         const auto layouts = pipeline.PipelineLayoutStateUnion();
+        // GPL can have multiple pipeline layouts used to build up a single valid compatible set
         std::ostringstream pipe_layouts_log;
         if (layouts.size() > 1) {
             pipe_layouts_log << "a union of layouts [ ";
@@ -1391,13 +1407,8 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
         } else {
             pipe_layouts_log << FormatHandle(*layouts.front());
         }
-        std::string pipeline_layout_handle_str;
-        if (last_bound_state.desc_set_pipeline_layout) {
-            pipeline_layout_handle_str = FormatHandle(last_bound_state.desc_set_pipeline_layout->Handle());
-            objlist.add(last_bound_state.desc_set_pipeline_layout->Handle());
-        } else {
-            pipeline_layout_handle_str = "Pipeline Layout never bound";  // < can happen when dealing with multiview
-        }
+
+        objlist.add(last_bound_state.desc_set_pipeline_layout->Handle());
 
         std::string range =
             pipeline.max_active_slot == 0 ? "set 0 is" : "all sets 0 to " + std::to_string(pipeline.max_active_slot) + " are";
@@ -1405,7 +1416,8 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
                          "The %s (created with %s) statically uses descriptor set %" PRIu32
                          ", but %s not compatible with the pipeline layout bound with %s (%s)\n%s",
                          FormatHandle(pipeline).c_str(), pipe_layouts_log.str().c_str(), pipeline.max_active_slot, range.c_str(),
-                         String(last_bound_state.desc_set_bound_command), pipeline_layout_handle_str.c_str(),
+                         String(last_bound_state.desc_set_bound_command),
+                         FormatHandle(last_bound_state.desc_set_pipeline_layout->Handle()).c_str(),
                          last_bound_state.DescribeNonCompatibleSet(pipeline.max_active_slot, *pipeline_layout).c_str());
     } else {
         // if the bound set is not compatible, the rest will just be extra redundant errors
@@ -1414,17 +1426,16 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
             const auto ds_slot = last_bound_state.ds_slots[set_index];
             if (!ds_slot.ds_state) {
                 skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
-                                 "%s uses set #%" PRIu32
+                                 "%s uses set %" PRIu32
                                  " but that set is not bound. (Need to use a command like vkCmdBindDescriptorSets to bind the set)",
                                  FormatHandle(pipeline).c_str(), set_index);
-            } else if (!VerifySetLayoutCompatibility(*ds_slot.ds_state, pipeline_layout->set_layouts, pipeline_layout->Handle(),
-                                                     set_index, error_string)) {
-                // Set is bound but not compatible w/ overlapping pipeline_layout from PSO
+            } else if (!VerifyDescriptorSetIsCompatibile(*ds_slot.ds_state, pipeline_layout->set_layouts, set_index,
+                                                         error_string)) {
+                // Set is bound but not compatible w/ corresponding VkPipelineLayoutCreateInfo::pSetLayouts
                 VkDescriptorSet set_handle = ds_slot.ds_state->VkHandle();
-                LogObjectList objlist = cb_state.GetObjectList(bind_point);
-                objlist.add(set_handle);
+                const LogObjectList objlist(cb_state.Handle(), set_handle, pipeline.Handle(), pipeline_layout->Handle());
                 skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                                 "%s bound as set #%" PRIu32 " is not compatible with overlapping %s due to: %s",
+                                 "%s bound as set %" PRIu32 " is not compatible with corresponding %s\n%s",
                                  FormatHandle(set_handle).c_str(), set_index, FormatHandle(*pipeline_layout).c_str(),
                                  error_string.c_str());
             } else {  // Valid set is bound and layout compatible, validate that it's updated
@@ -1450,16 +1461,37 @@ bool CoreChecks::ValidateActionStateDescriptorsShaderObject(const LastBound &las
 
     // Check if the current shader objects are compatible for the maximum used set with the bound sets.
     for (const auto &shader_state : last_bound_state.shader_object_states) {
-        if (!shader_state) continue;
+        if (!shader_state) {
+            continue;
+        }
 
-        if (shader_state && !shader_state->active_slots.empty() &&
+        if (!shader_state->active_slots.empty() &&
             !last_bound_state.IsBoundSetCompatible(shader_state->max_active_slot, *shader_state)) {
             LogObjectList objlist(cb_state.Handle(), shader_state->Handle());
-            skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                             "The %s statically uses descriptor set (index #%" PRIu32
-                             ") which is not compatible with the currently bound descriptor set's layout\n%s",
-                             FormatHandle(shader_state->Handle()).c_str(), shader_state->max_active_slot,
-                             last_bound_state.DescribeNonCompatibleSet(shader_state->max_active_slot, *shader_state).c_str());
+
+            if (!last_bound_state.desc_set_pipeline_layout) {
+                // If they never bound any descriptors
+                skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                                 "The %s statically uses descriptor set %" PRIu32
+                                 ", but because a descriptor was never bound, the pipeline layouts are not compatible.\nIf using "
+                                 "a descriptor, make sure to call one of vkCmdBindDescriptorSets, vkCmdPushDescriptorSet, "
+                                 "vkCmdSetDescriptorBufferOffset, etc for %s",
+                                 FormatHandle(shader_state->Handle()).c_str(), shader_state->max_active_slot,
+                                 string_VkPipelineBindPoint(bind_point));
+
+            } else {
+                objlist.add(last_bound_state.desc_set_pipeline_layout->Handle());
+                std::string range = shader_state->max_active_slot == 0
+                                        ? "set 0 is"
+                                        : "all sets 0 to " + std::to_string(shader_state->max_active_slot) + " are";
+                skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
+                                 "The %s statically uses descriptor set %" PRIu32
+                                 " but %s not compatible with the pipeline layout bound with %s (%s)\n%s",
+                                 FormatHandle(shader_state->Handle()).c_str(), shader_state->max_active_slot, range.c_str(),
+                                 String(last_bound_state.desc_set_bound_command),
+                                 FormatHandle(last_bound_state.desc_set_pipeline_layout->Handle()).c_str(),
+                                 last_bound_state.DescribeNonCompatibleSet(shader_state->max_active_slot, *shader_state).c_str());
+            }
         } else {
             // if the bound set is not copmatible, the rest will just be extra redundant errors
             for (const auto &[set_index, binding_req_map] : shader_state->active_slots) {
@@ -1468,15 +1500,15 @@ bool CoreChecks::ValidateActionStateDescriptorsShaderObject(const LastBound &las
                 if (!ds_slot.ds_state) {
                     const LogObjectList objlist(cb_state.Handle(), shader_state->Handle());
                     skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                                     "%s uses set #%" PRIu32 " but that set is not bound.",
+                                     "%s uses set %" PRIu32 " but that set is not bound.",
                                      FormatHandle(shader_state->Handle()).c_str(), set_index);
-                } else if (!VerifySetLayoutCompatibility(*ds_slot.ds_state, shader_state->set_layouts, shader_state->Handle(),
-                                                         set_index, error_string)) {
-                    // Set is bound but not compatible w/ overlapping pipeline_layout from PSO
+                } else if (!VerifyDescriptorSetIsCompatibile(*ds_slot.ds_state, shader_state->set_layouts, set_index,
+                                                             error_string)) {
+                    // Set is bound but not compatible w/ corresponding VkShaderCreateInfoEXT::pSetLayouts
                     VkDescriptorSet set_handle = ds_slot.ds_state->VkHandle();
                     const LogObjectList objlist(cb_state.Handle(), set_handle, shader_state->Handle());
                     skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                                     "%s bound as set #%" PRIu32 " is not compatible with overlapping %s due to: %s",
+                                     "%s bound as set %" PRIu32 " is not compatible with corresponding %s\n%s",
                                      FormatHandle(set_handle).c_str(), set_index, FormatHandle(shader_state->Handle()).c_str(),
                                      error_string.c_str());
                 } else {  // Valid set is bound and layout compatible, validate that it's updated
diff --git a/layers/core_checks/core_validation.h b/layers/core_checks/core_validation.h
index 58d9a93b909..6d962cdf80c 100644
--- a/layers/core_checks/core_validation.h
+++ b/layers/core_checks/core_validation.h
@@ -742,12 +742,12 @@ class CoreChecks : public vvl::DeviceProxy {
 
     bool ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding& b1, const VkDescriptorSetLayoutBinding& b2,
                                    bool& out_exception) const;
-    bool VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout& layout_dsl,
-                                      const vvl::DescriptorSetLayout& bound_dsl, std::string& error_msg) const;
-
-    bool VerifySetLayoutCompatibility(const vvl::DescriptorSet& descriptor_set,
-                                      const std::vector<std::shared_ptr<vvl::DescriptorSetLayout const>>& set_layouts,
-                                      const VulkanTypedHandle& handle, const uint32_t layoutIndex, std::string& error_msg) const;
+    bool VerifyDescriptorSetLayoutIsCompatibile(const vvl::DescriptorSetLayout& reference_dsl,
+                                                const vvl::DescriptorSetLayout& to_bind_dsl, std::string& error_msg) const;
+    bool VerifyDescriptorSetIsCompatibile(
+        const vvl::DescriptorSet& to_bind_descriptor_set,
+        const std::vector<std::shared_ptr<vvl::DescriptorSetLayout const>>& descriptor_set_layouts, const uint32_t index,
+        std::string& error_msg) const;
 
     bool VerifyPipelineLayoutCompatibility(const vvl::PipelineLayout& layout_a, const vvl::PipelineLayout& layout_b,
                                            std::string& error_msg) const;
@@ -1763,11 +1763,11 @@ class CoreChecks : public vvl::DeviceProxy {
                                                const ErrorObject& error_obj) const override;
     bool PreCallValidateCmdSetStencilReference(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t reference,
                                                const ErrorObject& error_obj) const override;
-    bool ValidateCmdBindDescriptorSets(const vvl::CommandBuffer&, VkPipelineLayout layout, uint32_t firstSet, uint32_t setCount,
-                                       const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount,
-                                       const uint32_t* pDynamicOffsets, const Location& loc) const;
+    bool ValidateCmdBindDescriptorSets(const vvl::CommandBuffer&, VkPipelineLayout layout, uint32_t firstSet,
+                                       uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets,
+                                       uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets, const Location& loc) const;
     bool PreCallValidateCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
-                                              VkPipelineLayout layout, uint32_t firstSet, uint32_t setCount,
+                                              VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount,
                                               const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount,
                                               const uint32_t* pDynamicOffsets, const ErrorObject& error_obj) const override;
     bool PreCallValidateCmdBindDescriptorSets2(VkCommandBuffer commandBuffer,
diff --git a/layers/state_tracker/cmd_buffer_state.cpp b/layers/state_tracker/cmd_buffer_state.cpp
index faf908d09d9..b5042015979 100644
--- a/layers/state_tracker/cmd_buffer_state.cpp
+++ b/layers/state_tracker/cmd_buffer_state.cpp
@@ -1316,7 +1316,7 @@ void CommandBuffer::UpdateLastBoundDescriptorSets(VkPipelineBindPoint pipeline_b
 
     uint32_t required_size = first_set + set_count;
     const uint32_t last_binding_index = required_size - 1;
-    assert(last_binding_index < pipeline_layout->set_compat_ids.size());
+    ASSERT_AND_RETURN(last_binding_index < pipeline_layout->set_compat_ids.size());
 
     // Some useful shorthand
     const auto lv_bind_point = ConvertToLvlBindPoint(pipeline_bind_point);
diff --git a/layers/state_tracker/last_bound_state.cpp b/layers/state_tracker/last_bound_state.cpp
index a2562c160ca..54eba8786a1 100644
--- a/layers/state_tracker/last_bound_state.cpp
+++ b/layers/state_tracker/last_bound_state.cpp
@@ -582,7 +582,9 @@ bool LastBound::IsBoundSetCompatible(uint32_t set, const vvl::ShaderObject &shad
 std::string LastBound::DescribeNonCompatibleSet(uint32_t set, const vvl::PipelineLayout &pipeline_layout) const {
     std::ostringstream ss;
     if (set >= ds_slots.size()) {
-        ss << "The set (" << set << ") is out of bounds for the number of sets bound (" << ds_slots.size() << ")\n";
+        ss << "The set (" << set << ") is out of bounds for the number of sets bound (" << ds_slots.size()
+           << ")\nHint: Make sure the previous calls to " << String(desc_set_bound_command)
+           << " has all sets bound that are needed.";
     } else if (set >= pipeline_layout.set_compat_ids.size()) {
         ss << "The set (" << set << ") is out of bounds for the number of sets in the non-compatible VkPipelineLayout ("
            << pipeline_layout.set_compat_ids.size() << ")\n";
@@ -595,7 +597,9 @@ std::string LastBound::DescribeNonCompatibleSet(uint32_t set, const vvl::Pipelin
 std::string LastBound::DescribeNonCompatibleSet(uint32_t set, const vvl::ShaderObject &shader_object_state) const {
     std::ostringstream ss;
     if (set >= ds_slots.size()) {
-        ss << "The set (" << set << ") is out of bounds for the number of sets bound (" << ds_slots.size() << ")\n";
+        ss << "The set (" << set << ") is out of bounds for the number of sets bound (" << ds_slots.size()
+           << ")\nHint: Make sure the previous calls to " << String(desc_set_bound_command)
+           << " has all sets bound that are needed.";
     } else if (set >= shader_object_state.set_compat_ids.size()) {
         ss << "The set (" << set << ") is out of bounds for the number of sets in the non-compatible VkDescriptorSetLayout ("
            << shader_object_state.set_compat_ids.size() << ")\n";
diff --git a/tests/unit/descriptors.cpp b/tests/unit/descriptors.cpp
index 6cf0a7ba40c..27aef14a824 100644
--- a/tests/unit/descriptors.cpp
+++ b/tests/unit/descriptors.cpp
@@ -346,44 +346,135 @@ TEST_F(NegativeDescriptors, DescriptorPool) {
     m_errorMonitor->VerifyFound();
 }
 
-TEST_F(NegativeDescriptors, DescriptorSet) {
-    // Attempt to bind an invalid Descriptor Set to a valid Command Buffer
-    // ObjectTracker should catch this.
-    // Create a valid cmd buffer
-    // call vk::CmdBindDescriptorSets w/ false Descriptor Set
+TEST_F(NegativeDescriptors, BindInvalidDescriptorSet) {
     RETURN_IF_SKIP(Init());
-
     constexpr uint64_t fake_set_handle = 0xbaad6001;
     VkDescriptorSet bad_set = CastFromUint64<VkDescriptorSet>(fake_set_handle);
 
-    VkDescriptorSetLayoutBinding layout_binding = {};
-    layout_binding.binding = 0;
-    layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-    layout_binding.descriptorCount = 1;
-    layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
-    layout_binding.pImmutableSamplers = NULL;
-
+    VkDescriptorSetLayoutBinding layout_binding = {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT, nullptr};
     const vkt::DescriptorSetLayout descriptor_set_layout(*m_device, {layout_binding});
-
     const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set_layout});
 
     m_command_buffer.Begin();
-    // Set invalid set
     m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-pDescriptorSets-parameter");
-    vk::CmdBindDescriptorSets(m_command_buffer.handle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout.handle(), 0, 1, &bad_set,
-                              0, NULL);
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &bad_set, 0, nullptr);
     m_errorMonitor->VerifyFound();
+}
 
-    OneOffDescriptorSet descriptor_set(m_device, {
-                                                     {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT, nullptr},
-                                                 });
-    VkDescriptorSet good_set = descriptor_set.set_;
+TEST_F(NegativeDescriptors, DescriptorSetNotInRange) {
+    RETURN_IF_SKIP(Init());
+
+    OneOffDescriptorSet descriptor_set(m_device,
+                                       {
+                                           {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                       });
+    vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_, &descriptor_set.layout_});
+    VkDescriptorSet descriptor_set_handles[4] = {descriptor_set.set_, descriptor_set.set_, descriptor_set.set_,
+                                                 descriptor_set.set_};
+
+    m_command_buffer.Begin();
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-firstSet-00360");
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0u, 3u, descriptor_set_handles,
+                              0u, nullptr);
+    m_errorMonitor->VerifyFound();
 
-    // Set out of range firstSet and descriptorSetCount sum
     m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-firstSet-00360");
-    m_errorMonitor->SetUnexpectedError("VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358");
-    vk::CmdBindDescriptorSets(m_command_buffer.handle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout.handle(), 2, 1, &good_set,
-                              0, NULL);
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 2u, 1u, descriptor_set_handles,
+                              0u, nullptr);
+    m_errorMonitor->VerifyFound();
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-firstSet-00360");
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1u, 2u, descriptor_set_handles,
+                              0u, nullptr);
+    m_errorMonitor->VerifyFound();
+}
+
+TEST_F(NegativeDescriptors, DescriptorSetNotInRange2) {
+    SetTargetApiVersion(VK_API_VERSION_1_1);
+    AddRequiredExtensions(VK_KHR_MAINTENANCE_6_EXTENSION_NAME);
+    AddRequiredFeature(vkt::Feature::maintenance6);
+    RETURN_IF_SKIP(Init());
+
+    OneOffDescriptorSet descriptor_set(m_device,
+                                       {
+                                           {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                       });
+    vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_, &descriptor_set.layout_});
+    VkDescriptorSet descriptor_set_handles[4] = {descriptor_set.set_, descriptor_set.set_, descriptor_set.set_,
+                                                 descriptor_set.set_};
+
+    VkBindDescriptorSetsInfo bind_ds_info = vku::InitStructHelper();
+    bind_ds_info.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+    bind_ds_info.layout = pipeline_layout;
+    bind_ds_info.pDescriptorSets = descriptor_set_handles;
+    bind_ds_info.dynamicOffsetCount = 0;
+    bind_ds_info.pDynamicOffsets = nullptr;
+
+    m_command_buffer.Begin();
+    m_errorMonitor->SetDesiredError("VUID-VkBindDescriptorSetsInfo-firstSet-00360");
+    bind_ds_info.firstSet = 0;
+    bind_ds_info.descriptorSetCount = 3;
+    vk::CmdBindDescriptorSets2KHR(m_command_buffer, &bind_ds_info);
+    m_errorMonitor->VerifyFound();
+
+    m_errorMonitor->SetDesiredError("VUID-VkBindDescriptorSetsInfo-firstSet-00360");
+    bind_ds_info.firstSet = 2;
+    bind_ds_info.descriptorSetCount = 1;
+    vk::CmdBindDescriptorSets2KHR(m_command_buffer, &bind_ds_info);
+    m_errorMonitor->VerifyFound();
+
+    m_errorMonitor->SetDesiredError("VUID-VkBindDescriptorSetsInfo-firstSet-00360");
+    bind_ds_info.firstSet = 1;
+    bind_ds_info.descriptorSetCount = 2;
+    vk::CmdBindDescriptorSets2KHR(m_command_buffer, &bind_ds_info);
+    m_errorMonitor->VerifyFound();
+}
+
+TEST_F(NegativeDescriptors, IncompatiblePipelineLayout) {
+    RETURN_IF_SKIP(Init());
+
+    OneOffDescriptorSet descriptor_set(m_device,
+                                       {
+                                           {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                       });
+    OneOffDescriptorSet descriptor_set_bad(m_device,
+                                           {
+                                               {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                           });
+    vkt::PipelineLayout pipeline_layout_bad(*m_device, {&descriptor_set_bad.layout_});
+
+    m_command_buffer.Begin();
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358");
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_bad, 0u, 1u, &descriptor_set.set_,
+                              0u, nullptr);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.End();
+}
+
+TEST_F(NegativeDescriptors, IncompatiblePipelineLayout2) {
+    RETURN_IF_SKIP(Init());
+    if (m_device->Physical().limits_.maxPerStageDescriptorStorageBuffers <= 8) {
+        GTEST_SKIP() << "maxPerStageDescriptorStorageBuffers of 8+ required";
+    }
+
+    OneOffDescriptorSet descriptor_set1(m_device,
+                                        {
+                                            {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                            {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                        });
+    OneOffDescriptorSet descriptor_set2(m_device,
+                                        {
+                                            {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                            {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                        });
+    vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set1.layout_, &descriptor_set1.layout_, &descriptor_set1.layout_});
+
+    VkDescriptorSet descriptor_set_handles[2] = {descriptor_set1.set_, descriptor_set2.set_};
+
+    m_command_buffer.Begin();
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358");
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1u, 2u, descriptor_set_handles,
+                              0u, nullptr);
     m_errorMonitor->VerifyFound();
     m_command_buffer.End();
 }
@@ -2027,13 +2118,6 @@ TEST_F(NegativeDescriptors, DescriptorSetCompatibility) {
                               CastToHandle<VkPipelineLayout, uintptr_t>(0xbaadb1be), 0, 1, &descriptorSet[0], 0, NULL);
     m_errorMonitor->VerifyFound();
 
-    // 2. layoutIndex exceeds # of layouts in layout
-    m_errorMonitor->SetDesiredError(" attempting to bind set to index 1");
-    m_errorMonitor->SetUnexpectedError("VUID-vkCmdBindDescriptorSets-firstSet-00360");
-    vk::CmdBindDescriptorSets(m_command_buffer.handle(), VK_PIPELINE_BIND_POINT_GRAPHICS, single_pipe_layout.handle(), 0, 2,
-                              &descriptorSet[0], 0, NULL);
-    m_errorMonitor->VerifyFound();
-
     // 3. Pipeline setLayout[0] has 2 descriptors, but set being bound has 5
     // descriptors
     m_errorMonitor->SetDesiredError(" has 2 total descriptors, but ");
diff --git a/tests/unit/pipeline.cpp b/tests/unit/pipeline.cpp
index c5f83e4d6cf..e6ffa3816ad 100644
--- a/tests/unit/pipeline.cpp
+++ b/tests/unit/pipeline.cpp
@@ -1819,6 +1819,33 @@ TEST_F(NegativePipeline, NotCompatibleForSetIndependent) {
     m_command_buffer.End();
 }
 
+TEST_F(NegativePipeline, DescriptorSetNotBound) {
+    RETURN_IF_SKIP(Init());
+
+    char const *cs_source = R"glsl(
+        #version 450
+        layout(set = 0, binding = 0) buffer SSBO { uint x; };
+        void main() {
+            x = 0;
+        }
+    )glsl";
+
+    OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
+    const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
+
+    CreateComputePipelineHelper pipe(*this);
+    pipe.cs_ = std::make_unique<VkShaderObj>(this, cs_source, VK_SHADER_STAGE_COMPUTE_BIT);
+    pipe.cp_ci_.layout = pipeline_layout;
+    pipe.CreateComputePipeline();
+
+    m_command_buffer.Begin();
+    vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.Handle());
+    m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08600");
+    vk::CmdDispatch(m_command_buffer, 1, 1, 1);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.End();
+}
+
 TEST_F(NegativePipeline, MaxPerStageResources) {
     TEST_DESCRIPTION("Check case where pipeline is created that exceeds maxPerStageResources");
 
diff --git a/tests/unit/shader_object.cpp b/tests/unit/shader_object.cpp
index 896bc18354d..75694aaf8a0 100644
--- a/tests/unit/shader_object.cpp
+++ b/tests/unit/shader_object.cpp
@@ -1077,37 +1077,28 @@ TEST_F(NegativeShaderObject, DrawWithShadersInNonDynamicRenderPass) {
 
 TEST_F(NegativeShaderObject, IncompatibleDescriptorSet) {
     TEST_DESCRIPTION("Bind an incompatible descriptor set.");
-
-    SetTargetApiVersion(VK_API_VERSION_1_1);
-    AddRequiredFeature(vkt::Feature::vertexPipelineStoresAndAtomics);
     RETURN_IF_SKIP(InitBasicShaderObject());
     InitDynamicRenderTarget();
 
-    static const char vert_src[] = R"glsl(
-        #version 460
-        layout(set = 0, binding = 1) buffer foo {
-            int x;
-        } bar;
-        void main() {
-           gl_Position = vec4(bar.x);
-        }
-    )glsl";
-
     OneOffDescriptorSet descriptor_set(m_device,
                                        {
                                            {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
-                                           {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT, nullptr},
                                        });
+    OneOffDescriptorSet descriptor_set_bad(m_device,
+                                           {
+                                               {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
+                                           });
+    vkt::PipelineLayout pipeline_layout_bad(*m_device, {&descriptor_set_bad.layout_});
 
-    const vkt::Shader vert_shader(*m_device, VK_SHADER_STAGE_VERTEX_BIT, vert_src, &descriptor_set.layout_.handle());
+    const vkt::Shader vert_shader(*m_device, VK_SHADER_STAGE_VERTEX_BIT, kMinimalShaderGlsl, &descriptor_set.layout_.handle());
     const vkt::Shader frag_shader(*m_device, VK_SHADER_STAGE_FRAGMENT_BIT, kFragmentUniformGlsl, &descriptor_set.layout_.handle());
 
     m_command_buffer.Begin();
     m_command_buffer.BeginRenderingColor(GetDynamicRenderTarget(), GetRenderTargetArea());
     SetDefaultDynamicStatesExclude();
     m_command_buffer.BindShaders(vert_shader, frag_shader);
-
-    m_errorMonitor->SetDesiredError("VUID-vkCmdDraw-None-08600");
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_bad, 0u, 1u,
+                              &descriptor_set_bad.set_, 0u, nullptr);
     m_errorMonitor->SetDesiredError("VUID-vkCmdDraw-None-08600");
     vk::CmdDraw(m_command_buffer.handle(), 3, 1, 0, 0);
     m_errorMonitor->VerifyFound();
@@ -1116,6 +1107,61 @@ TEST_F(NegativeShaderObject, IncompatibleDescriptorSet) {
     m_command_buffer.End();
 }
 
+TEST_F(NegativeShaderObject, DescriptorSetNotBound) {
+    RETURN_IF_SKIP(InitBasicShaderObject());
+
+    char const* cs_source = R"glsl(
+        #version 450
+        layout(set = 0, binding = 0) buffer SSBO { uint x; };
+        void main() {
+            x = 0;
+        }
+    )glsl";
+    auto cs_spirv = GLSLToSPV(VK_SHADER_STAGE_COMPUTE_BIT, cs_source);
+
+    OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
+    const vkt::Shader comp_shader(*m_device,
+                                  ShaderCreateInfo(cs_spirv, VK_SHADER_STAGE_COMPUTE_BIT, 1, &descriptor_set.layout_.handle()));
+    m_command_buffer.Begin();
+    m_command_buffer.BindCompShader(comp_shader);
+    m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08600");
+    vk::CmdDispatch(m_command_buffer, 1, 1, 1);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.End();
+}
+
+TEST_F(NegativeShaderObject, DescriptorSetBoundRange) {
+    RETURN_IF_SKIP(InitBasicShaderObject());
+
+    char const* cs_source = R"glsl(
+        #version 450
+        layout(set = 0, binding = 0) buffer SSBO0 { uint x; };
+        layout(set = 1, binding = 0) buffer SSBO1 { uint y; };
+        void main() {
+            x = y;
+        }
+    )glsl";
+    auto cs_spirv = GLSLToSPV(VK_SHADER_STAGE_COMPUTE_BIT, cs_source);
+
+    OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
+    VkDescriptorSetLayout dsl_handles[2] = {descriptor_set.layout_, descriptor_set.layout_};
+    const vkt::Shader comp_shader(*m_device, ShaderCreateInfo(cs_spirv, VK_SHADER_STAGE_COMPUTE_BIT, 2, dsl_handles));
+
+    vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
+    descriptor_set.WriteDescriptorBufferInfo(0, buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
+    descriptor_set.UpdateDescriptorSets();
+
+    m_command_buffer.Begin();
+    m_command_buffer.BindCompShader(comp_shader);
+    vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0u, 1u, &descriptor_set.set_, 0u,
+                              nullptr);
+    m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08600");
+    vk::CmdDispatch(m_command_buffer, 1, 1, 1);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.End();
+}
+
 TEST_F(NegativeShaderObject, NotSettingViewportAndScissor) {
     TEST_DESCRIPTION("Draw with shader object without setting viewport and scissor.");
 


Reviews:
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T18:59:20Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:00:40Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:01:17Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:01:45Z
user: login: ziga-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:51:22Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:53:39Z
user: login: ziga-lunarg
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:57:24Z
user: login: ziga-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:58:34Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:59:34Z
user: login: ziga-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:00:11Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:00:19Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:01:14Z
user: login: ziga-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:04:11Z
user: login: ziga-lunarg
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:04:15Z

Review comments:
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
path: layers/core_checks/cc_descriptor.cpp
user: login: spencer-lunarg
body: we had 2 `VerifySetLayoutCompatibility` and was confusing... 

- one is for comparing 2 `VkDescriptorSetLayout`
- one is for a given `VkDescriptorSet`, match it against a list of `VkDescriptorSetLayout`
created_at: 2025-05-01T18:59:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -233,40 +233,52 @@ bool CoreChecks::VerifyPipeleinLayoutCompatibilityUnion(const vvl::PipelineLayou
 }
 
 bool CoreChecks::ValidateCmdBindDescriptorSets(const vvl::CommandBuffer &cb_state, VkPipelineLayout layout, uint32_t firstSet,
-                                               uint32_t setCount, const VkDescriptorSet *pDescriptorSets,
+                                               uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets,
                                                uint32_t dynamicOffsetCount, const uint32_t *pDynamicOffsets,
                                                const Location &loc) const {
     bool skip = false;
     const bool is_2 = loc.function != Func::vkCmdBindDescriptorSets;
 
     auto pipeline_layout = Get<vvl::PipelineLayout>(layout);
-    if (!pipeline_layout) return skip;  // dynamicPipelineLayout feature
+    if (!pipeline_layout) {
+        return skip;  // dynamicPipelineLayout feature
+    }
 
     // Track total count of dynamic descriptor types to make sure we have an offset for each one
     uint32_t total_dynamic_descriptors = 0;
 
-    for (uint32_t set_idx = 0; set_idx < setCount; set_idx++) {
+    // If we detect we are binding to many sets, the extra sets will always be incompatible, so check first
+    if ((firstSet + descriptorSetCount) > static_cast<uint32_t>(pipeline_layout->set_layouts.size())) {
path: layers/core_checks/cc_descriptor.cpp
user: login: spencer-lunarg
body: moving this up (and doing a return skip) was nice. Basically you get these awful "set 2 is not compatible" because the pipeline layout didn't even have that set in it!
created_at: 2025-05-01T19:00:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1374,12 +1376,26 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
     }
 
     // Check if the current pipeline is compatible for the maximum used set with the bound sets.
-    if (pipeline.descriptor_buffer_mode) return skip;
+    if (pipeline.descriptor_buffer_mode) {
+        return skip;
+    }
 
     const auto pipeline_layout = pipeline.PipelineLayoutState();
     if (!pipeline.active_slots.empty() && !last_bound_state.IsBoundSetCompatible(pipeline.max_active_slot, *pipeline_layout)) {
+        // If they never bound any descriptors
+        if (!last_bound_state.desc_set_pipeline_layout) {
path: layers/core_checks/cc_drawdispatch.cpp
user: login: spencer-lunarg
body: if you don't call `vkCmdBindDescriptorSets`, makes it explicit that is why things are not compatible
created_at: 2025-05-01T19:01:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2027,13 +2115,6 @@ TEST_F(NegativeDescriptors, DescriptorSetCompatibility) {
                               CastToHandle<VkPipelineLayout, uintptr_t>(0xbaadb1be), 0, 1, &descriptorSet[0], 0, NULL);
     m_errorMonitor->VerifyFound();
 
-    // 2. layoutIndex exceeds # of layouts in layout
-    m_errorMonitor->SetDesiredError(" attempting to bind set to index 1");
-    m_errorMonitor->SetUnexpectedError("VUID-vkCmdBindDescriptorSets-firstSet-00360");
path: tests/unit/descriptors.cpp
user: login: spencer-lunarg
body: this already has dedicated test
created_at: 2025-05-01T19:01:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is used and which is DSL is which is not relevent
path: layers/core_checks/cc_descriptor.cpp
user: login: ziga-lunarg
body: `which is DSL is which is not relevent` makes my brain hurt and I don't know what it is trying to say.
Also relevent => relevant
created_at: 2025-05-01T19:51:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is used and which is DSL is which is not relevent
path: layers/core_checks/cc_descriptor.cpp
user: login: spencer-lunarg
body: agree, fixed
created_at: 2025-05-01T19:53:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1374,12 +1376,26 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
     }
 
     // Check if the current pipeline is compatible for the maximum used set with the bound sets.
-    if (pipeline.descriptor_buffer_mode) return skip;
+    if (pipeline.descriptor_buffer_mode) {
+        return skip;
+    }
 
     const auto pipeline_layout = pipeline.PipelineLayoutState();
     if (!pipeline.active_slots.empty() && !last_bound_state.IsBoundSetCompatible(pipeline.max_active_slot, *pipeline_layout)) {
+        // If they never bound any descriptors
+        if (!last_bound_state.desc_set_pipeline_layout) {
+            skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                             "The %s statically uses descriptor set %" PRIu32
+                             ", but because no descriptor was never bound, the pipeline layouts are not compatible.\nIf using a "
path: layers/core_checks/cc_drawdispatch.cpp
user: login: ziga-lunarg
body: You have a double negative here: "but because no descriptor was never bound"
Should be either "but because a descriptor was never bound"
or "but because no descriptor was ever bound"
created_at: 2025-05-01T19:55:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1450,16 +1461,37 @@ bool CoreChecks::ValidateActionStateDescriptorsShaderObject(const LastBound &las
 
     // Check if the current shader objects are compatible for the maximum used set with the bound sets.
     for (const auto &shader_state : last_bound_state.shader_object_states) {
-        if (!shader_state) continue;
+        if (!shader_state) {
+            continue;
+        }
 
-        if (shader_state && !shader_state->active_slots.empty() &&
+        if (!shader_state->active_slots.empty() &&
             !last_bound_state.IsBoundSetCompatible(shader_state->max_active_slot, *shader_state)) {
             LogObjectList objlist(cb_state.Handle(), shader_state->Handle());
-            skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                             "The %s statically uses descriptor set (index #%" PRIu32
-                             ") which is not compatible with the currently bound descriptor set's layout\n%s",
-                             FormatHandle(shader_state->Handle()).c_str(), shader_state->max_active_slot,
-                             last_bound_state.DescribeNonCompatibleSet(shader_state->max_active_slot, *shader_state).c_str());
+
+            if (!last_bound_state.desc_set_pipeline_layout) {
+                // If they never bound any descriptors
+                skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                                 "The %s statically uses descriptor set %" PRIu32
+                                 ", but because no descriptor was never bound, the pipeline layouts are not compatible.\nIf using "
path: layers/core_checks/cc_drawdispatch.cpp
user: login: ziga-lunarg
body: Same as above
created_at: 2025-05-01T19:56:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is also used, but which don't care which DSL is which
path: layers/core_checks/cc_descriptor.cpp
user: login: ziga-lunarg
body: `but which don't care which DSL is which`
Did you mean `but we don't care which DSL is which`
created_at: 2025-05-01T19:58:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1374,12 +1376,26 @@ bool CoreChecks::ValidateActionStateDescriptorsPipeline(const LastBound &last_bo
     }
 
     // Check if the current pipeline is compatible for the maximum used set with the bound sets.
-    if (pipeline.descriptor_buffer_mode) return skip;
+    if (pipeline.descriptor_buffer_mode) {
+        return skip;
+    }
 
     const auto pipeline_layout = pipeline.PipelineLayoutState();
     if (!pipeline.active_slots.empty() && !last_bound_state.IsBoundSetCompatible(pipeline.max_active_slot, *pipeline_layout)) {
+        // If they never bound any descriptors
+        if (!last_bound_state.desc_set_pipeline_layout) {
+            skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                             "The %s statically uses descriptor set %" PRIu32
+                             ", but because no descriptor was never bound, the pipeline layouts are not compatible.\nIf using a "
path: layers/core_checks/cc_drawdispatch.cpp
user: login: spencer-lunarg
body: taking `a descriptor was never bound`
created_at: 2025-05-01T19:59:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is also used, but which don't care which DSL is which
+bool CoreChecks::VerifyDescriptorSetLayoutIsCompatibile(const vvl::DescriptorSetLayout &reference_dsl,
+                                                        const vvl::DescriptorSetLayout &to_bind_dsl, std::string &error_msg) const {
     // Short circuit the detailed check.
-    if (layout_dsl.IsCompatible(&bound_dsl)) return true;
+    if (reference_dsl.IsCompatible(&to_bind_dsl)) {
+        return true;
+    }
 
     // Do a detailed compatibility check of this lhs def (referenced by layout_dsl), vs. the rhs (layout and def)
     // Should only be run if trivial accept has failed, and in that context should return false.
-    VkDescriptorSetLayout layout_dsl_handle = layout_dsl.VkHandle();
-    VkDescriptorSetLayout bound_dsl_handle = bound_dsl.VkHandle();
-    DescriptorSetLayoutDef const *layout_ds_layout_def = layout_dsl.GetLayoutDef();
-    DescriptorSetLayoutDef const *bound_ds_layout_def = bound_dsl.GetLayoutDef();
+    VkDescriptorSetLayout reference_dsl_handle = reference_dsl.VkHandle();
+    VkDescriptorSetLayout to_bind_dsl_handle = to_bind_dsl.VkHandle();
+    DescriptorSetLayoutDef const *reference_ds_layout_def = reference_dsl.GetLayoutDef();
path: layers/core_checks/cc_descriptor.cpp
user: login: ziga-lunarg
body: Why is this east const? We always use west const. Same for line below 
created_at: 2025-05-01T20:00:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is also used, but which don't care which DSL is which
path: layers/core_checks/cc_descriptor.cpp
user: login: spencer-lunarg
body: yes
created_at: 2025-05-01T20:00:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,42 +76,44 @@ bool CoreChecks::ImmutableSamplersAreEqual(const VkDescriptorSetLayoutBinding &b
     }
 }
 
-// If our layout is compatible with bound_dsl, return true,
-//  else return false and fill in error_msg will description of what causes incompatibility
-bool CoreChecks::VerifySetLayoutCompatibility(const vvl::DescriptorSetLayout &layout_dsl, const vvl::DescriptorSetLayout &bound_dsl,
-                                              std::string &error_msg) const {
+// Check if the |reference_dsl| (from PipelineLayout) is compatibile with |to_bind_dsl|
+// For GPL this is also used, but which don't care which DSL is which
+bool CoreChecks::VerifyDescriptorSetLayoutIsCompatibile(const vvl::DescriptorSetLayout &reference_dsl,
+                                                        const vvl::DescriptorSetLayout &to_bind_dsl, std::string &error_msg) const {
     // Short circuit the detailed check.
-    if (layout_dsl.IsCompatible(&bound_dsl)) return true;
+    if (reference_dsl.IsCompatible(&to_bind_dsl)) {
+        return true;
+    }
 
     // Do a detailed compatibility check of this lhs def (referenced by layout_dsl), vs. the rhs (layout and def)
     // Should only be run if trivial accept has failed, and in that context should return false.
-    VkDescriptorSetLayout layout_dsl_handle = layout_dsl.VkHandle();
-    VkDescriptorSetLayout bound_dsl_handle = bound_dsl.VkHandle();
-    DescriptorSetLayoutDef const *layout_ds_layout_def = layout_dsl.GetLayoutDef();
-    DescriptorSetLayoutDef const *bound_ds_layout_def = bound_dsl.GetLayoutDef();
+    VkDescriptorSetLayout reference_dsl_handle = reference_dsl.VkHandle();
+    VkDescriptorSetLayout to_bind_dsl_handle = to_bind_dsl.VkHandle();
+    DescriptorSetLayoutDef const *reference_ds_layout_def = reference_dsl.GetLayoutDef();
path: layers/core_checks/cc_descriptor.cpp
user: login: spencer-lunarg
body: this was here prior, but updated as well
created_at: 2025-05-01T20:01:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1450,16 +1461,37 @@ bool CoreChecks::ValidateActionStateDescriptorsShaderObject(const LastBound &las
 
     // Check if the current shader objects are compatible for the maximum used set with the bound sets.
     for (const auto &shader_state : last_bound_state.shader_object_states) {
-        if (!shader_state) continue;
+        if (!shader_state) {
+            continue;
+        }
 
-        if (shader_state && !shader_state->active_slots.empty() &&
+        if (!shader_state->active_slots.empty() &&
             !last_bound_state.IsBoundSetCompatible(shader_state->max_active_slot, *shader_state)) {
             LogObjectList objlist(cb_state.Handle(), shader_state->Handle());
-            skip |= LogError(vuid.compatible_pipeline_08600, objlist, vuid.loc(),
-                             "The %s statically uses descriptor set (index #%" PRIu32
-                             ") which is not compatible with the currently bound descriptor set's layout\n%s",
-                             FormatHandle(shader_state->Handle()).c_str(), shader_state->max_active_slot,
-                             last_bound_state.DescribeNonCompatibleSet(shader_state->max_active_slot, *shader_state).c_str());
+
+            if (!last_bound_state.desc_set_pipeline_layout) {
+                // If they never bound any descriptors
+                skip |= LogError(vuid.compatible_pipeline_08600, cb_state.GetObjectList(bind_point), vuid.loc(),
+                                 "The %s statically uses descriptor set %" PRIu32
+                                 ", but because a descriptor was never bound, the pipeline layouts are not compatible.\nIf using "
+                                 "a descriptor, make sure to call something like vkCmdBindDescriptorSets, vkCmdPushDescriptorSet, "
path: layers/core_checks/cc_drawdispatch.cpp
user: login: ziga-lunarg
body: Instead of `something like` I would say `one of`
created_at: 2025-05-01T20:04:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: ci-tester-lunarg
created_at: 2025-05-01T18:58:25Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434863.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T18:58:37Z
body: CI Vulkan-ValidationLayers build # 20020 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T19:47:35Z
body: CI Vulkan-ValidationLayers build # 20020 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T19:52:21Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434892.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T19:53:38Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434907.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T19:55:02Z
body: CI Vulkan-ValidationLayers build # 20022 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T20:00:18Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434924.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T20:01:33Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434939.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T20:05:02Z
body: CI Vulkan-ValidationLayers build # 20024 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T20:27:08Z
body: CI Vulkan-ValidationLayers build queued with queue ID 434959.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T20:30:02Z
body: CI Vulkan-ValidationLayers build # 20025 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-01T21:28:16Z
body: CI Vulkan-ValidationLayers build # 20025 passed.
author_association: COLLABORATOR