layers: Add validation caching for draw/dispatch
authorJohn Zulauf <jzulauf@lunarg.com>
Sat, 23 Dec 2017 00:14:54 +0000 (17:14 -0700)
committerjzulauf-lunarg <32470354+jzulauf-lunarg@users.noreply.github.com>
Fri, 5 Jan 2018 00:08:05 +0000 (17:08 -0700)
Validation of descriptors at draw or dispatch time is now cached, s.t.
without changes that affect validity, validation checks are not
repeated.

Change-Id: I713662d00813989bf4441921456afca431d730d7

layers/buffer_validation.cpp
layers/core_validation.cpp
layers/core_validation_types.h
layers/descriptor_sets.cpp
layers/descriptor_sets.h

index 05f2f40..112972f 100644 (file)
@@ -228,6 +228,7 @@ void SetLayout(layer_data *device_data, GLOBAL_CB_NODE *pCB, ImageSubresourcePai
 void SetImageLayout(layer_data *device_data, GLOBAL_CB_NODE *cb_node, const IMAGE_STATE *image_state,
                     VkImageSubresourceRange image_subresource_range, const VkImageLayout &layout) {
     assert(image_state);
+    cb_node->image_layout_change_count++;  // Change the version of this data to force revalidation
     for (uint32_t level_index = 0; level_index < image_subresource_range.levelCount; ++level_index) {
         uint32_t level = image_subresource_range.baseMipLevel + level_index;
         for (uint32_t layer_index = 0; layer_index < image_subresource_range.layerCount; layer_index++) {
@@ -385,6 +386,7 @@ void TransitionImageAspectLayout(layer_data *device_data, GLOBAL_CB_NODE *pCB, c
     VkImageSubresource sub = {aspect, level, layer};
     IMAGE_CMD_BUF_LAYOUT_NODE node;
     if (!FindCmdBufLayout(device_data, pCB, mem_barrier->image, sub, node)) {
+        pCB->image_layout_change_count++;  // Change the version of this data to force revalidation
         SetLayout(device_data, pCB, mem_barrier->image, sub,
                   IMAGE_CMD_BUF_LAYOUT_NODE(mem_barrier->oldLayout, mem_barrier->newLayout));
         return;
index 123db13..d55cede 100644 (file)
@@ -1184,15 +1184,24 @@ static bool ValidateDrawState(layer_data *dev_data, GLOBAL_CB_NODE *cb_node, CMD
                 cvdescriptorset::DescriptorSet *descriptor_set = state.boundDescriptorSets[setIndex];
                 // Validate the draw-time state for this descriptor set
                 std::string err_str;
-                if (!descriptor_set->IsPushDescriptor() &&
-                    !descriptor_set->ValidateDrawState(set_binding_pair.second, state.dynamicOffsets[setIndex], cb_node, function,
-                                                       &err_str)) {
-                    auto set = descriptor_set->GetSet();
-                    result |= log_msg(dev_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
-                                      VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, HandleToUint64(set), __LINE__,
-                                      DRAWSTATE_DESCRIPTOR_SET_NOT_UPDATED, "DS",
-                                      "Descriptor set 0x%" PRIx64 " encountered the following validation error at %s time: %s",
-                                      HandleToUint64(set), function, err_str.c_str());
+                if (!descriptor_set->IsPushDescriptor()) {
+                    // For the "bindless" style resource usage with many descriptors, need to optimize command <-> descriptor
+                    // binding validation. Take the requested binding set and prefilter it to eliminate redundant validation checks.
+                    // Here, the currently bound pipeline determines whether an image validation check is redundant...
+                    // for images are the "req" portion of the binding_req is indirectly (but tightly) coupled to the pipeline.
+                    const cvdescriptorset::PrefilterBindRequestMap reduced_map(*descriptor_set, set_binding_pair.second, cb_node,
+                                                                               pPipe);
+                    const auto &binding_req_map = reduced_map.Map();
+
+                    if (!descriptor_set->ValidateDrawState(binding_req_map, state.dynamicOffsets[setIndex], cb_node, function,
+                                                           &err_str)) {
+                        auto set = descriptor_set->GetSet();
+                        result |= log_msg(dev_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                          VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, HandleToUint64(set), __LINE__,
+                                          DRAWSTATE_DESCRIPTOR_SET_NOT_UPDATED, "DS",
+                                          "Descriptor set 0x%" PRIx64 " encountered the following validation error at %s time: %s",
+                                          HandleToUint64(set), function, err_str.c_str());
+                    }
                 }
             }
         }
@@ -1214,10 +1223,14 @@ static void UpdateDrawState(layer_data *dev_data, GLOBAL_CB_NODE *cb_state, cons
             // Pull the set node
             cvdescriptorset::DescriptorSet *descriptor_set = state.boundDescriptorSets[setIndex];
             if (!descriptor_set->IsPushDescriptor()) {
+                // For the "bindless" style resource usage with many descriptors, need to optimize command <-> descriptor binding
+                const cvdescriptorset::PrefilterBindRequestMap reduced_map(*descriptor_set, set_binding_pair.second, cb_state);
+                const auto &binding_req_map = reduced_map.Map();
+
                 // Bind this set and its active descriptor resources to the command buffer
-                descriptor_set->BindCommandBuffer(cb_state, set_binding_pair.second);
+                descriptor_set->BindCommandBuffer(cb_state, binding_req_map);
                 // For given active slots record updated images & buffers
-                descriptor_set->GetStorageUpdates(set_binding_pair.second, &cb_state->updateBuffers, &cb_state->updateImages);
+                descriptor_set->GetStorageUpdates(binding_req_map, &cb_state->updateBuffers, &cb_state->updateImages);
             }
         }
     }
@@ -1762,6 +1775,7 @@ static void ResetCommandBufferState(layer_data *dev_data, const VkCommandBuffer
         pCB->hasDrawCmd = false;
         pCB->state = CB_NEW;
         pCB->submitCount = 0;
+        pCB->image_layout_change_count = 1;  // Start at 1. 0 is insert value for validation cache versions, s.t. new == dirty
         pCB->status = 0;
         pCB->static_status = 0;
         pCB->viewportMask = 0;
@@ -5369,6 +5383,13 @@ VKAPI_ATTR VkResult VKAPI_CALL BeginCommandBuffer(VkCommandBuffer commandBuffer,
 
     return result;
 }
+static void PostCallRecordEndCommandBuffer(layer_data *dev_data, GLOBAL_CB_NODE *cb_state) {
+    // Cached validation is specific to a specific recording of a specific command buffer.
+    for (auto descriptor_set : cb_state->validated_descriptor_sets) {
+        descriptor_set->ClearCachedValidation(cb_state);
+    }
+    cb_state->validated_descriptor_sets.clear();
+}
 
 VKAPI_ATTR VkResult VKAPI_CALL EndCommandBuffer(VkCommandBuffer commandBuffer) {
     bool skip = false;
@@ -5394,6 +5415,7 @@ VKAPI_ATTR VkResult VKAPI_CALL EndCommandBuffer(VkCommandBuffer commandBuffer) {
         lock.unlock();
         auto result = dev_data->dispatch_table.EndCommandBuffer(commandBuffer);
         lock.lock();
+        PostCallRecordEndCommandBuffer(dev_data, pCB);
         if (VK_SUCCESS == result) {
             pCB->state = CB_RECORDED;
         }
@@ -5709,6 +5731,7 @@ static void PreCallRecordCmdBindDescriptorSets(layer_data *device_data, GLOBAL_C
                                           pDynamicOffsets + total_dynamic_descriptors + set_dynamic_descriptor_count);
                 total_dynamic_descriptors += set_dynamic_descriptor_count;
             }
+            cb_state->validated_descriptor_sets.insert(descriptor_set);
         }
         // For any previously bound sets, need to set them to "invalid" if they were disturbed by this update
         if (firstSet > 0) {
index fd48faf..6f1610b 100644 (file)
@@ -649,6 +649,8 @@ struct GLOBAL_CB_NODE : public BASE_NODE {
     bool hasDrawCmd;
     CB_STATE state;                      // Track cmd buffer update state
     uint64_t submitCount;                // Number of times CB has been submitted
+    typedef uint64_t ImageLayoutUpdateCount;
+    ImageLayoutUpdateCount image_layout_change_count;  // The sequence number for changes to image layout (for cached validation)
     CBStatusFlags status;                // Track status of various bindings on cmd buffer
     CBStatusFlags static_status;         // All state bits provided by current graphics pipeline
                                          // rather than dynamic state
@@ -697,6 +699,7 @@ struct GLOBAL_CB_NODE : public BASE_NODE {
     std::unordered_set<VkDeviceMemory> memObjs;
     std::vector<std::function<bool(VkQueue)>> eventUpdates;
     std::vector<std::function<bool(VkQueue)>> queryUpdates;
+    std::unordered_set<cvdescriptorset::DescriptorSet *> validated_descriptor_sets;
 };
 
 struct SEMAPHORE_WAIT {
index ed03840..b217d91 100644 (file)
@@ -30,6 +30,7 @@
 #include <algorithm>
 
 // Construct DescriptorSetLayout instance from given create info
+// Proactively reserve and resize as possible, as the reallocation was visible in profiling
 cvdescriptorset::DescriptorSetLayout::DescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo *p_create_info,
                                                           const VkDescriptorSetLayout layout)
     : layout_(layout),
@@ -37,7 +38,7 @@ cvdescriptorset::DescriptorSetLayout::DescriptorSetLayout(const VkDescriptorSetL
       binding_count_(p_create_info->bindingCount),
       descriptor_count_(0),
       dynamic_descriptor_count_(0) {
-    // Proactively reserve and resize as possible, as the reallocation was visible in profiling
+    binding_type_stats_ = {0, 0, 0};
     std::set<uint32_t> sorted_bindings;
     // Create the sorted set and unsorted map of bindings and indices
     for (uint32_t i = 0; i < binding_count_; i++) {
@@ -65,6 +66,12 @@ cvdescriptorset::DescriptorSetLayout::DescriptorSetLayout(const VkDescriptorSetL
             binding_info.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC) {
             binding_to_dyn_count[binding_num] = binding_info.descriptorCount;
             dynamic_descriptor_count_ += binding_info.descriptorCount;
+            binding_type_stats_.dynamic_buffer_count++;
+        } else if ((binding_info.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) ||
+                   (binding_info.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)) {
+            binding_type_stats_.non_dynamic_buffer_count++;
+        } else {
+            binding_type_stats_.image_sampler_count++;
         }
     }
     assert(bindings_.size() == binding_count_);
@@ -296,7 +303,7 @@ cvdescriptorset::AllocateDescriptorSetsData::AllocateDescriptorSetsData(uint32_t
     : required_descriptors_by_type{}, layout_nodes(count, nullptr) {}
 
 cvdescriptorset::DescriptorSet::DescriptorSet(const VkDescriptorSet set, const VkDescriptorPool pool,
-                                              const std::shared_ptr<DescriptorSetLayout const> &layout, const layer_data *dev_data)
+                                              const std::shared_ptr<DescriptorSetLayout const> &layout, layer_data *dev_data)
     : some_update_(false),
       set_(set),
       pool_state_(nullptr),
@@ -383,7 +390,7 @@ bool cvdescriptorset::DescriptorSet::IsCompatible(DescriptorSetLayout const *con
 //  that any update buffers are valid, and that any dynamic offsets are within the bounds of their buffers.
 // Return true if state is acceptable, or false and write an error message into error string
 bool cvdescriptorset::DescriptorSet::ValidateDrawState(const std::map<uint32_t, descriptor_req> &bindings,
-                                                       const std::vector<uint32_t> &dynamic_offsets, const GLOBAL_CB_NODE *cb_node,
+                                                       const std::vector<uint32_t> &dynamic_offsets, GLOBAL_CB_NODE *cb_node,
                                                        const char *caller, std::string *error) const {
     for (auto binding_pair : bindings) {
         auto binding = binding_pair.first;
@@ -415,7 +422,7 @@ bool cvdescriptorset::DescriptorSet::ValidateDrawState(const std::map<uint32_t,
                                   << " references invalid buffer " << buffer << ".";
                         *error = error_str.str();
                         return false;
-                    } else {
+                    } else if (!buffer_node->sparse) {
                         for (auto mem_binding : buffer_node->GetBoundMemory()) {
                             if (!GetMemObjInfo(device_data_, mem_binding)) {
                                 std::stringstream error_str;
@@ -425,6 +432,13 @@ bool cvdescriptorset::DescriptorSet::ValidateDrawState(const std::map<uint32_t,
                                 return false;
                             }
                         }
+                    } else {
+                        // Enqueue sparse resource validation, as these can only be validated at submit time
+                        auto device_data_copy = device_data_;  // Cannot capture members by value, so make capturable copy.
+                        std::function<bool(void)> function = [device_data_copy, caller, buffer_node]() {
+                            return core_validation::ValidateBufferMemoryIsValid(device_data_copy, buffer_node, caller);
+                        };
+                        cb_node->queue_submit_functions.push_back(function);
                     }
                     if (descriptors_[i]->IsDynamic()) {
                         // Validate that dynamic offsets are within the buffer
@@ -723,6 +737,72 @@ void cvdescriptorset::DescriptorSet::BindCommandBuffer(GLOBAL_CB_NODE *cb_node,
         }
     }
 }
+void cvdescriptorset::DescriptorSet::FilterAndTrackOneBindingReq(const BindingReqMap::value_type &binding_req_pair,
+                                                                 const BindingReqMap &in_req, BindingReqMap *out_req,
+                                                                 TrackedBindings *bindings) {
+    assert(out_req);
+    assert(bindings);
+    const auto binding = binding_req_pair.first;
+    // Use insert and look at the boolean ("was inserted") in the returned pair to see if this is a new set member.
+    // Saves one hash lookup vs. find ... compare w/ end ... insert.
+    const auto it_bool_pair = bindings->insert(binding);
+    if (it_bool_pair.second) {
+        out_req->emplace(binding_req_pair);
+    }
+}
+void cvdescriptorset::DescriptorSet::FilterAndTrackOneBindingReq(const BindingReqMap::value_type &binding_req_pair,
+                                                                 const BindingReqMap &in_req, BindingReqMap *out_req,
+                                                                 TrackedBindings *bindings, uint32_t limit) {
+    if (bindings->size() < limit) FilterAndTrackOneBindingReq(binding_req_pair, in_req, out_req, bindings);
+}
+
+void cvdescriptorset::DescriptorSet::FilterAndTrackBindingReqs(GLOBAL_CB_NODE *cb_state, const BindingReqMap &in_req,
+                                                               BindingReqMap *out_req) {
+    TrackedBindings &bound = cached_validation_[cb_state].command_binding_and_usage;
+    if (bound.size() == GetBindingCount()) {
+        return;  // All bindings are bound, out req is empty
+    }
+    for (const auto &binding_req_pair : in_req) {
+        const auto binding = binding_req_pair.first;
+        // If a binding doesn't exist, or has already been bound, skip it
+        if (p_layout_->HasBinding(binding)) {
+            FilterAndTrackOneBindingReq(binding_req_pair, in_req, out_req, &bound);
+        }
+    }
+}
+
+void cvdescriptorset::DescriptorSet::FilterAndTrackBindingReqs(GLOBAL_CB_NODE *cb_state, PIPELINE_STATE *pipeline,
+                                                               const BindingReqMap &in_req, BindingReqMap *out_req) {
+    auto &validated = cached_validation_[cb_state];
+    auto &image_sample_val = validated.image_samplers[pipeline];
+    auto *const dynamic_buffers = &validated.dynamic_buffers;
+    auto *const non_dynamic_buffers = &validated.non_dynamic_buffers;
+    const auto &stats = p_layout_->GetBindingTypeStats();
+    for (const auto &binding_req_pair : in_req) {
+        auto binding = binding_req_pair.first;
+        VkDescriptorSetLayoutBinding const *layout_binding = p_layout_->GetDescriptorSetLayoutBindingPtrFromBinding(binding);
+        if (!layout_binding) {
+            continue;
+        }
+        // Caching criteria differs per type.
+        // If image_layout have changed , the image descriptors need to be validated against them.
+        if ((layout_binding->descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) ||
+            (layout_binding->descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC)) {
+            FilterAndTrackOneBindingReq(binding_req_pair, in_req, out_req, dynamic_buffers, stats.dynamic_buffer_count);
+        } else if ((layout_binding->descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) ||
+                   (layout_binding->descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)) {
+            FilterAndTrackOneBindingReq(binding_req_pair, in_req, out_req, non_dynamic_buffers, stats.non_dynamic_buffer_count);
+        } else {
+            // This is rather crude, as the changed layouts may not impact the bound descriptors,
+            // but the simple "versioning" is a simple "dirt" test.
+            auto &version = image_sample_val[binding];  // Take advantage of default construtor zero initialzing new entries
+            if (version != cb_state->image_layout_change_count) {
+                version = cb_state->image_layout_change_count;
+                out_req->emplace(binding_req_pair);
+            }
+        }
+    }
+}
 
 cvdescriptorset::SamplerDescriptor::SamplerDescriptor(const VkSampler *immut) : sampler_(VK_NULL_HANDLE), immutable_(false) {
     updated = false;
@@ -1706,7 +1786,7 @@ void cvdescriptorset::PerformAllocateDescriptorSets(const VkDescriptorSetAllocat
                                                     const AllocateDescriptorSetsData *ds_data,
                                                     std::unordered_map<VkDescriptorPool, DESCRIPTOR_POOL_STATE *> *pool_map,
                                                     std::unordered_map<VkDescriptorSet, cvdescriptorset::DescriptorSet *> *set_map,
-                                                    const layer_data *dev_data) {
+                                                    layer_data *dev_data) {
     auto pool_state = (*pool_map)[p_alloc_info->descriptorPool];
     // Account for sets and individual descriptors allocated from pool
     pool_state->availableSets -= p_alloc_info->descriptorSetCount;
@@ -1723,3 +1803,20 @@ void cvdescriptorset::PerformAllocateDescriptorSets(const VkDescriptorSetAllocat
         (*set_map)[descriptor_sets[i]] = new_ds;
     }
 }
+
+cvdescriptorset::PrefilterBindRequestMap::PrefilterBindRequestMap(cvdescriptorset::DescriptorSet &ds, const BindingReqMap &in_map,
+                                                                  GLOBAL_CB_NODE *cb_state)
+    : filtered_map_(), orig_map_(in_map) {
+    if (ds.GetTotalDescriptorCount() > kManyDescriptors_) {
+        filtered_map_.reset(new std::map<uint32_t, descriptor_req>());
+        ds.FilterAndTrackBindingReqs(cb_state, orig_map_, filtered_map_.get());
+    }
+}
+cvdescriptorset::PrefilterBindRequestMap::PrefilterBindRequestMap(cvdescriptorset::DescriptorSet &ds, const BindingReqMap &in_map,
+                                                                  GLOBAL_CB_NODE *cb_state, PIPELINE_STATE *pipeline)
+    : filtered_map_(), orig_map_(in_map) {
+    if (ds.GetTotalDescriptorCount() > kManyDescriptors_) {
+        filtered_map_.reset(new std::map<uint32_t, descriptor_req>());
+        ds.FilterAndTrackBindingReqs(cb_state, pipeline, orig_map_, filtered_map_.get());
+    }
+}
\ No newline at end of file
index 1ac2d0a..cbc4ad1 100644 (file)
@@ -41,6 +41,7 @@ using core_validation::layer_data;
 // Descriptor Data structures
 namespace cvdescriptorset {
 
+// Utility structs/classes/types
 // Index range for global indices below, end is exclusive, i.e. [start,end)
 struct IndexRange {
     IndexRange(uint32_t start_in, uint32_t end_in) : start(start_in), end(end_in) {}
@@ -48,6 +49,7 @@ struct IndexRange {
     uint32_t start;
     uint32_t end;
 };
+typedef std::map<uint32_t, descriptor_req> BindingReqMap;
 
 /*
  * DescriptorSetLayout class
@@ -145,6 +147,13 @@ class DescriptorSetLayout {
     bool VerifyUpdateConsistency(uint32_t, uint32_t, uint32_t, const char *, const VkDescriptorSet, std::string *) const;
     bool IsPushDescriptor() const { return GetCreateFlags() & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; };
 
+    struct BindingTypeStats {
+        uint32_t dynamic_buffer_count;
+        uint32_t non_dynamic_buffer_count;
+        uint32_t image_sampler_count;
+    };
+    const BindingTypeStats &GetBindingTypeStats() const { return binding_type_stats_; }
+
    private:
     VkDescriptorSetLayout layout_;
     std::set<uint32_t> non_empty_bindings_;  // Containing non-emtpy bindings in numerical order
@@ -159,6 +168,7 @@ class DescriptorSetLayout {
     std::vector<safe_VkDescriptorSetLayoutBinding> bindings_;
     uint32_t descriptor_count_;  // total # descriptors in this layout
     uint32_t dynamic_descriptor_count_;
+    BindingTypeStats binding_type_stats_;
 };
 
 /*
@@ -302,7 +312,7 @@ bool ValidateAllocateDescriptorSets(const core_validation::layer_data *, const V
 void PerformAllocateDescriptorSets(const VkDescriptorSetAllocateInfo *, const VkDescriptorSet *, const AllocateDescriptorSetsData *,
                                    std::unordered_map<VkDescriptorPool, DESCRIPTOR_POOL_STATE *> *,
                                    std::unordered_map<VkDescriptorSet, cvdescriptorset::DescriptorSet *> *,
-                                   const core_validation::layer_data *);
+                                   core_validation::layer_data *);
 
 /*
  * DescriptorSet class
@@ -325,7 +335,7 @@ void PerformAllocateDescriptorSets(const VkDescriptorSetAllocateInfo *, const Vk
 class DescriptorSet : public BASE_NODE {
    public:
     DescriptorSet(const VkDescriptorSet, const VkDescriptorPool, const std::shared_ptr<DescriptorSetLayout const> &,
-                  const core_validation::layer_data *);
+                  core_validation::layer_data *);
     ~DescriptorSet();
     // A number of common Get* functions that return data based on layout from which this set was created
     uint32_t GetTotalDescriptorCount() const { return p_layout_->GetTotalDescriptorCount(); };
@@ -347,7 +357,7 @@ class DescriptorSet : public BASE_NODE {
     // Is this set compatible with the given layout?
     bool IsCompatible(DescriptorSetLayout const *const, std::string *) const;
     // For given bindings validate state at time of draw is correct, returning false on error and writing error details into string*
-    bool ValidateDrawState(const std::map<uint32_t, descriptor_req> &, const std::vector<uint32_t> &, const GLOBAL_CB_NODE *,
+    bool ValidateDrawState(const std::map<uint32_t, descriptor_req> &, const std::vector<uint32_t> &, GLOBAL_CB_NODE *,
                            const char *caller, std::string *) const;
     // For given set of bindings, add any buffers and images that will be updated to their respective unordered_sets & return number
     // of objects inserted
@@ -372,8 +382,23 @@ class DescriptorSet : public BASE_NODE {
     std::unordered_set<GLOBAL_CB_NODE *> GetBoundCmdBuffers() const { return cb_bindings; }
     // Bind given cmd_buffer to this descriptor set
     void BindCommandBuffer(GLOBAL_CB_NODE *, const std::map<uint32_t, descriptor_req> &);
+
+    // Track work that has been bound or validated to avoid duplicate work, important when large descriptor arrays
+    // are present
+    typedef std::unordered_set<uint32_t> TrackedBindings;
+    static void FilterAndTrackOneBindingReq(const BindingReqMap::value_type &binding_req_pair, const BindingReqMap &in_req,
+                                            BindingReqMap *out_req, TrackedBindings *set);
+    static void FilterAndTrackOneBindingReq(const BindingReqMap::value_type &binding_req_pair, const BindingReqMap &in_req,
+                                            BindingReqMap *out_req, TrackedBindings *set, uint32_t limit);
+    void FilterAndTrackBindingReqs(GLOBAL_CB_NODE *, const BindingReqMap &in_req, BindingReqMap *out_req);
+    void FilterAndTrackBindingReqs(GLOBAL_CB_NODE *, PIPELINE_STATE *, const BindingReqMap &in_req, BindingReqMap *out_req);
+    void ClearCachedDynamicDescriptorValidation(GLOBAL_CB_NODE *cb_state) { cached_validation_[cb_state].dynamic_buffers.clear(); }
+    void ClearCachedValidation(GLOBAL_CB_NODE *cb_state) { cached_validation_.erase(cb_state); }
     // If given cmd_buffer is in the cb_bindings set, remove it
-    void RemoveBoundCommandBuffer(GLOBAL_CB_NODE *cb_node) { cb_bindings.erase(cb_node); }
+    void RemoveBoundCommandBuffer(GLOBAL_CB_NODE *cb_node) {
+        cb_bindings.erase(cb_node);
+        ClearCachedValidation(cb_node);
+    }
     VkSampler const *GetImmutableSamplerPtrFromBinding(const uint32_t index) const {
         return p_layout_->GetImmutableSamplerPtrFromBinding(index);
     };
@@ -401,8 +426,37 @@ class DescriptorSet : public BASE_NODE {
     const std::shared_ptr<DescriptorSetLayout const> p_layout_;
     std::vector<std::unique_ptr<Descriptor>> descriptors_;
     // Ptr to device data used for various data look-ups
-    const core_validation::layer_data *device_data_;
+    core_validation::layer_data *const device_data_;
     const VkPhysicalDeviceLimits limits_;
+
+    // Cached binding and validation support:
+    //
+    // For the lifespan of a given command buffer recording, do lazy evaluation, caching, and dirtying of
+    // expensive validation operation (typically per-draw)
+    typedef std::unordered_map<GLOBAL_CB_NODE *, TrackedBindings> TrackedBindingMap;
+    typedef std::unordered_map<PIPELINE_STATE *, TrackedBindingMap> ValidatedBindings;
+    // Track the validation caching of bindings vs. the command buffer and draw state
+    typedef std::unordered_map<uint32_t, GLOBAL_CB_NODE::ImageLayoutUpdateCount> VersionedBindings;
+    struct CachedValidation {
+        TrackedBindings command_binding_and_usage;                               // Persistent for the life of the recording
+        TrackedBindings non_dynamic_buffers;                                     // Persistent for the life of the recording
+        TrackedBindings dynamic_buffers;                                         // Dirtied (flushed) each BindDescriptorSet
+        std::unordered_map<PIPELINE_STATE *, VersionedBindings> image_samplers;  // Tested vs. changes to CB's ImageLayout
+    };
+    typedef std::unordered_map<GLOBAL_CB_NODE *, CachedValidation> CachedValidationMap;
+    // Image and ImageView bindings are validated per pipeline and not invalidate by repeated binding
+    CachedValidationMap cached_validation_;
+};
+// For the "bindless" style resource usage with many descriptors, need to optimize binding and validation
+class PrefilterBindRequestMap {
+   public:
+    static const uint32_t kManyDescriptors_ = 64;  // TODO base this number on measured data
+    std::unique_ptr<BindingReqMap> filtered_map_;
+    const BindingReqMap &orig_map_;
+
+    PrefilterBindRequestMap(DescriptorSet &ds, const BindingReqMap &in_map, GLOBAL_CB_NODE *cb_state);
+    PrefilterBindRequestMap(DescriptorSet &ds, const BindingReqMap &in_map, GLOBAL_CB_NODE *cb_state, PIPELINE_STATE *);
+    const BindingReqMap &Map() const { return (filtered_map_) ? *filtered_map_ : orig_map_; }
 };
 }
 #endif  // CORE_VALIDATION_DESCRIPTOR_SETS_H_