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
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++) {
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;
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());
+ }
}
}
}
// 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);
}
}
}
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;
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;
lock.unlock();
auto result = dev_data->dispatch_table.EndCommandBuffer(commandBuffer);
lock.lock();
+ PostCallRecordEndCommandBuffer(dev_data, pCB);
if (VK_SUCCESS == result) {
pCB->state = CB_RECORDED;
}
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) {
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
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 {
#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),
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++) {
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_);
: 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),
// 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;
<< " 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;
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
}
}
}
+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;
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;
(*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
// 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) {}
uint32_t start;
uint32_t end;
};
+typedef std::map<uint32_t, descriptor_req> BindingReqMap;
/*
* DescriptorSetLayout class
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
std::vector<safe_VkDescriptorSetLayoutBinding> bindings_;
uint32_t descriptor_count_; // total # descriptors in this layout
uint32_t dynamic_descriptor_count_;
+ BindingTypeStats binding_type_stats_;
};
/*
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
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(); };
// 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
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);
};
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_