[Vulkan] Merge texture and sampler descriptors into a single descriptor set

Put all descriptors used by translated shaders in up to 4 descriptor sets, which is the minimum required, and the most common on Android, `maxBoundDescriptorSets` device limit value
This commit is contained in:
Triang3l
2022-07-09 17:10:28 +03:00
parent ff35a4b3a1
commit b3edc56576
9 changed files with 782 additions and 553 deletions

View File

@@ -0,0 +1,415 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/linked_type_descriptor_set_allocator.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
void LinkedTypeDescriptorSetAllocator::Reset() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
page_usable_latest_.pool);
page_usable_latest_.descriptors_remaining.reset();
for (const std::pair<const uint32_t, Page>& page_pair : pages_usable_) {
dfn.vkDestroyDescriptorPool(device, page_pair.second.pool, nullptr);
}
pages_usable_.clear();
for (VkDescriptorPool pool : pages_full_) {
dfn.vkDestroyDescriptorPool(device, pool, nullptr);
}
pages_full_.clear();
}
VkDescriptorSet LinkedTypeDescriptorSetAllocator::Allocate(
VkDescriptorSetLayout descriptor_set_layout,
const VkDescriptorPoolSize* descriptor_counts,
uint32_t descriptor_type_count) {
assert_not_zero(descriptor_type_count);
#ifndef NDEBUG
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
assert_not_zero(descriptor_count_for_type.descriptorCount);
for (uint32_t j = 0; j < i; ++j) {
assert_true(descriptor_counts[j].type != descriptor_count_for_type.type);
}
}
#endif
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkDescriptorSetAllocateInfo descriptor_set_allocate_info;
descriptor_set_allocate_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_allocate_info.pNext = nullptr;
descriptor_set_allocate_info.descriptorSetCount = 1;
descriptor_set_allocate_info.pSetLayouts = &descriptor_set_layout;
VkDescriptorSet descriptor_set;
// Check if more descriptors have been requested than a page can hold, or
// descriptors of types not provided by this allocator, and if that's the
// case, create a dedicated pool for this allocation.
bool dedicated_descriptor_pool_needed = false;
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
// If the type is one that's not supported by the allocator, a dedicated
// pool is required. If it's supported, and the allocator has large enough
// pools to hold the requested number of descriptors,
// dedicated_descriptor_pool_needed will be set to false for this iteration,
// and the loop will continue. Otherwise, if that doesn't happen, a
// dedicated pool is required.
dedicated_descriptor_pool_needed = true;
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
const VkDescriptorPoolSize& descriptor_pool_size =
descriptor_pool_sizes_[j];
if (descriptor_count_for_type.type != descriptor_pool_size.type) {
continue;
}
if (descriptor_count_for_type.descriptorCount <=
descriptor_pool_size.descriptorCount) {
// For this type, pages can hold enough descriptors.
dedicated_descriptor_pool_needed = false;
}
break;
}
if (dedicated_descriptor_pool_needed) {
// For at least one requested type, pages can't hold enough descriptors.
break;
}
}
if (dedicated_descriptor_pool_needed) {
VkDescriptorPoolCreateInfo dedicated_descriptor_pool_create_info;
dedicated_descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
dedicated_descriptor_pool_create_info.pNext = nullptr;
dedicated_descriptor_pool_create_info.flags = 0;
dedicated_descriptor_pool_create_info.maxSets = 1;
dedicated_descriptor_pool_create_info.poolSizeCount = descriptor_type_count;
dedicated_descriptor_pool_create_info.pPoolSizes = descriptor_counts;
VkDescriptorPool dedicated_descriptor_pool;
if (dfn.vkCreateDescriptorPool(
device, &dedicated_descriptor_pool_create_info, nullptr,
&dedicated_descriptor_pool) != VK_SUCCESS) {
XELOGE(
"LinkedTypeDescriptorSetAllocator: Failed to create a dedicated "
"descriptor pool for a descriptor set that is too large for a pool "
"page");
return VK_NULL_HANDLE;
}
descriptor_set_allocate_info.descriptorPool = dedicated_descriptor_pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE(
"LinkedTypeDescriptorSetAllocator: Failed to allocate descriptors in "
"a dedicated pool");
dfn.vkDestroyDescriptorPool(device, dedicated_descriptor_pool, nullptr);
return VK_NULL_HANDLE;
}
pages_full_.push_back(dedicated_descriptor_pool);
return descriptor_set;
}
// Try allocating from the latest page an allocation has happened from, to
// avoid detaching from the map and re-attaching for every allocation.
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
assert_not_zero(page_usable_latest_.descriptor_sets_remaining);
bool allocate_from_latest_page = true;
bool latest_page_becomes_full =
page_usable_latest_.descriptor_sets_remaining == 1;
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
const VkDescriptorPoolSize& descriptors_remaining_for_type =
page_usable_latest_.descriptors_remaining[j];
if (descriptor_count_for_type.type !=
descriptors_remaining_for_type.type) {
continue;
}
if (descriptor_count_for_type.descriptorCount >=
descriptors_remaining_for_type.descriptorCount) {
if (descriptor_count_for_type.descriptorCount >
descriptors_remaining_for_type.descriptorCount) {
allocate_from_latest_page = false;
break;
}
latest_page_becomes_full = true;
}
}
if (!allocate_from_latest_page) {
break;
}
}
if (allocate_from_latest_page) {
descriptor_set_allocate_info.descriptorPool = page_usable_latest_.pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
descriptor_set = VK_NULL_HANDLE;
// Failed to allocate internally even though there should be enough
// space, don't try to allocate from this pool again at all.
latest_page_becomes_full = true;
}
if (latest_page_becomes_full) {
pages_full_.push_back(page_usable_latest_.pool);
page_usable_latest_.pool = VK_NULL_HANDLE;
page_usable_latest_.descriptors_remaining.reset();
} else {
--page_usable_latest_.descriptor_sets_remaining;
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
VkDescriptorPoolSize& descriptors_remaining_for_type =
page_usable_latest_.descriptors_remaining[j];
if (descriptor_count_for_type.type !=
descriptors_remaining_for_type.type) {
continue;
}
descriptors_remaining_for_type.descriptorCount -=
descriptor_count_for_type.descriptorCount;
}
}
}
if (descriptor_set != VK_NULL_HANDLE) {
return descriptor_set;
}
}
}
// Count the maximum number of descriptors requested for any type to stop
// searching for pages once they can't satisfy this requirement.
uint32_t max_descriptors_per_type = descriptor_counts[0].descriptorCount;
for (uint32_t i = 1; i < descriptor_type_count; ++i) {
max_descriptors_per_type = std::max(max_descriptors_per_type,
descriptor_counts[i].descriptorCount);
}
// If allocating from the latest pool wasn't possible, pick any that has
// enough free space. Prefer filling pages that have the most free space as
// they can more likely be used for more allocations later.
auto page_usable_it_next = pages_usable_.rbegin();
while (page_usable_it_next != pages_usable_.rend()) {
auto page_usable_it = page_usable_it_next;
++page_usable_it_next;
if (page_usable_it->first < max_descriptors_per_type) {
// All other pages_usable_ entries have smaller maximum number of free
// descriptor for any type (it's the map key).
break;
}
// Check if the page has enough free descriptors for all requested types,
// and whether allocating the requested number of descriptors in it will
// result in the page becoming full.
bool map_page_has_sufficient_space = true;
bool map_page_becomes_full =
page_usable_it->second.descriptor_sets_remaining == 1;
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
const VkDescriptorPoolSize& descriptors_remaining_for_type =
page_usable_it->second.descriptors_remaining[j];
if (descriptor_count_for_type.type !=
descriptors_remaining_for_type.type) {
continue;
}
if (descriptor_count_for_type.descriptorCount >=
descriptors_remaining_for_type.descriptorCount) {
if (descriptor_count_for_type.descriptorCount >
descriptors_remaining_for_type.descriptorCount) {
map_page_has_sufficient_space = false;
break;
}
map_page_becomes_full = true;
}
}
if (!map_page_has_sufficient_space) {
break;
}
}
if (!map_page_has_sufficient_space) {
// Even though the coarse (maximum number of descriptors for any type)
// check has passed, for the exact types requested this page doesn't have
// sufficient space - try another one.
continue;
}
// Remove the page from the map unconditionally - in case of a successful
// allocation, it will have a different number of free descriptors for
// different types, thus potentially a new map key (but it will also become
// page_usable_latest_ instead even), or will become full, and in case of a
// failure to allocate internally even though there still should be enough
// space, it should never be allocated from again.
Page map_page = std::move(page_usable_it->second);
// Convert the reverse iterator to a forward iterator for erasing.
pages_usable_.erase(std::next(page_usable_it).base());
descriptor_set_allocate_info.descriptorPool = map_page.pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
descriptor_set = VK_NULL_HANDLE;
// Failed to allocate internally even though there should be enough space,
// don't try to allocate from this pool again at all.
map_page_becomes_full = true;
}
if (map_page_becomes_full) {
map_page.descriptors_remaining.reset();
pages_full_.push_back(map_page.pool);
} else {
--map_page.descriptor_sets_remaining;
for (uint32_t i = 0; i < descriptor_type_count; ++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
VkDescriptorPoolSize& descriptors_remaining_for_type =
map_page.descriptors_remaining[j];
if (descriptor_count_for_type.type !=
descriptors_remaining_for_type.type) {
continue;
}
descriptors_remaining_for_type.descriptorCount -=
descriptor_count_for_type.descriptorCount;
}
}
// Move the latest page that allocation couldn't be done in to the usable
// pages to replace it with the new one.
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
// Calculate the map key (the maximum number of remaining descriptors of
// any type).
uint32_t latest_page_max_descriptors_remaining =
page_usable_latest_.descriptors_remaining[0].descriptorCount;
for (uint32_t i = 1; i < descriptor_pool_size_count_; ++i) {
latest_page_max_descriptors_remaining = std::max(
latest_page_max_descriptors_remaining,
page_usable_latest_.descriptors_remaining[i].descriptorCount);
}
assert_not_zero(latest_page_max_descriptors_remaining);
pages_usable_.emplace(latest_page_max_descriptors_remaining,
std::move(page_usable_latest_));
}
page_usable_latest_ = std::move(map_page);
}
if (descriptor_set != VK_NULL_HANDLE) {
return descriptor_set;
}
}
// Try allocating from a new page.
// See if the new page has instantly become full.
bool new_page_becomes_full = descriptor_sets_per_page_ == 1;
for (uint32_t i = 0; !new_page_becomes_full && i < descriptor_type_count;
++i) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[i];
for (uint32_t j = 0; j < descriptor_pool_size_count_; ++j) {
const VkDescriptorPoolSize& descriptors_remaining_for_type =
descriptor_pool_sizes_[j];
if (descriptor_count_for_type.type !=
descriptors_remaining_for_type.type) {
continue;
}
assert_true(descriptor_count_for_type.descriptorCount <=
descriptors_remaining_for_type.descriptorCount);
if (descriptor_count_for_type.descriptorCount >=
descriptors_remaining_for_type.descriptorCount) {
new_page_becomes_full = true;
break;
}
}
}
// Allocate from a new page. However, if the new page becomes full
// immediately, create a dedicated pool instead for the exact number of
// descriptors not to leave any unused space in the pool.
VkDescriptorPoolCreateInfo new_descriptor_pool_create_info;
new_descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
new_descriptor_pool_create_info.pNext = nullptr;
new_descriptor_pool_create_info.flags = 0;
if (new_page_becomes_full) {
new_descriptor_pool_create_info.maxSets = 1;
new_descriptor_pool_create_info.poolSizeCount = descriptor_type_count;
new_descriptor_pool_create_info.pPoolSizes = descriptor_counts;
} else {
new_descriptor_pool_create_info.maxSets = descriptor_sets_per_page_;
new_descriptor_pool_create_info.poolSizeCount = descriptor_pool_size_count_;
new_descriptor_pool_create_info.pPoolSizes = descriptor_pool_sizes_.get();
}
VkDescriptorPool new_descriptor_pool;
if (dfn.vkCreateDescriptorPool(device, &new_descriptor_pool_create_info,
nullptr, &new_descriptor_pool) != VK_SUCCESS) {
XELOGE(
"LinkedTypeDescriptorSetAllocator: Failed to create a descriptor pool");
return VK_NULL_HANDLE;
}
descriptor_set_allocate_info.descriptorPool = new_descriptor_pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE("LinkedTypeDescriptorSetAllocator: Failed to allocate descriptors");
dfn.vkDestroyDescriptorPool(device, new_descriptor_pool, nullptr);
return VK_NULL_HANDLE;
}
if (new_page_becomes_full) {
pages_full_.push_back(new_descriptor_pool);
} else {
// Move the latest page that allocation couldn't be done in to the usable
// pages to replace it with the new one.
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
// Calculate the map key (the maximum number of remaining descriptors of
// any type).
uint32_t latest_page_max_descriptors_remaining =
page_usable_latest_.descriptors_remaining[0].descriptorCount;
for (uint32_t i = 1; i < descriptor_pool_size_count_; ++i) {
latest_page_max_descriptors_remaining = std::max(
latest_page_max_descriptors_remaining,
page_usable_latest_.descriptors_remaining[i].descriptorCount);
}
assert_not_zero(latest_page_max_descriptors_remaining);
pages_usable_.emplace(latest_page_max_descriptors_remaining,
std::move(page_usable_latest_));
}
page_usable_latest_.pool = new_descriptor_pool;
page_usable_latest_.descriptors_remaining =
std::unique_ptr<VkDescriptorPoolSize[]>(
new VkDescriptorPoolSize[descriptor_pool_size_count_]);
for (uint32_t i = 0; i < descriptor_pool_size_count_; ++i) {
const VkDescriptorPoolSize& descriptor_pool_size_for_type =
descriptor_pool_sizes_[i];
page_usable_latest_.descriptors_remaining[i] =
descriptor_pool_size_for_type;
for (uint32_t j = 0; j < descriptor_type_count; ++j) {
const VkDescriptorPoolSize& descriptor_count_for_type =
descriptor_counts[j];
if (descriptor_count_for_type.type !=
descriptor_pool_size_for_type.type) {
continue;
}
page_usable_latest_.descriptors_remaining[i].descriptorCount -=
descriptor_count_for_type.descriptorCount;
break;
}
}
page_usable_latest_.descriptor_sets_remaining =
descriptor_sets_per_page_ - 1;
}
return descriptor_set;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,125 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_LINKED_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_
#define XENIA_UI_VULKAN_LINKED_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
// Allocates multiple descriptors of in descriptor set layouts consisting of
// descriptors of types specified during initialization.
//
// "LinkedType" means that the allocator is designed for allocating descriptor
// sets containing descriptors of multiple types together - for instance, it
// will mark the entire page as full even if no space is left in it for just one
// of the descriptor types (not all at once).
//
// The primary usage scenario for this kind of an allocator is allocating image
// and sampler descriptors in a single descriptor set if they both are actually
// used in one. It is expected that the ratio of the numbers of descriptors per
// type specified during the initialization will roughly correspond to the ratio
// of the numbers of descriptors that will actually be allocated. For instance,
// if there are approximately 2 images for each 1 sampler, it's recommended to
// make the image count per page twice the sampler count per page.
//
// If some allocations use just one type, and some use just another, completely
// independently, it's preferable to use separate allocators rather than a
// single one.
//
// This allocator is also suitable for allocating variable-length descriptor
// sets containing descriptors of just a single type.
//
// There's no way to free these descriptors within the allocator object itself,
// per-layout free lists should be used externally.
class LinkedTypeDescriptorSetAllocator {
public:
// Multiple descriptor sizes for the same descriptor type, and zero sizes, are
// not allowed.
explicit LinkedTypeDescriptorSetAllocator(
const ui::vulkan::VulkanProvider& provider,
const VkDescriptorPoolSize* descriptor_sizes,
uint32_t descriptor_size_count, uint32_t descriptor_sets_per_page)
: provider_(provider),
descriptor_pool_sizes_(new VkDescriptorPoolSize[descriptor_size_count]),
descriptor_pool_size_count_(descriptor_size_count),
descriptor_sets_per_page_(descriptor_sets_per_page) {
assert_not_zero(descriptor_size_count);
assert_not_zero(descriptor_sets_per_page_);
#ifndef NDEBUG
for (uint32_t i = 0; i < descriptor_size_count; ++i) {
const VkDescriptorPoolSize& descriptor_size = descriptor_sizes[i];
assert_not_zero(descriptor_size.descriptorCount);
for (uint32_t j = 0; j < i; ++j) {
assert_true(descriptor_sizes[j].type != descriptor_size.type);
}
}
#endif
std::memcpy(descriptor_pool_sizes_.get(), descriptor_sizes,
sizeof(VkDescriptorPoolSize) * descriptor_size_count);
}
LinkedTypeDescriptorSetAllocator(
const LinkedTypeDescriptorSetAllocator& allocator) = delete;
LinkedTypeDescriptorSetAllocator& operator=(
const LinkedTypeDescriptorSetAllocator& allocator) = delete;
~LinkedTypeDescriptorSetAllocator() { Reset(); }
void Reset();
VkDescriptorSet Allocate(VkDescriptorSetLayout descriptor_set_layout,
const VkDescriptorPoolSize* descriptor_counts,
uint32_t descriptor_type_count);
private:
struct Page {
VkDescriptorPool pool;
std::unique_ptr<VkDescriptorPoolSize[]> descriptors_remaining;
uint32_t descriptor_sets_remaining;
};
const ui::vulkan::VulkanProvider& provider_;
std::unique_ptr<VkDescriptorPoolSize[]> descriptor_pool_sizes_;
uint32_t descriptor_pool_size_count_;
uint32_t descriptor_sets_per_page_;
std::vector<VkDescriptorPool> pages_full_;
// Because allocations must be contiguous, overflow may happen even if a page
// still has free descriptors, so multiple pages may have free space.
// To avoid removing and re-adding the page to the map that keeps them sorted
// (the key is the maximum number of free descriptors remaining across all
// types - and lookups need to be made with the maximum of the requested
// number of descriptors across all types since it's pointless to check the
// pages that can't even potentially fit the largest amount of descriptors of
// a requested type, and unlike using the minimum as the key, this doesn't
// degenerate if, for example, 0 descriptors are requested for some type - and
// it changes at every allocation from a page), instead of always looking for
// a free space in the map, maintaining one page outside the map, and
// allocation attempts will be made from that page first.
std::multimap<uint32_t, Page> pages_usable_;
// Doesn't exist if page_usable_latest_.pool == VK_NULL_HANDLE.
Page page_usable_latest_ = {};
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_CONNECTED_DESCRIPTOR_SET_ALLOCATOR_H_

View File

@@ -1,216 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/single_type_descriptor_set_allocator.h"
#include "xenia/base/logging.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
void SingleTypeDescriptorSetAllocator::Reset() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
page_usable_latest_.pool);
for (const std::pair<uint32_t, Page>& page_pair : pages_usable_) {
dfn.vkDestroyDescriptorPool(device, page_pair.second.pool, nullptr);
}
pages_usable_.clear();
for (VkDescriptorPool pool : pages_full_) {
dfn.vkDestroyDescriptorPool(device, pool, nullptr);
}
pages_full_.clear();
}
VkDescriptorSet SingleTypeDescriptorSetAllocator::Allocate(
VkDescriptorSetLayout descriptor_set_layout, uint32_t descriptor_count) {
assert_not_zero(descriptor_count);
if (descriptor_count == 0) {
return VK_NULL_HANDLE;
}
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkDescriptorSetAllocateInfo descriptor_set_allocate_info;
descriptor_set_allocate_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_allocate_info.pNext = nullptr;
descriptor_set_allocate_info.descriptorSetCount = 1;
descriptor_set_allocate_info.pSetLayouts = &descriptor_set_layout;
VkDescriptorSet descriptor_set;
if (descriptor_count > descriptor_pool_size_.descriptorCount) {
// Can't allocate in the pool, need a dedicated allocation.
VkDescriptorPoolSize dedicated_descriptor_pool_size;
dedicated_descriptor_pool_size.type = descriptor_pool_size_.type;
dedicated_descriptor_pool_size.descriptorCount = descriptor_count;
VkDescriptorPoolCreateInfo dedicated_descriptor_pool_create_info;
dedicated_descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
dedicated_descriptor_pool_create_info.pNext = nullptr;
dedicated_descriptor_pool_create_info.flags = 0;
dedicated_descriptor_pool_create_info.maxSets = 1;
dedicated_descriptor_pool_create_info.poolSizeCount = 1;
dedicated_descriptor_pool_create_info.pPoolSizes =
&dedicated_descriptor_pool_size;
VkDescriptorPool dedicated_descriptor_pool;
if (dfn.vkCreateDescriptorPool(
device, &dedicated_descriptor_pool_create_info, nullptr,
&dedicated_descriptor_pool) != VK_SUCCESS) {
XELOGE(
"SingleTypeDescriptorSetAllocator: Failed to create a dedicated pool "
"for {} descriptors",
dedicated_descriptor_pool_size.descriptorCount);
return VK_NULL_HANDLE;
}
descriptor_set_allocate_info.descriptorPool = dedicated_descriptor_pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE(
"SingleTypeDescriptorSetAllocator: Failed to allocate {} descriptors "
"in a dedicated pool",
descriptor_count);
dfn.vkDestroyDescriptorPool(device, dedicated_descriptor_pool, nullptr);
return VK_NULL_HANDLE;
}
pages_full_.push_back(dedicated_descriptor_pool);
return descriptor_set;
}
// Try allocating from the latest page an allocation has happened from, to
// avoid detaching from the map and re-attaching for every allocation.
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
assert_not_zero(page_usable_latest_.descriptors_remaining);
assert_not_zero(page_usable_latest_.descriptor_sets_remaining);
if (page_usable_latest_.descriptors_remaining >= descriptor_count) {
descriptor_set_allocate_info.descriptorPool = page_usable_latest_.pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) == VK_SUCCESS) {
page_usable_latest_.descriptors_remaining -= descriptor_count;
--page_usable_latest_.descriptor_sets_remaining;
if (!page_usable_latest_.descriptors_remaining ||
!page_usable_latest_.descriptor_sets_remaining) {
pages_full_.push_back(page_usable_latest_.pool);
page_usable_latest_.pool = VK_NULL_HANDLE;
}
return descriptor_set;
}
// Failed to allocate internally even though there should be enough space,
// don't try to allocate from this pool again at all.
pages_full_.push_back(page_usable_latest_.pool);
page_usable_latest_.pool = VK_NULL_HANDLE;
}
}
// If allocating from the latest pool wasn't possible, pick any that has free
// space. Prefer filling pages that have the most free space as they can more
// likely be used for more allocations later.
while (!pages_usable_.empty()) {
auto page_usable_last_it = std::prev(pages_usable_.cend());
if (page_usable_last_it->second.descriptors_remaining < descriptor_count) {
// All other pages_usable_ entries have fewer free descriptors too (the
// remaining count is the map key).
break;
}
// Remove the page from the map unconditionally - in case of a successful
// allocation, it will have a different number of free descriptors, thus a
// new map key (but it will also become page_usable_latest_ instead even),
// or will become full, and in case of a failure to allocate internally even
// though there still should be enough space, it should never be allocated
// from again.
Page map_page = page_usable_last_it->second;
pages_usable_.erase(page_usable_last_it);
descriptor_set_allocate_info.descriptorPool = map_page.pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
pages_full_.push_back(map_page.pool);
continue;
}
map_page.descriptors_remaining -= descriptor_count;
--map_page.descriptor_sets_remaining;
if (!map_page.descriptors_remaining ||
!map_page.descriptor_sets_remaining) {
pages_full_.push_back(map_page.pool);
} else {
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
// Make the page with more free descriptors the next to allocate from.
if (map_page.descriptors_remaining >
page_usable_latest_.descriptors_remaining) {
pages_usable_.emplace(page_usable_latest_.descriptors_remaining,
page_usable_latest_);
page_usable_latest_ = map_page;
} else {
pages_usable_.emplace(map_page.descriptors_remaining, map_page);
}
} else {
page_usable_latest_ = map_page;
}
}
return descriptor_set;
}
// Try allocating from a new page.
VkDescriptorPoolCreateInfo new_descriptor_pool_create_info;
new_descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
new_descriptor_pool_create_info.pNext = nullptr;
new_descriptor_pool_create_info.flags = 0;
new_descriptor_pool_create_info.maxSets = descriptor_sets_per_page_;
new_descriptor_pool_create_info.poolSizeCount = 1;
new_descriptor_pool_create_info.pPoolSizes = &descriptor_pool_size_;
VkDescriptorPool new_descriptor_pool;
if (dfn.vkCreateDescriptorPool(device, &new_descriptor_pool_create_info,
nullptr, &new_descriptor_pool) != VK_SUCCESS) {
XELOGE(
"SingleTypeDescriptorSetAllocator: Failed to create a pool for {} sets "
"with {} descriptors",
descriptor_sets_per_page_, descriptor_pool_size_.descriptorCount);
return VK_NULL_HANDLE;
}
descriptor_set_allocate_info.descriptorPool = new_descriptor_pool;
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE(
"SingleTypeDescriptorSetAllocator: Failed to allocate {} descriptors",
descriptor_count);
dfn.vkDestroyDescriptorPool(device, new_descriptor_pool, nullptr);
return VK_NULL_HANDLE;
}
Page new_page;
new_page.pool = new_descriptor_pool;
new_page.descriptors_remaining =
descriptor_pool_size_.descriptorCount - descriptor_count;
new_page.descriptor_sets_remaining = descriptor_sets_per_page_ - 1;
if (!new_page.descriptors_remaining || !new_page.descriptor_sets_remaining) {
pages_full_.push_back(new_page.pool);
} else {
if (page_usable_latest_.pool != VK_NULL_HANDLE) {
// Make the page with more free descriptors the next to allocate from.
if (new_page.descriptors_remaining >
page_usable_latest_.descriptors_remaining) {
pages_usable_.emplace(page_usable_latest_.descriptors_remaining,
page_usable_latest_);
page_usable_latest_ = new_page;
} else {
pages_usable_.emplace(new_page.descriptors_remaining, new_page);
}
} else {
page_usable_latest_ = new_page;
}
}
return descriptor_set;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,84 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_
#define XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_
#include <algorithm>
#include <cstdint>
#include <map>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
// Allocates multiple descriptors of a single type in descriptor set layouts
// consisting of descriptors of only that type. There's no way to free these
// descriptors within the SingleTypeDescriptorSetAllocator, per-layout free
// lists should be used externally.
class SingleTypeDescriptorSetAllocator {
public:
explicit SingleTypeDescriptorSetAllocator(
const ui::vulkan::VulkanProvider& provider,
VkDescriptorType descriptor_type, uint32_t descriptors_per_page,
uint32_t descriptor_sets_per_page)
: provider_(provider),
descriptor_sets_per_page_(descriptor_sets_per_page) {
assert_not_zero(descriptor_sets_per_page_);
descriptor_pool_size_.type = descriptor_type;
// Not allocating sets with 0 descriptors using the allocator - pointless to
// have the descriptor count below the set count.
descriptor_pool_size_.descriptorCount =
std::max(descriptors_per_page, descriptor_sets_per_page);
}
SingleTypeDescriptorSetAllocator(
const SingleTypeDescriptorSetAllocator& allocator) = delete;
SingleTypeDescriptorSetAllocator& operator=(
const SingleTypeDescriptorSetAllocator& allocator) = delete;
~SingleTypeDescriptorSetAllocator() { Reset(); }
void Reset();
VkDescriptorSet Allocate(VkDescriptorSetLayout descriptor_set_layout,
uint32_t descriptor_count);
private:
struct Page {
VkDescriptorPool pool;
uint32_t descriptors_remaining;
uint32_t descriptor_sets_remaining;
};
const ui::vulkan::VulkanProvider& provider_;
VkDescriptorPoolSize descriptor_pool_size_;
uint32_t descriptor_sets_per_page_;
std::vector<VkDescriptorPool> pages_full_;
// Because allocations must be contiguous, overflow may happen even if a page
// still has free descriptors, so multiple pages may have free space.
// To avoid removing and re-adding the page to the map that keeps them sorted
// (the key is the number of free descriptors remaining, and it changes at
// every allocation from a page), instead of always looking for a free space
// in the map, maintaining one page outside the map, and allocation attempts
// will be made from that page first.
std::multimap<uint32_t, Page> pages_usable_;
// Doesn't exist if page_usable_latest_.pool == VK_NULL_HANDLE.
Page page_usable_latest_ = {};
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_