[Vulkan] Basic texture descriptor set allocation/binding

This commit is contained in:
Triang3l
2022-05-17 22:42:28 +03:00
parent 3381d679b4
commit 46202dd27a
17 changed files with 1561 additions and 492 deletions

View File

@@ -94,8 +94,7 @@ size_t SingleLayoutDescriptorSetPool::Allocate() {
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE(
"SingleLayoutDescriptorSetPool: Failed to allocate a descriptor "
"layout");
"SingleLayoutDescriptorSetPool: Failed to allocate a descriptor set");
if (current_pool_sets_remaining_ >= pool_set_count_) {
// Failed to allocate in a new pool - something completely wrong, don't
// store empty pools as full.

View File

@@ -0,0 +1,216 @@
/**
******************************************************************************
* 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 = pages_usable_.crend()->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

@@ -0,0 +1,84 @@
/**
******************************************************************************
* 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_

View File

@@ -1,162 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/transient_descriptor_pool.h"
#include <cstdint>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
namespace xe {
namespace ui {
namespace vulkan {
TransientDescriptorPool::TransientDescriptorPool(
const VulkanProvider& provider, VkDescriptorType descriptor_type,
uint32_t page_descriptor_set_count, uint32_t page_descriptor_count)
: provider_(provider),
descriptor_type_(descriptor_type),
page_descriptor_set_count_(page_descriptor_set_count),
page_descriptor_count_(page_descriptor_count) {
assert_not_zero(page_descriptor_set_count);
assert_true(page_descriptor_set_count <= page_descriptor_count);
}
TransientDescriptorPool::~TransientDescriptorPool() { ClearCache(); }
void TransientDescriptorPool::Reclaim(uint64_t completed_submission_index) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
while (!pages_submitted_.empty()) {
const auto& descriptor_pool_pair = pages_submitted_.front();
if (descriptor_pool_pair.second > completed_submission_index) {
break;
}
dfn.vkResetDescriptorPool(device, descriptor_pool_pair.first, 0);
pages_writable_.push_back(descriptor_pool_pair.first);
pages_submitted_.pop_front();
}
}
void TransientDescriptorPool::ClearCache() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
for (const auto& descriptor_pool_pair : pages_submitted_) {
dfn.vkDestroyDescriptorPool(device, descriptor_pool_pair.first, nullptr);
}
pages_submitted_.clear();
page_current_descriptors_used_ = 0;
page_current_descriptor_sets_used_ = 0;
page_current_last_submission_ = 0;
for (VkDescriptorPool descriptor_pool : pages_writable_) {
dfn.vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
}
pages_writable_.clear();
}
VkDescriptorSet TransientDescriptorPool::Request(
uint64_t submission_index, VkDescriptorSetLayout layout,
uint32_t layout_descriptor_count) {
assert_true(submission_index >= page_current_last_submission_);
assert_not_zero(layout_descriptor_count);
assert_true(layout_descriptor_count <= page_descriptor_count_);
const 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 = &layout;
VkDescriptorSet descriptor_set;
// Try to allocate as normal.
// TODO(Triang3l): Investigate the possibility of reuse of descriptor sets, as
// vkAllocateDescriptorSets may be implemented suboptimally.
if (!pages_writable_.empty()) {
if (page_current_descriptor_sets_used_ < page_descriptor_set_count_ &&
page_current_descriptors_used_ + layout_descriptor_count <=
page_descriptor_count_) {
descriptor_set_allocate_info.descriptorPool = pages_writable_.front();
switch (dfn.vkAllocateDescriptorSets(
device, &descriptor_set_allocate_info, &descriptor_set)) {
case VK_SUCCESS:
page_current_last_submission_ = submission_index;
++page_current_descriptor_sets_used_;
page_current_descriptors_used_ += layout_descriptor_count;
return descriptor_set;
case VK_ERROR_FRAGMENTED_POOL:
case VK_ERROR_OUT_OF_POOL_MEMORY:
// Need to create a new pool.
break;
default:
XELOGE(
"Failed to allocate a transient Vulkan descriptor set with {} "
"descriptors of type {}",
layout_descriptor_count, uint32_t(descriptor_type_));
return VK_NULL_HANDLE;
}
}
// Overflow - go to the next pool.
pages_submitted_.emplace_back(pages_writable_.front(),
page_current_last_submission_);
pages_writable_.front() = pages_writable_.back();
pages_writable_.pop_back();
page_current_descriptor_sets_used_ = 0;
page_current_descriptors_used_ = 0;
}
if (pages_writable_.empty()) {
VkDescriptorPoolSize descriptor_pool_size;
descriptor_pool_size.type = descriptor_type_;
descriptor_pool_size.descriptorCount = page_descriptor_count_;
VkDescriptorPoolCreateInfo descriptor_pool_create_info;
descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_create_info.pNext = nullptr;
descriptor_pool_create_info.flags = 0;
descriptor_pool_create_info.maxSets = page_descriptor_set_count_;
descriptor_pool_create_info.poolSizeCount = 1;
descriptor_pool_create_info.pPoolSizes = &descriptor_pool_size;
VkDescriptorPool descriptor_pool;
if (dfn.vkCreateDescriptorPool(device, &descriptor_pool_create_info,
nullptr, &descriptor_pool) != VK_SUCCESS) {
XELOGE(
"Failed to create a transient Vulkan descriptor pool for {} sets of "
"up to {} descriptors of type {}",
page_descriptor_set_count_, page_descriptor_count_,
uint32_t(descriptor_type_));
return VK_NULL_HANDLE;
}
pages_writable_.push_back(descriptor_pool);
}
// Try to allocate after handling overflow.
descriptor_set_allocate_info.descriptorPool = pages_writable_.front();
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
&descriptor_set) != VK_SUCCESS) {
XELOGE(
"Failed to allocate a transient Vulkan descriptor set with {} "
"descriptors of type {}",
layout_descriptor_count, uint32_t(descriptor_type_));
return VK_NULL_HANDLE;
}
page_current_last_submission_ = submission_index;
++page_current_descriptor_sets_used_;
page_current_descriptors_used_ += layout_descriptor_count;
return descriptor_set;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,61 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_
#define XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_
#include <cstdint>
#include <deque>
#include <utility>
#include <vector>
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
// A pool of descriptor pools for single-submission use. For simplicity of
// tracking when overflow happens, only allocating descriptors for sets
// containing descriptors of a single type.
class TransientDescriptorPool {
public:
TransientDescriptorPool(const VulkanProvider& provider,
VkDescriptorType descriptor_type,
uint32_t page_descriptor_set_count,
uint32_t page_descriptor_count);
~TransientDescriptorPool();
void Reclaim(uint64_t completed_submission_index);
void ClearCache();
// Returns the allocated set, or VK_NULL_HANDLE if failed to allocate.
VkDescriptorSet Request(uint64_t submission_index,
VkDescriptorSetLayout layout,
uint32_t layout_descriptor_count);
private:
const VulkanProvider& provider_;
VkDescriptorType descriptor_type_;
uint32_t page_descriptor_set_count_;
uint32_t page_descriptor_count_;
std::vector<VkDescriptorPool> pages_writable_;
uint64_t page_current_last_submission_ = 0;
uint32_t page_current_descriptor_sets_used_ = 0;
uint32_t page_current_descriptors_used_ = 0;
std::deque<std::pair<VkDescriptorPool, uint64_t>> pages_submitted_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_