renderer_vulkan: Optimize descriptor binding (#7142)
For each draw, Citra will rebind all descriptor set slots and may redundantly re-bind descriptor-sets that were already bound. Instead it should only bind the descriptor-sets that have either changed or have had their buffer-offsets changed. This also allows entire calls to `vkCmdBindDescriptorSets` to be removed in the case that nothing has changed between draw calls.
This commit is contained in:
parent
5118798c30
commit
312068eebf
|
@ -205,19 +205,55 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_RASTERIZER_SETS; i++) {
|
u32 new_descriptors_start = 0;
|
||||||
if (!set_dirty[i]) {
|
std::span<vk::DescriptorSet> new_descriptors_span{};
|
||||||
continue;
|
std::span<u32> new_offsets_span{};
|
||||||
}
|
|
||||||
bound_descriptor_sets[i] = descriptor_set_providers[i].Acquire(update_data[i]);
|
// Ensure all the descriptor sets are set at least once at the beginning.
|
||||||
set_dirty[i] = false;
|
if (scheduler.IsStateDirty(StateFlags::DescriptorSets)) {
|
||||||
|
set_dirty.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (set_dirty.any()) {
|
||||||
|
for (u32 i = 0; i < NUM_RASTERIZER_SETS; i++) {
|
||||||
|
if (!set_dirty.test(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bound_descriptor_sets[i] = descriptor_set_providers[i].Acquire(update_data[i]);
|
||||||
|
}
|
||||||
|
new_descriptors_span = bound_descriptor_sets;
|
||||||
|
|
||||||
|
// Only send new offsets if the buffer descriptor-set changed.
|
||||||
|
if (set_dirty.test(0)) {
|
||||||
|
new_offsets_span = offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to compact the number of updated descriptor-set slots to the ones that have actually
|
||||||
|
// changed
|
||||||
|
if (!set_dirty.all()) {
|
||||||
|
const u64 dirty_mask = set_dirty.to_ulong();
|
||||||
|
new_descriptors_start = static_cast<u32>(std::countr_zero(dirty_mask));
|
||||||
|
const u32 new_descriptors_end = 64u - static_cast<u32>(std::countl_zero(dirty_mask));
|
||||||
|
const u32 new_descriptors_size = new_descriptors_end - new_descriptors_start;
|
||||||
|
|
||||||
|
new_descriptors_span =
|
||||||
|
new_descriptors_span.subspan(new_descriptors_start, new_descriptors_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_dirty.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::container::static_vector<vk::DescriptorSet, NUM_RASTERIZER_SETS> new_descriptors(
|
||||||
|
new_descriptors_span.begin(), new_descriptors_span.end());
|
||||||
|
boost::container::static_vector<u32, NUM_DYNAMIC_OFFSETS> new_offsets(new_offsets_span.begin(),
|
||||||
|
new_offsets_span.end());
|
||||||
|
|
||||||
const bool is_dirty = scheduler.IsStateDirty(StateFlags::Pipeline);
|
const bool is_dirty = scheduler.IsStateDirty(StateFlags::Pipeline);
|
||||||
const bool pipeline_dirty = (current_pipeline != pipeline) || is_dirty;
|
const bool pipeline_dirty = (current_pipeline != pipeline) || is_dirty;
|
||||||
scheduler.Record([this, is_dirty, pipeline_dirty, pipeline,
|
scheduler.Record([this, is_dirty, pipeline_dirty, pipeline,
|
||||||
current_dynamic = current_info.dynamic, dynamic = info.dynamic,
|
current_dynamic = current_info.dynamic, dynamic = info.dynamic,
|
||||||
descriptor_sets = bound_descriptor_sets, offsets = offsets,
|
new_descriptors_start, descriptor_sets = std::move(new_descriptors),
|
||||||
|
offsets = std::move(new_offsets),
|
||||||
current_rasterization = current_info.rasterization,
|
current_rasterization = current_info.rasterization,
|
||||||
current_depth_stencil = current_info.depth_stencil,
|
current_depth_stencil = current_info.depth_stencil,
|
||||||
rasterization = info.rasterization,
|
rasterization = info.rasterization,
|
||||||
|
@ -318,13 +354,16 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||||
}
|
}
|
||||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle());
|
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle());
|
||||||
}
|
}
|
||||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0,
|
|
||||||
descriptor_sets, offsets);
|
if (descriptor_sets.size()) {
|
||||||
|
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout,
|
||||||
|
new_descriptors_start, descriptor_sets, offsets);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
current_info = info;
|
current_info = info;
|
||||||
current_pipeline = pipeline;
|
current_pipeline = pipeline;
|
||||||
scheduler.MarkStateNonDirty(StateFlags::Pipeline);
|
scheduler.MarkStateNonDirty(StateFlags::Pipeline | StateFlags::DescriptorSets);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -497,7 +536,10 @@ void PipelineCache::BindTexelBuffer(u32 binding, vk::BufferView buffer_view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineCache::SetBufferOffset(u32 binding, size_t offset) {
|
void PipelineCache::SetBufferOffset(u32 binding, size_t offset) {
|
||||||
offsets[binding] = static_cast<u32>(offset);
|
if (offsets[binding] != static_cast<u32>(offset)) {
|
||||||
|
offsets[binding] = static_cast<u32>(offset);
|
||||||
|
set_dirty[0] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PipelineCache::IsCacheValid(std::span<const u8> data) const {
|
bool PipelineCache::IsCacheValid(std::span<const u8> data) const {
|
||||||
|
|
Reference in New Issue