[Vulkan] Refactoring and fixes for VulkanProvider and related areas

Enable portability subset physical device enumeration.

Don't use Vulkan 1.1+ logical devices on Vulkan 1.0 instances due to the
VkApplicationInfo::apiVersion specification.

Make sure all extension dependencies are enabled when creating a device.

Prefer exposing feature support over extension support via the device
interface to avoid causing confusion with regard to promoted extensions
(especially those that required some features as extensions, but had those
features made optional when they were promoted).

Allow creating presentation-only devices, not demanding any optional
features beyond the basic Vulkan 1.0, for use cases such as internal tools
or CPU rendering.

Require the independentBlend feature for GPU emulation as working around is
complicated, while support is almost ubiquitous.

Move the graphics system initialization fatal error message to xenia_main
after attempting to initialize all implementations, for automatic fallback
to other implementations in the future.

Log Vulkan driver info.

Improve Vulkan debug message logging, enabled by default.

Refactor code, with simplified logic for enabling extensions and layers.
This commit is contained in:
Triang3l
2025-08-12 23:21:59 +03:00
parent a06be03f1b
commit b5432ab83f
70 changed files with 3238 additions and 2757 deletions

View File

@@ -22,8 +22,8 @@ namespace ui {
namespace vulkan {
void LinkedTypeDescriptorSetAllocator::Reset() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
page_usable_latest_.pool);
page_usable_latest_.descriptors_remaining.reset();
@@ -53,8 +53,8 @@ VkDescriptorSet LinkedTypeDescriptorSetAllocator::Allocate(
}
#endif
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkDescriptorSetAllocateInfo descriptor_set_allocate_info;
descriptor_set_allocate_info.sType =

View File

@@ -18,7 +18,7 @@
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
@@ -54,13 +54,15 @@ class LinkedTypeDescriptorSetAllocator {
// 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),
const VulkanDevice* const vulkan_device,
const VkDescriptorPoolSize* const descriptor_sizes,
const uint32_t descriptor_size_count,
const uint32_t descriptor_sets_per_page)
: vulkan_device_(vulkan_device),
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_null(vulkan_device);
assert_not_zero(descriptor_size_count);
assert_not_zero(descriptor_sets_per_page_);
#ifndef NDEBUG
@@ -94,7 +96,7 @@ class LinkedTypeDescriptorSetAllocator {
uint32_t descriptor_sets_remaining;
};
const ui::vulkan::VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
std::unique_ptr<VkDescriptorPoolSize[]> descriptor_pool_sizes_;
uint32_t descriptor_pool_size_count_;

View File

@@ -17,13 +17,14 @@ namespace ui {
namespace vulkan {
SingleLayoutDescriptorSetPool::SingleLayoutDescriptorSetPool(
const VulkanProvider& provider, uint32_t pool_set_count,
uint32_t set_layout_descriptor_counts_count,
const VkDescriptorPoolSize* set_layout_descriptor_counts,
VkDescriptorSetLayout set_layout)
: provider_(provider),
const VulkanDevice* const vulkan_device, const uint32_t pool_set_count,
const uint32_t set_layout_descriptor_counts_count,
const VkDescriptorPoolSize* const set_layout_descriptor_counts,
const VkDescriptorSetLayout set_layout)
: vulkan_device_(vulkan_device),
pool_set_count_(pool_set_count),
set_layout_(set_layout) {
assert_not_null(vulkan_device);
assert_not_zero(pool_set_count);
pool_descriptor_counts_.resize(set_layout_descriptor_counts_count);
for (uint32_t i = 0; i < set_layout_descriptor_counts_count; ++i) {
@@ -38,8 +39,8 @@ SingleLayoutDescriptorSetPool::SingleLayoutDescriptorSetPool(
}
SingleLayoutDescriptorSetPool::~SingleLayoutDescriptorSetPool() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (current_pool_ != VK_NULL_HANDLE) {
dfn.vkDestroyDescriptorPool(device, current_pool_, nullptr);
}
@@ -55,8 +56,8 @@ size_t SingleLayoutDescriptorSetPool::Allocate() {
return free_index;
}
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
// Two iterations so if vkAllocateDescriptorSets fails even with a non-zero
// current_pool_sets_remaining_, another attempt will be made in a new pool.

View File

@@ -15,7 +15,7 @@
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
@@ -28,7 +28,7 @@ class SingleLayoutDescriptorSetPool {
// set count will be done internally). The descriptor set layout must not be
// destroyed until this object is also destroyed.
SingleLayoutDescriptorSetPool(
const VulkanProvider& provider, uint32_t pool_set_count,
const VulkanDevice* vulkan_device, uint32_t pool_set_count,
uint32_t set_layout_descriptor_counts_count,
const VkDescriptorPoolSize* set_layout_descriptor_counts,
VkDescriptorSetLayout set_layout);
@@ -43,7 +43,7 @@ class SingleLayoutDescriptorSetPool {
VkDescriptorSet Get(size_t index) const { return descriptor_sets_[index]; }
private:
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
uint32_t pool_set_count_;
std::vector<VkDescriptorPoolSize> pool_descriptor_counts_;
VkDescriptorSetLayout set_layout_;

View File

@@ -0,0 +1,88 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
namespace xe {
namespace ui {
namespace vulkan {
std::unique_ptr<UISamplers> UISamplers::Create(
const VulkanDevice* const vulkan_device) {
assert_not_null(vulkan_device);
std::unique_ptr<UISamplers> ui_samplers(new UISamplers(vulkan_device));
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VkSamplerCreateInfo sampler_create_info = {
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
for (int sampler_index = 0; sampler_index < kSamplerCount; ++sampler_index) {
if (sampler_index == kSamplerIndexLinearRepeat ||
sampler_index == kSamplerIndexLinearClampToEdge) {
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
} else {
sampler_create_info.magFilter = VK_FILTER_NEAREST;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
sampler_create_info.minFilter = sampler_create_info.magFilter;
if (sampler_index == kSamplerIndexNearestClampToEdge ||
sampler_index == kSamplerIndexLinearClampToEdge) {
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
} else {
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
sampler_create_info.addressModeV = sampler_create_info.addressModeU;
sampler_create_info.addressModeW = sampler_create_info.addressModeU;
const VkResult sampler_create_result =
dfn.vkCreateSampler(device, &sampler_create_info, nullptr,
&ui_samplers->samplers_[sampler_index]);
if (sampler_create_result != VK_SUCCESS) {
XELOGE(
"Failed to create the Vulkan UI sampler with filter {}, addressing "
"mode {}: {}",
vk::to_string(vk::Filter(sampler_create_info.magFilter)),
vk::to_string(
vk::SamplerAddressMode(sampler_create_info.addressModeU)),
vk::to_string(vk::Result(sampler_create_result)));
return nullptr;
}
}
return ui_samplers;
}
UISamplers::~UISamplers() {
for (const VkSampler sampler : samplers_) {
if (sampler == VK_NULL_HANDLE) {
continue;
}
vulkan_device_->functions().vkDestroySampler(vulkan_device_->device(),
sampler, nullptr);
}
}
UISamplers::UISamplers(const VulkanDevice* vulkan_device)
: vulkan_device_(vulkan_device) {
assert_not_null(vulkan_device);
samplers_.fill(VK_NULL_HANDLE);
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,63 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_UI_SAMPLERS_H_
#define XENIA_UI_VULKAN_VULKAN_UI_SAMPLERS_H_
#include <array>
#include <memory>
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
namespace vulkan {
/// Samplers that can be used for presentation and UI drawing.
/// Because maxSamplerAllocationCount can be as low as 4000 (on Nvidia and Intel
/// GPUs primarily), no other samplers must be created for UI purposes.
/// The rest of the sampler allocation space on the device must be available to
/// GPU emulation.
class UISamplers {
public:
static std::unique_ptr<UISamplers> Create(const VulkanDevice* vulkan_device);
UISamplers(const UISamplers&) = delete;
UISamplers& operator=(const UISamplers&) = delete;
UISamplers(UISamplers&&) = delete;
UISamplers& operator=(UISamplers&&) = delete;
~UISamplers();
enum SamplerIndex {
kSamplerIndexNearestRepeat,
kSamplerIndexNearestClampToEdge,
kSamplerIndexLinearRepeat,
kSamplerIndexLinearClampToEdge,
kSamplerCount,
};
const std::array<VkSampler, kSamplerCount>& samplers() const {
return samplers_;
}
private:
explicit UISamplers(const VulkanDevice* vulkan_device);
const VulkanDevice* vulkan_device_;
std::array<VkSampler, kSamplerCount> samplers_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_UI_SAMPLERS_H_

View File

@@ -0,0 +1,50 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_API_H_
#define XENIA_UI_VULKAN_VULKAN_API_H_
#include "xenia/base/assert.h" // For Vulkan-Hpp.
#include "xenia/base/platform.h"
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES
#endif
#ifndef VK_ENABLE_BETA_EXTENSIONS
#define VK_ENABLE_BETA_EXTENSIONS
#endif
#if XE_PLATFORM_ANDROID
#ifndef VK_USE_PLATFORM_ANDROID_KHR
#define VK_USE_PLATFORM_ANDROID_KHR
#endif
#endif
#if XE_PLATFORM_GNU_LINUX
#ifndef VK_USE_PLATFORM_XCB_KHR
#define VK_USE_PLATFORM_XCB_KHR
#endif
#endif
#if XE_PLATFORM_WIN32
// Must be included before including vulkan.h with VK_USE_PLATFORM_WIN32_KHR
// because it includes Windows.h too.
#include "xenia/base/platform_win.h"
#ifndef VK_USE_PLATFORM_WIN32_KHR
#define VK_USE_PLATFORM_WIN32_KHR
#endif
#endif
#include "third_party/Vulkan-Headers/include/vulkan/vulkan.h"
#include "third_party/Vulkan-Headers/include/vulkan/vulkan_hpp_macros.hpp"
#include "third_party/Vulkan-Headers/include/vulkan/vulkan_to_string.hpp"
#endif // XENIA_UI_VULKAN_VULKAN_API_H_

View File

@@ -0,0 +1,828 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <unordered_map>
#include <vector>
namespace xe {
namespace ui {
namespace vulkan {
template <typename Structure, VkStructureType StructureType>
struct VulkanFeatures {
Structure supported = {StructureType};
Structure enabled = {StructureType};
void Link(VkPhysicalDeviceFeatures2& supported_features_2,
VkDeviceCreateInfo& device_create_info) {
supported.pNext = supported_features_2.pNext;
supported_features_2.pNext = &supported;
enabled.pNext = const_cast<void*>(device_create_info.pNext);
device_create_info.pNext = &enabled;
}
};
std::unique_ptr<VulkanDevice> VulkanDevice::CreateIfSupported(
const VulkanInstance* const vulkan_instance,
const VkPhysicalDevice physical_device, const bool with_gpu_emulation,
const bool with_swapchain) {
assert_not_null(vulkan_instance);
assert_not_null(physical_device);
const VulkanInstance::Functions& ifn = vulkan_instance->functions();
// Get supported Vulkan 1.0 properties and features.
VkPhysicalDeviceProperties properties = {};
ifn.vkGetPhysicalDeviceProperties(physical_device, &properties);
const uint32_t unclamped_api_version = properties.apiVersion;
if (vulkan_instance->api_version() < VK_MAKE_API_VERSION(0, 1, 1, 0)) {
// From the VkApplicationInfo specification:
//
// "The Khronos validation layers will treat apiVersion as the highest API
// version the application targets, and will validate API usage against the
// minimum of that version and the implementation version (instance or
// device, depending on context). If an application tries to use
// functionality from a greater version than this, a validation error will
// be triggered."
//
// "Vulkan 1.0 implementations were required to return
// VK_ERROR_INCOMPATIBLE_DRIVER if apiVersion was larger than 1.0."
properties.apiVersion = VK_MAKE_API_VERSION(
0, 1, 0, VK_API_VERSION_PATCH(properties.apiVersion));
}
VkPhysicalDeviceFeatures supported_features = {};
ifn.vkGetPhysicalDeviceFeatures(physical_device, &supported_features);
if (with_gpu_emulation) {
if (!supported_features.independentBlend) {
// Not trivial to work around:
// - Affects not only the blend equation, but also the color write mask.
// - Can't reuse the blend state of the first attachment for all because
// some attachments may have a format that doesn't support blending.
// - Not possible to split the draw into per-attachment draws because of
// depth / stencil.
// Not supported only on the proprietary driver for the Qualcomm
// Adreno 4xx, where the driver is largely experimental and doesn't expose
// a lot of the functionality available in the hardware.
XELOGW(
"Vulkan device '{}' doesn't support the independentBlend feature "
"required for GPU emulation",
properties.deviceName);
return nullptr;
}
}
// Enable needed extensions.
std::unique_ptr<VulkanDevice> device(
new VulkanDevice(vulkan_instance, physical_device));
const bool get_physical_device_properties2_supported =
vulkan_instance->extensions().ext_1_1_KHR_get_physical_device_properties2;
// Name pointers from `requested_extensions` will be used in the enabled
// extensions vector.
std::unordered_map<std::string, bool*> requested_extensions;
const auto request_promoted_extension =
[&](const char* const name, uint32_t const major, uint32_t const minor,
bool* const supported_ptr) {
assert_not_null(supported_ptr);
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, major, minor, 0)) {
*supported_ptr = true;
} else {
requested_extensions.emplace(name, supported_ptr);
}
};
#define XE_UI_VULKAN_STRUCT_EXTENSION(name) \
requested_extensions.emplace("VK_" #name, &device->extensions_.ext_##name);
#define XE_UI_VULKAN_LOCAL_EXTENSION(name) \
requested_extensions.emplace("VK_" #name, &ext_##name);
#define XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(name, major, minor) \
request_promoted_extension( \
"VK_" #name, major, minor, \
&device->extensions_.ext_##major##_##minor##_##name);
#define XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(name, major, minor) \
request_promoted_extension("VK_" #name, major, minor, \
&ext_##major##_##minor##_##name);
bool ext_KHR_portability_subset = false;
bool ext_1_2_KHR_driver_properties = false;
if (get_physical_device_properties2_supported) {
// #164. Must be enabled according to the specification if the physical
// device is a portability subset one.
XE_UI_VULKAN_LOCAL_EXTENSION(KHR_portability_subset)
// #197
XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(KHR_driver_properties, 1, 2)
}
// Used by the Vulkan Memory Allocator and potentially by Xenia.
// #128.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_dedicated_allocation, 1, 1)
// #147. Also must be enabled for VK_KHR_dedicated_allocation and
// VK_KHR_sampler_ycbcr_conversion.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_get_memory_requirements2, 1, 1)
// #158. Also must be enabled for VK_KHR_sampler_ycbcr_conversion.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_bind_memory2, 1, 1)
if (get_physical_device_properties2_supported) {
// #238.
XE_UI_VULKAN_STRUCT_EXTENSION(EXT_memory_budget)
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
// #414.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_maintenance4, 1, 3)
}
if (with_swapchain) {
// #2.
XE_UI_VULKAN_STRUCT_EXTENSION(KHR_swapchain)
}
bool ext_1_2_KHR_sampler_mirror_clamp_to_edge = false;
bool ext_1_1_KHR_maintenance1 = false;
bool ext_1_2_KHR_shader_float_controls = false;
bool ext_EXT_fragment_shader_interlock = false;
bool ext_1_3_EXT_shader_demote_to_helper_invocation = false;
bool ext_EXT_non_seamless_cube_map = false;
if (with_gpu_emulation) {
// #15.
XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(KHR_sampler_mirror_clamp_to_edge, 1,
2)
// #70. Must be enabled for VK_KHR_sampler_ycbcr_conversion.
XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(KHR_maintenance1, 1, 1)
// #141.
XE_UI_VULKAN_STRUCT_EXTENSION(EXT_shader_stencil_export)
// #148.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_image_format_list, 1, 2)
if (get_physical_device_properties2_supported) {
// #157.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_sampler_ycbcr_conversion, 1, 1)
// #198. Also must be enabled for VK_KHR_spirv_1_4.
XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(KHR_shader_float_controls, 1, 2)
// #252.
XE_UI_VULKAN_LOCAL_EXTENSION(EXT_fragment_shader_interlock)
// #277.
XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION(
EXT_shader_demote_to_helper_invocation, 1, 3)
// #423.
XE_UI_VULKAN_LOCAL_EXTENSION(EXT_non_seamless_cube_map)
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
// #237.
XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION(KHR_spirv_1_4, 1, 2)
}
}
#undef XE_UI_VULKAN_STRUCT_EXTENSION
#undef XE_UI_VULKAN_LOCAL_EXTENSION
#undef XE_UI_VULKAN_STRUCT_PROMOTED_EXTENSION
#undef XE_UI_VULKAN_LOCAL_PROMOTED_EXTENSION
std::vector<const char*> enabled_extensions;
{
uint32_t supported_extension_count = 0;
const VkResult get_supported_extension_count_result =
ifn.vkEnumerateDeviceExtensionProperties(
physical_device, nullptr, &supported_extension_count, nullptr);
if (get_supported_extension_count_result != VK_SUCCESS &&
get_supported_extension_count_result != VK_INCOMPLETE) {
XELOGW("Failed to get the Vulkan device '{}' extension count",
properties.deviceName);
return nullptr;
}
if (supported_extension_count) {
std::vector<VkExtensionProperties> supported_extensions(
supported_extension_count);
if (ifn.vkEnumerateDeviceExtensionProperties(
physical_device, nullptr, &supported_extension_count,
supported_extensions.data()) != VK_SUCCESS) {
XELOGW("Failed to get the Vulkan device '{}' extensions",
properties.deviceName);
return nullptr;
}
assert_true(supported_extension_count == supported_extensions.size());
for (const VkExtensionProperties& supported_extension :
supported_extensions) {
const auto requested_extension_it =
requested_extensions.find(supported_extension.extensionName);
if (requested_extension_it == requested_extensions.cend()) {
continue;
}
assert_not_null(requested_extension_it->second);
if (!*requested_extension_it->second) {
enabled_extensions.emplace_back(
requested_extension_it->first.c_str());
*requested_extension_it->second = true;
}
}
}
}
if (with_swapchain && !device->extensions_.ext_KHR_swapchain) {
XELOGW("Vulkan device '{}' doesn't support swapchains",
properties.deviceName);
return nullptr;
}
VkDeviceCreateInfo device_create_info = {
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
device_create_info.enabledExtensionCount =
uint32_t(enabled_extensions.size());
device_create_info.ppEnabledExtensionNames = enabled_extensions.data();
// Get supported Vulkan 1.1+ and extension properties and features.
//
// The property and feature structures are initialized to zero or to the
// minimum / maximum requirements for the simplicity of handling unavailable
// VK_KHR_get_physical_device_properties2.
VkPhysicalDeviceProperties2 properties_2 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
VkPhysicalDeviceFeatures2 supported_features_2 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
VulkanFeatures<VkPhysicalDeviceVulkan12Features,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES>
features_1_2;
VulkanFeatures<VkPhysicalDeviceVulkan13Features,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES>
features_1_3;
VulkanFeatures<
VkPhysicalDevicePortabilitySubsetFeaturesKHR,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR>
features_KHR_portability_subset;
VkPhysicalDeviceDriverPropertiesKHR properties_1_2_KHR_driver_properties = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
VkPhysicalDeviceFloatControlsProperties
properties_1_2_KHR_shader_float_controls = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES};
VulkanFeatures<
VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT>
features_EXT_fragment_shader_interlock;
VulkanFeatures<
VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES_EXT>
features_1_3_EXT_shader_demote_to_helper_invocation;
VulkanFeatures<
VkPhysicalDeviceNonSeamlessCubeMapFeaturesEXT,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT>
features_EXT_non_seamless_cube_map;
if (get_physical_device_properties2_supported) {
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 2, 0)) {
features_1_2.Link(supported_features_2, device_create_info);
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
features_1_3.Link(supported_features_2, device_create_info);
} else {
if (ext_1_3_EXT_shader_demote_to_helper_invocation) {
features_1_3_EXT_shader_demote_to_helper_invocation.Link(
supported_features_2, device_create_info);
}
}
if (ext_KHR_portability_subset) {
features_KHR_portability_subset.Link(supported_features_2,
device_create_info);
}
if (ext_1_2_KHR_driver_properties) {
properties_1_2_KHR_driver_properties.pNext = properties_2.pNext;
properties_2.pNext = &properties_1_2_KHR_driver_properties;
}
if (ext_1_2_KHR_shader_float_controls) {
properties_1_2_KHR_shader_float_controls.pNext = properties_2.pNext;
properties_2.pNext = &properties_1_2_KHR_shader_float_controls;
}
if (ext_EXT_fragment_shader_interlock) {
features_EXT_fragment_shader_interlock.Link(supported_features_2,
device_create_info);
}
if (ext_EXT_non_seamless_cube_map) {
features_EXT_non_seamless_cube_map.Link(supported_features_2,
device_create_info);
}
ifn.vkGetPhysicalDeviceProperties2(physical_device, &properties_2);
ifn.vkGetPhysicalDeviceFeatures2(physical_device, &supported_features_2);
}
uint32_t queue_family_count = 0;
ifn.vkGetPhysicalDeviceQueueFamilyProperties(physical_device,
&queue_family_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
ifn.vkGetPhysicalDeviceQueueFamilyProperties(
physical_device, &queue_family_count, queue_families.data());
device->queue_families_.resize(queue_family_count);
uint32_t first_queue_family_graphics_compute_sparse_binding = UINT32_MAX;
uint32_t first_queue_family_graphics_compute = UINT32_MAX;
uint32_t first_queue_family_sparse_binding = UINT32_MAX;
bool has_presentation_queue_family = false;
for (uint32_t queue_family_index = 0; queue_family_index < queue_family_count;
++queue_family_index) {
QueueFamily& queue_family = device->queue_families_[queue_family_index];
const VkQueueFamilyProperties& queue_family_properties =
queue_families[queue_family_index];
const VkQueueFlags queue_unsupported_flags =
~queue_family_properties.queueFlags;
if (!(queue_unsupported_flags &
(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))) {
first_queue_family_graphics_compute =
std::min(queue_family_index, first_queue_family_graphics_compute);
}
if (with_gpu_emulation && supported_features.sparseBinding &&
!(queue_unsupported_flags & VK_QUEUE_SPARSE_BINDING_BIT)) {
first_queue_family_sparse_binding =
std::min(queue_family_index, first_queue_family_sparse_binding);
if (!(queue_unsupported_flags &
(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))) {
first_queue_family_graphics_compute_sparse_binding =
std::min(queue_family_index,
first_queue_family_graphics_compute_sparse_binding);
}
}
if (with_swapchain) {
#if XE_PLATFORM_WIN32
queue_family.may_support_presentation =
vulkan_instance->extensions().ext_KHR_win32_surface &&
ifn.vkGetPhysicalDeviceWin32PresentationSupportKHR(
physical_device, queue_family_index);
#else
queue_family.may_support_presentation = true;
#endif
if (queue_family.may_support_presentation) {
queue_family.queues.resize(
std::max(size_t(1), queue_family.queues.size()));
has_presentation_queue_family = true;
}
}
}
if (first_queue_family_graphics_compute == UINT32_MAX) {
// Not valid according to the Vulkan specification, but for safety.
XELOGW(
"Vulkan device '{}' doesn't provide a graphics and compute queue "
"family",
properties.deviceName);
return nullptr;
}
if (with_swapchain && !has_presentation_queue_family) {
XELOGW(
"Vulkan device '{}' doesn't provide a queue family that supports "
"presentation",
properties.deviceName);
return nullptr;
}
// Get the queues to create.
if (first_queue_family_sparse_binding == UINT32_MAX) {
// Not valid not to provide a sparse binding queue if the sparseBinding
// feature is supported according to the Vulkan specification, but for
// safety and simplicity.
supported_features.sparseBinding = VK_FALSE;
}
if (!supported_features.sparseBinding) {
supported_features.sparseResidencyBuffer = VK_FALSE;
supported_features.sparseResidencyImage2D = VK_FALSE;
supported_features.sparseResidencyImage3D = VK_FALSE;
supported_features.sparseResidency2Samples = VK_FALSE;
supported_features.sparseResidency4Samples = VK_FALSE;
supported_features.sparseResidency8Samples = VK_FALSE;
supported_features.sparseResidency16Samples = VK_FALSE;
supported_features.sparseResidencyAliased = VK_FALSE;
}
// Prefer using one queue for everything whenever possible for simplicity.
// TODO(Triang3l): Research if separate queues for purposes like composition,
// swapchain image presentation, and sparse binding, may be beneficial.
if (first_queue_family_graphics_compute_sparse_binding != UINT32_MAX) {
device->queue_family_graphics_compute_ =
first_queue_family_graphics_compute_sparse_binding;
device->queue_family_sparse_binding_ =
first_queue_family_graphics_compute_sparse_binding;
} else {
device->queue_family_graphics_compute_ =
first_queue_family_graphics_compute;
device->queue_family_sparse_binding_ = first_queue_family_sparse_binding;
}
device->queue_families_[device->queue_family_graphics_compute_].queues.resize(
std::max(size_t(1),
device->queue_families_[device->queue_family_graphics_compute_]
.queues.size()));
if (device->queue_family_sparse_binding_ != UINT32_MAX) {
device->queue_families_[device->queue_family_sparse_binding_].queues.resize(
std::max(size_t(1),
device->queue_families_[device->queue_family_sparse_binding_]
.queues.size()));
}
size_t max_enabled_queues_per_family = 0;
for (const QueueFamily& queue_family : device->queue_families_) {
max_enabled_queues_per_family =
std::max(queue_family.queues.size(), max_enabled_queues_per_family);
}
const std::vector<float> queue_priorities(max_enabled_queues_per_family,
1.0f);
std::vector<VkDeviceQueueCreateInfo> queue_create_infos;
for (size_t queue_family_index = 0;
queue_family_index < device->queue_families_.size();
++queue_family_index) {
const QueueFamily& queue_family =
device->queue_families_[queue_family_index];
if (queue_family.queues.empty()) {
continue;
}
VkDeviceQueueCreateInfo& queue_create_info =
queue_create_infos.emplace_back();
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_create_info.pNext = nullptr;
queue_create_info.flags = 0;
queue_create_info.queueFamilyIndex = uint32_t(queue_family_index);
queue_create_info.queueCount = uint32_t(queue_family.queues.size());
queue_create_info.pQueuePriorities = queue_priorities.data();
}
device_create_info.queueCreateInfoCount = uint32_t(queue_create_infos.size());
device_create_info.pQueueCreateInfos = queue_create_infos.data();
// Enable needed features and copy the properties.
//
// Enabling only actually used features because drivers may take more optimal
// paths when certain features are disabled. Also, in VK_EXT_shader_object,
// the state that the application must set for the draw depends on which
// features are enabled.
device->properties_.apiVersion = properties.apiVersion;
device->properties_.driverVersion = properties.driverVersion;
device->properties_.vendorID = properties.vendorID;
device->properties_.deviceID = properties.deviceID;
std::strcpy(device->properties_.deviceName, properties.deviceName);
XELOGI(
"Vulkan device '{}': API {}.{}.{}, vendor 0x{:04X}, device 0x{:04X}, "
"driver version 0x{:X}",
properties.deviceName, VK_VERSION_MAJOR(properties.apiVersion),
VK_VERSION_MINOR(properties.apiVersion),
VK_VERSION_PATCH(properties.apiVersion), properties.vendorID,
properties.deviceID, properties.driverVersion);
if (unclamped_api_version != properties.apiVersion) {
XELOGI(
"Device supports Vulkan API {}.{}.{}, but the used version is limited "
"by the instance",
VK_VERSION_MAJOR(unclamped_api_version),
VK_VERSION_MINOR(unclamped_api_version),
VK_VERSION_PATCH(unclamped_api_version));
}
XELOGI("Enabled Vulkan device extensions:");
for (uint32_t enabled_extension_index = 0;
enabled_extension_index < device_create_info.enabledExtensionCount;
++enabled_extension_index) {
XELOGI("* {}",
device_create_info.ppEnabledExtensionNames[enabled_extension_index]);
}
XELOGI("Vulkan device properties and enabled features:");
VkPhysicalDeviceFeatures enabled_features = {};
device_create_info.pEnabledFeatures = &enabled_features;
#define XE_UI_VULKAN_LIMIT(name) \
device->properties_.name = properties.limits.name; \
XELOGI("* " #name ": {}", properties.limits.name);
#define XE_UI_VULKAN_ENUM_LIMIT(name, type) \
device->properties_.name = properties.limits.name; \
XELOGI("* " #name ": {}", vk::to_string(vk::type(properties.limits.name)));
#define XE_UI_VULKAN_FEATURE(name) \
enabled_features.name = supported_features.name; \
device->properties_.name = supported_features.name; \
if (supported_features.name) { \
XELOGI("* " #name); \
}
#define XE_UI_VULKAN_PROPERTY_2(structure, name) \
device->properties_.name = structure.name; \
XELOGI("* " #name ": {}", structure.name);
#define XE_UI_VULKAN_ENUM_PROPERTY_2(structure, name, type) \
device->properties_.name = structure.name; \
XELOGI("* " #name ": {}", vk::to_string(vk::type(structure.name)));
#define XE_UI_VULKAN_FEATURE_2(structure, name) \
structure.enabled.name = structure.supported.name; \
device->properties_.name = structure.supported.name; \
if (structure.supported.name) { \
XELOGI("* " #name); \
}
#define XE_UI_VULKAN_FEATURE_IMPLIED(name) \
device->properties_.name = true; \
XELOGI("* " #name);
if (ext_1_2_KHR_driver_properties) {
XE_UI_VULKAN_ENUM_PROPERTY_2(properties_1_2_KHR_driver_properties, driverID,
DriverId);
XELOGI("* driverName: {}", properties_1_2_KHR_driver_properties.driverName);
if (properties_1_2_KHR_driver_properties.driverInfo[0]) {
XELOGI("* driverInfo: {}",
properties_1_2_KHR_driver_properties.driverInfo);
}
XELOGI("* conformanceVersion: {}.{}.{}.{}",
properties_1_2_KHR_driver_properties.conformanceVersion.major,
properties_1_2_KHR_driver_properties.conformanceVersion.minor,
properties_1_2_KHR_driver_properties.conformanceVersion.subminor,
properties_1_2_KHR_driver_properties.conformanceVersion.patch);
}
XE_UI_VULKAN_LIMIT(maxImageDimension2D)
XE_UI_VULKAN_LIMIT(maxImageDimension3D)
XE_UI_VULKAN_LIMIT(maxImageDimensionCube)
XE_UI_VULKAN_LIMIT(maxImageArrayLayers)
XE_UI_VULKAN_LIMIT(maxStorageBufferRange)
XE_UI_VULKAN_LIMIT(maxSamplerAllocationCount)
XE_UI_VULKAN_LIMIT(maxPerStageDescriptorSamplers)
XE_UI_VULKAN_LIMIT(maxPerStageDescriptorStorageBuffers)
XE_UI_VULKAN_LIMIT(maxPerStageDescriptorSampledImages)
XE_UI_VULKAN_LIMIT(maxPerStageResources)
XE_UI_VULKAN_LIMIT(maxVertexOutputComponents)
XE_UI_VULKAN_LIMIT(maxTessellationEvaluationOutputComponents)
XE_UI_VULKAN_LIMIT(maxGeometryInputComponents)
XE_UI_VULKAN_LIMIT(maxGeometryOutputComponents)
XE_UI_VULKAN_LIMIT(maxFragmentInputComponents)
XE_UI_VULKAN_LIMIT(maxFragmentCombinedOutputResources)
XE_UI_VULKAN_LIMIT(maxSamplerAnisotropy)
XE_UI_VULKAN_LIMIT(maxViewportDimensions[0])
XE_UI_VULKAN_LIMIT(maxViewportDimensions[1])
XE_UI_VULKAN_LIMIT(minUniformBufferOffsetAlignment)
XE_UI_VULKAN_LIMIT(minStorageBufferOffsetAlignment)
XE_UI_VULKAN_LIMIT(maxFramebufferWidth)
XE_UI_VULKAN_LIMIT(maxFramebufferHeight)
XE_UI_VULKAN_ENUM_LIMIT(framebufferColorSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(framebufferDepthSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(framebufferStencilSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(framebufferNoAttachmentsSampleCounts,
SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(sampledImageColorSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(sampledImageIntegerSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(sampledImageDepthSampleCounts, SampleCountFlags)
XE_UI_VULKAN_ENUM_LIMIT(sampledImageStencilSampleCounts, SampleCountFlags)
XE_UI_VULKAN_LIMIT(standardSampleLocations)
XE_UI_VULKAN_LIMIT(optimalBufferCopyOffsetAlignment)
XE_UI_VULKAN_LIMIT(optimalBufferCopyRowPitchAlignment)
XE_UI_VULKAN_LIMIT(nonCoherentAtomSize)
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE(robustBufferAccess)
XE_UI_VULKAN_FEATURE(fullDrawIndexUint32)
XE_UI_VULKAN_FEATURE(independentBlend)
XE_UI_VULKAN_FEATURE(geometryShader)
XE_UI_VULKAN_FEATURE(tessellationShader)
XE_UI_VULKAN_FEATURE(sampleRateShading)
XE_UI_VULKAN_FEATURE(depthClamp)
XE_UI_VULKAN_FEATURE(fillModeNonSolid)
XE_UI_VULKAN_FEATURE(samplerAnisotropy)
XE_UI_VULKAN_FEATURE(occlusionQueryPrecise)
XE_UI_VULKAN_FEATURE(vertexPipelineStoresAndAtomics)
XE_UI_VULKAN_FEATURE(fragmentStoresAndAtomics)
XE_UI_VULKAN_FEATURE(shaderClipDistance)
XE_UI_VULKAN_FEATURE(shaderCullDistance)
XE_UI_VULKAN_FEATURE(sparseBinding)
XE_UI_VULKAN_FEATURE(sparseResidencyBuffer)
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 2, 0)) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(features_1_2, samplerMirrorClampToEdge);
}
} else {
if (ext_1_2_KHR_sampler_mirror_clamp_to_edge) {
XE_UI_VULKAN_FEATURE_IMPLIED(samplerMirrorClampToEdge)
}
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(features_1_3, shaderDemoteToHelperInvocation);
}
} else {
if (ext_1_3_EXT_shader_demote_to_helper_invocation) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(
features_1_3_EXT_shader_demote_to_helper_invocation,
shaderDemoteToHelperInvocation);
}
}
}
if (ext_KHR_portability_subset) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset,
constantAlphaColorBlendFactors)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset,
imageViewFormatReinterpretation)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset,
imageViewFormatSwizzle)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset, pointPolygons)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset,
separateStencilMaskRef)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset,
shaderSampleRateInterpolationFunctions)
XE_UI_VULKAN_FEATURE_2(features_KHR_portability_subset, triangleFans)
}
} else {
// Not a portability subset device.
XE_UI_VULKAN_FEATURE_IMPLIED(constantAlphaColorBlendFactors)
XE_UI_VULKAN_FEATURE_IMPLIED(imageViewFormatReinterpretation)
XE_UI_VULKAN_FEATURE_IMPLIED(imageViewFormatSwizzle)
XE_UI_VULKAN_FEATURE_IMPLIED(pointPolygons)
XE_UI_VULKAN_FEATURE_IMPLIED(separateStencilMaskRef)
XE_UI_VULKAN_FEATURE_IMPLIED(shaderSampleRateInterpolationFunctions)
XE_UI_VULKAN_FEATURE_IMPLIED(triangleFans)
}
if (ext_1_2_KHR_shader_float_controls) {
XE_UI_VULKAN_PROPERTY_2(properties_1_2_KHR_shader_float_controls,
shaderSignedZeroInfNanPreserveFloat32);
XE_UI_VULKAN_PROPERTY_2(properties_1_2_KHR_shader_float_controls,
shaderDenormFlushToZeroFloat32);
XE_UI_VULKAN_PROPERTY_2(properties_1_2_KHR_shader_float_controls,
shaderRoundingModeRTEFloat32);
}
if (ext_EXT_fragment_shader_interlock) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(features_EXT_fragment_shader_interlock,
fragmentShaderSampleInterlock)
XE_UI_VULKAN_FEATURE_2(features_EXT_fragment_shader_interlock,
fragmentShaderPixelInterlock)
}
}
if (ext_EXT_non_seamless_cube_map) {
if (with_gpu_emulation) {
XE_UI_VULKAN_FEATURE_2(features_EXT_non_seamless_cube_map,
nonSeamlessCubeMap)
}
}
#undef XE_UI_VULKAN_LIMIT
#undef XE_UI_VULKAN_ENUM_LIMIT
#undef XE_UI_VULKAN_FEATURE
#undef XE_UI_VULKAN_PROPERTY_2
#undef XE_UI_VULKAN_ENUM_PROPERTY_2
#undef XE_UI_VULKAN_FEATURE_2
// Create the device.
const VkResult device_create_result = ifn.vkCreateDevice(
physical_device, &device_create_info, nullptr, &device->device_);
if (device_create_result != VK_SUCCESS) {
XELOGE(
"Failed to create a Vulkan logical device from the physical device "
"'{}': {}",
properties.deviceName, vk::to_string(vk::Result(device_create_result)));
return nullptr;
}
// Load device functions.
bool functions_loaded = true;
Functions& dfn = device->functions_;
#define XE_UI_VULKAN_FUNCTION(name) \
functions_loaded &= (dfn.name = PFN_##name(ifn.vkGetDeviceProcAddr( \
device->device_, #name))) != nullptr;
// Vulkan 1.0.
#include "xenia/ui/vulkan/functions/device_1_0.inc"
// Extensions promoted to a Vulkan version supported by the device.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(dfn.core_name = PFN_##core_name( \
ifn.vkGetDeviceProcAddr(device->device_, #core_name))) != nullptr;
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
#include "xenia/ui/vulkan/functions/device_1_1_khr_bind_memory2.inc"
#include "xenia/ui/vulkan/functions/device_1_1_khr_get_memory_requirements2.inc"
}
if (properties.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
#include "xenia/ui/vulkan/functions/device_1_3_khr_maintenance4.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
// Non-promoted extensions, and extensions promoted to a Vulkan version not
// supported by the device.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(dfn.core_name = PFN_##core_name(ifn.vkGetDeviceProcAddr( \
device->device_, #extension_name))) != nullptr;
if (properties.apiVersion < VK_MAKE_API_VERSION(0, 1, 1, 0)) {
if (device->extensions_.ext_1_1_KHR_get_memory_requirements2) {
#include "xenia/ui/vulkan/functions/device_1_1_khr_get_memory_requirements2.inc"
}
if (device->extensions_.ext_1_1_KHR_bind_memory2) {
#include "xenia/ui/vulkan/functions/device_1_1_khr_bind_memory2.inc"
}
}
if (properties.apiVersion < VK_MAKE_API_VERSION(0, 1, 3, 0)) {
if (device->extensions_.ext_1_3_KHR_maintenance4) {
#include "xenia/ui/vulkan/functions/device_1_3_khr_maintenance4.inc"
}
}
if (device->extensions_.ext_KHR_swapchain) {
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
if (!functions_loaded) {
XELOGE("Failed to get all Vulkan device function pointers for '{}'",
properties.deviceName);
return nullptr;
}
// Get the queues.
for (size_t queue_family_index = 0;
queue_family_index < device->queue_families_.size();
++queue_family_index) {
QueueFamily& queue_family = device->queue_families_[queue_family_index];
for (size_t queue_index = 0; queue_index < queue_family.queues.size();
++queue_index) {
VkQueue queue;
dfn.vkGetDeviceQueue(device->device_, uint32_t(queue_family_index),
uint32_t(queue_index), &queue);
queue_family.queues[queue_index] = std::make_unique<Queue>(queue);
}
}
// Get the memory types.
VkPhysicalDeviceMemoryProperties memory_properties;
ifn.vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
for (uint32_t memory_type_index = 0;
memory_type_index < memory_properties.memoryTypeCount;
++memory_type_index) {
const uint32_t memory_type_bit = uint32_t(1) << memory_type_index;
const VkMemoryPropertyFlags memory_type_flags =
memory_properties.memoryTypes[memory_type_index].propertyFlags;
if (memory_type_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
device->memory_types_.device_local |= memory_type_bit;
}
if (memory_type_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
device->memory_types_.host_visible |= memory_type_bit;
}
if (memory_type_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
device->memory_types_.host_coherent |= memory_type_bit;
}
if (memory_type_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
device->memory_types_.host_cached |= memory_type_bit;
}
}
return device;
}
VulkanDevice::~VulkanDevice() {
if (device_) {
vulkan_instance_->functions().vkDestroyDevice(device_, nullptr);
}
}
VulkanDevice::VulkanDevice(const VulkanInstance* const vulkan_instance,
const VkPhysicalDevice physical_device)
: vulkan_instance_(vulkan_instance), physical_device_(physical_device) {
assert_not_null(vulkan_instance);
assert_not_null(physical_device);
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,297 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_DEVICE_H_
#define XENIA_UI_VULKAN_VULKAN_DEVICE_H_
#include <memory>
#include <mutex>
#include <vector>
#include "xenia/ui/vulkan/vulkan_instance.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanDevice {
public:
static std::unique_ptr<VulkanDevice> CreateIfSupported(
const VulkanInstance* vulkan_instance, VkPhysicalDevice physical_device,
bool with_gpu_emulation, bool with_swapchain);
VulkanDevice(const VulkanDevice&) = delete;
VulkanDevice& operator=(const VulkanDevice&) = delete;
VulkanDevice(VulkanDevice&&) = delete;
VulkanDevice& operator=(VulkanDevice&&) = delete;
~VulkanDevice();
const VulkanInstance* vulkan_instance() const { return vulkan_instance_; }
VkPhysicalDevice physical_device() const { return physical_device_; }
// If functionality from higher API versions is used, increase this.
// This is for VkApplicationInfo.
// "apiVersion must be the highest version of Vulkan that the application is
// designed to use"
// "The patch version number specified in apiVersion is ignored when creating
// an instance object"
static constexpr uint32_t kHighestUsedApiMinorVersion =
VK_MAKE_API_VERSION(0, 1, 3, 0);
struct Properties {
// Vulkan 1.0
uint32_t apiVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
uint32_t driverVersion = 0;
uint32_t vendorID = 0;
uint32_t deviceID = 0;
char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {};
uint32_t maxImageDimension2D = 4096;
uint32_t maxImageDimension3D = 256;
uint32_t maxImageDimensionCube = 4096;
uint32_t maxImageArrayLayers = 256;
uint32_t maxStorageBufferRange = uint32_t(1) << 27;
uint32_t maxSamplerAllocationCount = 4000;
uint32_t maxPerStageDescriptorSamplers = 16;
uint32_t maxPerStageDescriptorStorageBuffers = 4;
uint32_t maxPerStageDescriptorSampledImages = 16;
uint32_t maxPerStageResources = 128;
uint32_t maxVertexOutputComponents = 64;
uint32_t maxTessellationEvaluationOutputComponents = 64;
uint32_t maxGeometryInputComponents = 64;
uint32_t maxGeometryOutputComponents = 64;
uint32_t maxFragmentInputComponents = 64;
uint32_t maxFragmentCombinedOutputResources = 4;
float maxSamplerAnisotropy = 1.0f;
uint32_t maxViewportDimensions[2] = {4096, 4096};
VkDeviceSize minUniformBufferOffsetAlignment = 256;
VkDeviceSize minStorageBufferOffsetAlignment = 256;
uint32_t maxFramebufferWidth = 4096;
uint32_t maxFramebufferHeight = 4096;
VkSampleCountFlags framebufferColorSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags framebufferDepthSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags framebufferStencilSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags framebufferNoAttachmentsSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags sampledImageColorSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags sampledImageIntegerSampleCounts = VK_SAMPLE_COUNT_1_BIT;
VkSampleCountFlags sampledImageDepthSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
VkSampleCountFlags sampledImageStencilSampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
bool standardSampleLocations = false;
VkDeviceSize optimalBufferCopyOffsetAlignment = 1;
VkDeviceSize optimalBufferCopyRowPitchAlignment = 1;
VkDeviceSize nonCoherentAtomSize = 256;
bool robustBufferAccess = false;
bool fullDrawIndexUint32 = false;
bool independentBlend = false;
bool geometryShader = false;
bool tessellationShader = false;
bool sampleRateShading = false;
bool depthClamp = false;
bool fillModeNonSolid = false;
bool samplerAnisotropy = false;
bool occlusionQueryPrecise = false;
bool vertexPipelineStoresAndAtomics = false;
bool fragmentStoresAndAtomics = false;
bool shaderClipDistance = false;
bool shaderCullDistance = false;
bool sparseBinding = false;
bool sparseResidencyBuffer = false;
// VK_KHR_sampler_mirror_clamp_to_edge (#15, promoted to 1.2)
bool samplerMirrorClampToEdge = false;
// VK_KHR_portability_subset (#164)
bool constantAlphaColorBlendFactors = false;
bool imageViewFormatReinterpretation = false;
bool imageViewFormatSwizzle = false;
bool pointPolygons = false;
bool separateStencilMaskRef = false;
bool shaderSampleRateInterpolationFunctions = false;
bool triangleFans = false;
// VK_KHR_driver_properties (#197, promoted to 1.2)
VkDriverId driverID = VkDriverId(0);
// VK_KHR_shader_float_controls (#198, promoted to 1.2)
bool shaderSignedZeroInfNanPreserveFloat32 = false;
bool shaderDenormFlushToZeroFloat32 = false;
bool shaderRoundingModeRTEFloat32 = false;
// VK_EXT_fragment_shader_interlock (#252)
bool fragmentShaderSampleInterlock = false;
bool fragmentShaderPixelInterlock = false;
// VK_EXT_shader_demote_to_helper_invocation (#277, promoted to 1.3)
bool shaderDemoteToHelperInvocation = false;
// VK_EXT_non_seamless_cube_map (#423)
bool nonSeamlessCubeMap = false;
};
// Properties of the core API and enabled extensions, and enabled features.
// Some supported functionality is enabled conditionally based on the
// `with_swapchain` and `with_gpu_emulation` options.
const Properties& properties() const { return properties_; }
// Enabled extensions not fully covered by the device properties and optional
// feature flags in the `Properties` structure (primarily those adding API
// functionality rather than GPU features). Also set to true if the version of
// the Vulkan API they were promoted to it supported (with the
// `ext_major_minor_` prefix rather than `ext_`).
struct Extensions {
bool ext_KHR_swapchain = false; // #2
bool ext_1_1_KHR_dedicated_allocation = false; // #128
bool ext_EXT_shader_stencil_export = false; // #141
bool ext_1_1_KHR_get_memory_requirements2 = false; // #147
bool ext_1_2_KHR_image_format_list = false; // #148
// Has optional features not implied by this being true.
bool ext_1_1_KHR_sampler_ycbcr_conversion = false; // #157
bool ext_1_1_KHR_bind_memory2 = false; // #158
bool ext_1_2_KHR_spirv_1_4 = false; // #237
bool ext_EXT_memory_budget = false; // #238
// Has optional features not implied by this being true.
bool ext_1_3_KHR_maintenance4 = false; // #414
};
const Extensions& extensions() const { return extensions_; }
VkDevice device() const { return device_; }
struct Functions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name = nullptr;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##core_name core_name = nullptr;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
// VK_KHR_swapchain (#2)
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
// VK_KHR_get_memory_requirements2 (#147, promoted to 1.1)
#include "xenia/ui/vulkan/functions/device_1_1_khr_get_memory_requirements2.inc"
// VK_KHR_bind_memory2 (#158, promoted to 1.1)
#include "xenia/ui/vulkan/functions/device_1_1_khr_bind_memory2.inc"
// VK_KHR_maintenance4 (#414, promoted to 1.3)
#include "xenia/ui/vulkan/functions/device_1_3_khr_maintenance4.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const Functions& functions() const { return functions_; }
template <typename Object>
void SetObjectName(const VkObjectType object_type, const Object object_handle,
const char* const object_name) const {
if (!vulkan_instance()->extensions().ext_EXT_debug_utils) {
return;
}
VkDebugUtilsObjectNameInfoEXT object_name_info;
object_name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
object_name_info.pNext = nullptr;
object_name_info.objectType = object_type;
object_name_info.objectHandle = (uint64_t)object_handle;
object_name_info.pObjectName = object_name;
vulkan_instance()->functions().vkSetDebugUtilsObjectNameEXT(
device(), &object_name_info);
}
struct Queue {
// Host access to queues must be externally synchronized in Vulkan.
std::recursive_mutex mutex;
VkQueue queue = nullptr;
explicit Queue(const VkQueue queue) : queue(queue) {}
class Acquisition {
public:
explicit Acquisition(Queue& queue)
: lock_(queue.mutex), queue_(queue.queue) {}
VkQueue queue() const { return queue_; }
private:
std::unique_lock<std::recursive_mutex> lock_;
VkQueue queue_;
};
Acquisition Acquire() { return Acquisition(*this); }
};
struct QueueFamily {
VkQueueFlags queue_flags = 0;
bool may_support_presentation = false;
std::vector<std::unique_ptr<Queue>> queues;
};
const std::vector<QueueFamily>& queue_families() const {
return queue_families_;
}
uint32_t queue_family_graphics_compute() const {
return queue_family_graphics_compute_;
}
// UINT32_MAX if not supported or not enabled.
// May be the same as queue_family_graphics_compute().
uint32_t queue_family_sparse_binding() const {
return queue_family_sparse_binding_;
}
Queue::Acquisition AcquireQueue(const uint32_t queue_family_index,
const uint32_t queue_index) const {
return queue_families()[queue_family_index].queues[queue_index]->Acquire();
}
struct MemoryTypes {
uint32_t device_local = 0b0;
uint32_t host_visible = 0b0;
uint32_t host_coherent = 0b0;
uint32_t host_cached = 0b0;
};
const MemoryTypes& memory_types() const { return memory_types_; }
private:
explicit VulkanDevice(const VulkanInstance* vulkan_instance,
VkPhysicalDevice physical_device);
const VulkanInstance* vulkan_instance_ = nullptr;
VkPhysicalDevice physical_device_ = nullptr;
Properties properties_;
Extensions extensions_;
VkDevice device_ = nullptr;
Functions functions_;
std::vector<QueueFamily> queue_families_;
uint32_t queue_family_graphics_compute_ = UINT32_MAX;
uint32_t queue_family_sparse_binding_ = UINT32_MAX;
MemoryTypes memory_types_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_DEVICE_H_

View File

@@ -30,6 +30,20 @@ namespace shaders {
#include "xenia/ui/shaders/bytecode/vulkan_spirv/immediate_vs.h"
} // namespace shaders
std::unique_ptr<VulkanImmediateDrawer> VulkanImmediateDrawer::Create(
const VulkanDevice* const vulkan_device,
const UISamplers* const ui_samplers) {
assert_not_null(vulkan_device);
assert_not_null(ui_samplers);
auto immediate_drawer = std::unique_ptr<VulkanImmediateDrawer>(
new VulkanImmediateDrawer(vulkan_device, ui_samplers));
if (!immediate_drawer->Initialize()) {
return nullptr;
}
return immediate_drawer;
}
VulkanImmediateDrawer::VulkanImmediateTexture::~VulkanImmediateTexture() {
if (immediate_drawer_) {
immediate_drawer_->OnImmediateTextureDestroyed(*this);
@@ -45,8 +59,8 @@ VulkanImmediateDrawer::~VulkanImmediateDrawer() {
last_paint_submission_index_);
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_line_);
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_triangle_);
@@ -88,9 +102,17 @@ VulkanImmediateDrawer::~VulkanImmediateDrawer() {
texture_descriptor_set_layout_);
}
VulkanImmediateDrawer::VulkanImmediateDrawer(
const VulkanDevice* const vulkan_device,
const UISamplers* const ui_samplers)
: vulkan_device_(vulkan_device), ui_samplers_(ui_samplers) {
assert_not_null(vulkan_device);
assert_not_null(ui_samplers);
}
bool VulkanImmediateDrawer::Initialize() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkDescriptorSetLayoutBinding texture_descriptor_set_layout_binding;
texture_descriptor_set_layout_binding.binding = 0;
@@ -128,7 +150,7 @@ bool VulkanImmediateDrawer::Initialize() {
}
vertex_buffer_pool_ = std::make_unique<VulkanUploadBufferPool>(
provider_,
vulkan_device_,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
VkPushConstantRange push_constant_ranges[1];
@@ -194,8 +216,8 @@ void VulkanImmediateDrawer::Begin(UIDrawContext& ui_draw_context,
last_completed_submission_index_ =
vulkan_ui_draw_context.submission_index_completed();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
// Destroy deleted textures.
for (auto it = textures_deleted_.begin(); it != textures_deleted_.end();) {
@@ -272,7 +294,7 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
VkCommandBuffer draw_command_buffer =
vulkan_ui_draw_context.draw_command_buffer();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
// Bind the vertices.
size_t vertex_buffer_size = sizeof(ImmediateVertex) * batch.vertex_count;
@@ -319,7 +341,7 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
return;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VulkanUIDrawContext& vulkan_ui_draw_context =
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
VkCommandBuffer draw_command_buffer =
@@ -410,7 +432,7 @@ void VulkanImmediateDrawer::End() {
vulkan_presenter.AcquireUISetupCommandBufferFromUIThread();
if (setup_command_buffer != VK_NULL_HANDLE) {
size_t texture_uploads_pending_count = texture_uploads_pending_.size();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VulkanUIDrawContext& vulkan_ui_draw_context =
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
@@ -528,8 +550,8 @@ void VulkanImmediateDrawer::OnLeavePresenter() {
texture->last_usage_submission_ = 0;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
for (SubmittedTextureUploadBuffer& submitted_texture_upload_buffer :
texture_upload_buffers_submitted_) {
@@ -566,8 +588,8 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreatedForCurrentRenderPass() {
last_paint_submission_index_);
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
// Safe to destroy the pipelines now - if the render pass was recreated,
// completion of its usage has already been awaited.
@@ -581,8 +603,8 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreatedForCurrentRenderPass() {
VkPipelineShaderStageCreateInfo stages[2] = {};
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[0].module = util::CreateShaderModule(provider_, shaders::immediate_vs,
sizeof(shaders::immediate_vs));
stages[0].module = util::CreateShaderModule(
vulkan_device_, shaders::immediate_vs, sizeof(shaders::immediate_vs));
if (stages[0].module == VK_NULL_HANDLE) {
XELOGE("VulkanImmediateDrawer: Failed to create the vertex shader module");
return false;
@@ -590,8 +612,8 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreatedForCurrentRenderPass() {
stages[0].pName = "main";
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = util::CreateShaderModule(provider_, shaders::immediate_ps,
sizeof(shaders::immediate_ps));
stages[1].module = util::CreateShaderModule(
vulkan_device_, shaders::immediate_ps, sizeof(shaders::immediate_ps));
if (stages[1].module == VK_NULL_HANDLE) {
XELOGE(
"VulkanImmediateDrawer: Failed to create the fragment shader module");
@@ -753,8 +775,8 @@ uint32_t VulkanImmediateDrawer::AllocateTextureDescriptor() {
return (pool->index << 6) | local_index;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkDescriptorSetAllocateInfo allocate_info;
allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
@@ -864,8 +886,8 @@ bool VulkanImmediateDrawer::CreateTextureResource(
bool is_repeated, const uint8_t* data,
VulkanImmediateTexture::Resource& resource_out,
size_t& pending_upload_index_out) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
// Create the image and the descriptor.
@@ -890,7 +912,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImage image;
VkDeviceMemory image_memory;
if (!util::CreateDedicatedAllocationImage(provider_, image_create_info,
if (!util::CreateDedicatedAllocationImage(vulkan_device_, image_create_info,
util::MemoryPurpose::kDeviceLocal,
image, image_memory)) {
XELOGE(
@@ -910,7 +932,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
// data == nullptr is a special case for (1, 1, 1, 1), though the image will
// be cleared to (1, 1, 1, 1) anyway, just a micro-optimization.
VkComponentSwizzle swizzle =
(data || !provider_.device_info().imageViewFormatSwizzle)
(data || !vulkan_device_->properties().imageViewFormatSwizzle)
? VK_COMPONENT_SWIZZLE_IDENTITY
: VK_COMPONENT_SWIZZLE_ONE;
image_view_create_info.components.r = swizzle;
@@ -940,15 +962,16 @@ bool VulkanImmediateDrawer::CreateTextureResource(
return false;
}
VkDescriptorImageInfo descriptor_image_info;
VulkanProvider::HostSampler host_sampler;
UISamplers::SamplerIndex ui_sampler_index;
if (filter == ImmediateTextureFilter::kLinear) {
host_sampler = is_repeated ? VulkanProvider::HostSampler::kLinearRepeat
: VulkanProvider::HostSampler::kLinearClamp;
ui_sampler_index = is_repeated ? UISamplers::kSamplerIndexLinearRepeat
: UISamplers::kSamplerIndexLinearClampToEdge;
} else {
host_sampler = is_repeated ? VulkanProvider::HostSampler::kNearestRepeat
: VulkanProvider::HostSampler::kNearestClamp;
ui_sampler_index = is_repeated
? UISamplers::kSamplerIndexNearestRepeat
: UISamplers::kSamplerIndexNearestClampToEdge;
}
descriptor_image_info.sampler = provider_.GetHostSampler(host_sampler);
descriptor_image_info.sampler = ui_samplers_->samplers()[ui_sampler_index];
descriptor_image_info.imageView = image_view;
descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet descriptor_write;
@@ -974,7 +997,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
size_t data_size = sizeof(uint32_t) * width * height;
uint32_t upload_buffer_memory_type;
if (!util::CreateDedicatedAllocationBuffer(
provider_, VkDeviceSize(data_size),
vulkan_device_, VkDeviceSize(data_size),
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, util::MemoryPurpose::kUpload,
upload_buffer, upload_buffer_memory, &upload_buffer_memory_type)) {
XELOGE(
@@ -1003,7 +1026,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
return false;
}
std::memcpy(upload_buffer_mapping, data, data_size);
util::FlushMappedMemoryRange(provider_, upload_buffer_memory,
util::FlushMappedMemoryRange(vulkan_device_, upload_buffer_memory,
upload_buffer_memory_type);
dfn.vkUnmapMemory(device, upload_buffer_memory);
}
@@ -1030,8 +1053,8 @@ bool VulkanImmediateDrawer::CreateTextureResource(
void VulkanImmediateDrawer::DestroyTextureResource(
VulkanImmediateTexture::Resource& resource) {
FreeTextureDescriptor(resource.descriptor_index);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
dfn.vkDestroyImageView(device, resource.image_view, nullptr);
dfn.vkDestroyImage(device, resource.image, nullptr);
dfn.vkFreeMemory(device, resource.memory, nullptr);

View File

@@ -17,6 +17,7 @@
#include <vector>
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
@@ -26,14 +27,7 @@ namespace vulkan {
class VulkanImmediateDrawer : public ImmediateDrawer {
public:
static std::unique_ptr<VulkanImmediateDrawer> Create(
const VulkanProvider& provider) {
auto immediate_drawer = std::unique_ptr<VulkanImmediateDrawer>(
new VulkanImmediateDrawer(provider));
if (!immediate_drawer->Initialize()) {
return nullptr;
}
return std::move(immediate_drawer);
}
const VulkanDevice* vulkan_device, const UISamplers* ui_samplers);
~VulkanImmediateDrawer();
@@ -96,7 +90,8 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
TextureDescriptorPool* recycled_next;
};
VulkanImmediateDrawer(const VulkanProvider& provider) : provider_(provider) {}
explicit VulkanImmediateDrawer(const VulkanDevice* vulkan_device,
const UISamplers* ui_samplers);
bool Initialize();
bool EnsurePipelinesCreatedForCurrentRenderPass();
@@ -117,7 +112,8 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
void DestroyTextureResource(VulkanImmediateTexture::Resource& resource);
void OnImmediateTextureDestroyed(VulkanImmediateTexture& texture);
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
const UISamplers* ui_samplers_;
// Combined image sampler pools for textures.
VkDescriptorSetLayout texture_descriptor_set_layout_;

View File

@@ -0,0 +1,667 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_instance.h"
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/ui/vulkan/vulkan_presenter.h"
#if XE_PLATFORM_LINUX
#include <dlfcn.h>
#elif XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#endif
DEFINE_bool(
vulkan_log_debug_messages, true,
"Write Vulkan VK_EXT_debug_utils messages to the Xenia log, as opposed to "
"the OS debug output.",
"Vulkan");
namespace xe {
namespace ui {
namespace vulkan {
std::unique_ptr<VulkanInstance> VulkanInstance::Create(
const bool with_surface, const bool try_enable_validation) {
std::unique_ptr<VulkanInstance> vulkan_instance(new VulkanInstance());
// Load the RenderDoc API if connected.
vulkan_instance->renderdoc_api_ = RenderDocAPI::CreateIfConnected();
// Load the loader library.
Functions& ifn = vulkan_instance->functions_;
bool functions_loaded = true;
#if XE_PLATFORM_LINUX
#if XE_PLATFORM_ANDROID
const char* const loader_library_name = "libvulkan.so";
#else
const char* const loader_library_name = "libvulkan.so.1";
#endif
// http://developer.download.nvidia.com/mobile/shield/assets/Vulkan/UsingtheVulkanAPI.pdf
vulkan_instance->loader_ = dlopen(loader_library_name, RTLD_NOW | RTLD_LOCAL);
if (!vulkan_instance->loader_) {
XELOGE("Failed to load {}", loader_library_name);
return false;
}
#define XE_VULKAN_LOAD_LOADER_FUNCTION(name) \
functions_loaded &= \
(ifn.name = PFN_##name(dlsym(vulkan_instance->loader_, #name))) != \
nullptr;
#elif XE_PLATFORM_WIN32
vulkan_instance->loader_ = LoadLibraryW(L"vulkan-1.dll");
if (!vulkan_instance->loader_) {
XELOGE("Failed to load vulkan-1.dll");
return false;
}
#define XE_VULKAN_LOAD_LOADER_FUNCTION(name) \
functions_loaded &= (ifn.name = PFN_##name(GetProcAddress( \
vulkan_instance->loader_, #name))) != nullptr;
#else
#error No Vulkan loader library loading provided for the target platform.
#endif
XE_VULKAN_LOAD_LOADER_FUNCTION(vkGetInstanceProcAddr);
XE_VULKAN_LOAD_LOADER_FUNCTION(vkDestroyInstance);
#undef XE_VULKAN_LOAD_LOADER_FUNCTION
if (!functions_loaded) {
XELOGE("Failed to get Vulkan loader function pointers");
return nullptr;
}
// Load global functions.
functions_loaded &=
(ifn.vkCreateInstance = PFN_vkCreateInstance(
ifn.vkGetInstanceProcAddr(nullptr, "vkCreateInstance"))) != nullptr;
functions_loaded &=
(ifn.vkEnumerateInstanceExtensionProperties =
PFN_vkEnumerateInstanceExtensionProperties(ifn.vkGetInstanceProcAddr(
nullptr, "vkEnumerateInstanceExtensionProperties"))) != nullptr;
functions_loaded &=
(ifn.vkEnumerateInstanceLayerProperties =
PFN_vkEnumerateInstanceLayerProperties(ifn.vkGetInstanceProcAddr(
nullptr, "vkEnumerateInstanceLayerProperties"))) != nullptr;
if (!functions_loaded) {
XELOGE(
"Failed to get Vulkan global function pointers via "
"vkGetInstanceProcAddr");
return nullptr;
}
// Available since Vulkan 1.1. If this is nullptr, it's a Vulkan 1.0 instance.
ifn.vkEnumerateInstanceVersion = PFN_vkEnumerateInstanceVersion(
ifn.vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"));
// Get the API version.
if (ifn.vkEnumerateInstanceVersion) {
ifn.vkEnumerateInstanceVersion(&vulkan_instance->api_version_);
}
// Enable extensions and layers.
// Name pointers from `requested_extensions` will be used in the enabled
// extensions vector.
std::unordered_map<std::string, bool*> requested_extensions;
if (vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
vulkan_instance->extensions_.ext_1_1_KHR_get_physical_device_properties2 =
true;
} else {
// #60.
requested_extensions.emplace(
"VK_KHR_get_physical_device_properties2",
&vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2);
}
// #129.
requested_extensions.emplace(
"VK_EXT_debug_utils", &vulkan_instance->extensions_.ext_EXT_debug_utils);
// #395.
requested_extensions.emplace(
"VK_KHR_portability_enumeration",
&vulkan_instance->extensions_.ext_KHR_portability_enumeration);
if (with_surface) {
// #1.
requested_extensions.emplace("VK_KHR_surface",
&vulkan_instance->extensions_.ext_KHR_surface);
#ifdef VK_USE_PLATFORM_XCB_KHR
// #6.
requested_extensions.emplace(
"VK_KHR_xcb_surface",
&vulkan_instance->extensions_.ext_KHR_xcb_surface);
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
// #9.
requested_extensions.emplace(
"VK_KHR_android_surface",
&vulkan_instance->extensions_.ext_KHR_android_surface);
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
// #10.
requested_extensions.emplace(
"VK_KHR_win32_surface",
&vulkan_instance->extensions_.ext_KHR_win32_surface);
#endif
}
std::vector<const char*> enabled_extensions;
std::vector<VkExtensionProperties> supported_implementation_extensions;
while (true) {
uint32_t supported_implementation_extension_count = 0;
const VkResult get_supported_implementation_extension_count_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_implementation_extension_count, nullptr);
if (get_supported_implementation_extension_count_result != VK_SUCCESS &&
get_supported_implementation_extension_count_result != VK_INCOMPLETE) {
XELOGW("Failed to get the Vulkan instance extension count");
return nullptr;
}
if (supported_implementation_extension_count) {
supported_implementation_extensions.resize(
supported_implementation_extension_count);
const VkResult get_supported_implementation_extensions_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_implementation_extension_count,
supported_implementation_extensions.data());
if (get_supported_implementation_extensions_result == VK_INCOMPLETE) {
continue;
}
if (get_supported_implementation_extensions_result != VK_SUCCESS) {
XELOGW("Failed to get the Vulkan instance extensions");
return nullptr;
}
}
supported_implementation_extensions.resize(
supported_implementation_extension_count);
break;
}
for (const VkExtensionProperties& supported_extension :
supported_implementation_extensions) {
const auto requested_extension_it =
requested_extensions.find(supported_extension.extensionName);
if (requested_extension_it == requested_extensions.cend()) {
continue;
}
assert_not_null(requested_extension_it->second);
if (!*requested_extension_it->second) {
enabled_extensions.emplace_back(requested_extension_it->first.c_str());
*requested_extension_it->second = true;
}
}
// If enabled layers are not present, will disable all extensions provided by
// the layers by truncating the enabled extension vector to this size.
const size_t enabled_implementation_extension_count =
enabled_extensions.size();
std::vector<bool*> enabled_layer_extension_enablement_bools;
// Name pointers from `requested_layers` will be used in the enabled layer
// vector.
std::unordered_map<std::string, bool*> requested_layers;
bool layer_khronos_validation = false;
if (try_enable_validation) {
requested_layers.emplace("VK_LAYER_KHRONOS_validation",
&layer_khronos_validation);
}
std::vector<const char*> enabled_layers;
if (!requested_layers.empty()) {
std::vector<VkLayerProperties> available_layers;
// "The list of available layers may change at any time due to actions
// outside of the Vulkan implementation"
while (true) {
available_layers.clear();
uint32_t available_layer_count = 0;
const VkResult get_available_layer_count_result =
ifn.vkEnumerateInstanceLayerProperties(&available_layer_count,
nullptr);
if (get_available_layer_count_result != VK_SUCCESS &&
get_available_layer_count_result != VK_INCOMPLETE) {
break;
}
if (available_layer_count) {
available_layers.resize(available_layer_count);
const VkResult get_available_layers_result =
ifn.vkEnumerateInstanceLayerProperties(&available_layer_count,
available_layers.data());
if (get_available_layers_result == VK_INCOMPLETE) {
// New layers were added.
continue;
}
if (get_available_layers_result != VK_SUCCESS) {
available_layers.clear();
break;
}
// In case the second enumeration returned fewer layers.
available_layers.resize(available_layer_count);
}
break;
}
if (!available_layers.empty()) {
std::vector<VkExtensionProperties> supported_layer_extensions;
for (const VkLayerProperties& available_layer : available_layers) {
auto requested_layer_it =
requested_layers.find(available_layer.layerName);
if (requested_layer_it == requested_layers.cend()) {
continue;
}
bool got_layer_extensions = true;
// "Because the list of available layers may change externally between
// calls to vkEnumerateInstanceExtensionProperties, two calls may
// retrieve different results if a pLayerName is available in one call
// but not in another."
while (true) {
uint32_t supported_layer_extension_count = 0;
const VkResult get_supported_layer_extension_count_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_layer_extension_count, nullptr);
if (get_supported_layer_extension_count_result != VK_SUCCESS &&
get_supported_layer_extension_count_result != VK_INCOMPLETE) {
got_layer_extensions = false;
break;
}
if (supported_layer_extension_count) {
supported_layer_extensions.resize(supported_layer_extension_count);
const VkResult get_supported_layer_extensions_result =
ifn.vkEnumerateInstanceExtensionProperties(
available_layer.layerName, &supported_layer_extension_count,
supported_layer_extensions.data());
if (get_supported_layer_extensions_result == VK_INCOMPLETE) {
continue;
}
if (get_supported_layer_extensions_result != VK_SUCCESS) {
got_layer_extensions = false;
break;
}
}
supported_layer_extensions.resize(supported_layer_extension_count);
break;
}
if (!got_layer_extensions) {
// The layer was possibly removed.
continue;
}
for (const VkExtensionProperties& supported_extension :
supported_layer_extensions) {
const auto requested_extension_it =
requested_extensions.find(supported_extension.extensionName);
if (requested_extension_it == requested_extensions.cend()) {
continue;
}
assert_not_null(requested_extension_it->second);
// Don't add the extension to the enabled vector multiple times if
// provided by the implementation itself or by another layer.
if (!*requested_extension_it->second) {
enabled_extensions.emplace_back(
requested_extension_it->first.c_str());
enabled_layer_extension_enablement_bools.push_back(
requested_layer_it->second);
*requested_extension_it->second = true;
}
}
assert_not_null(requested_layer_it->second);
if (!*requested_layer_it->second) {
enabled_layers.emplace_back(requested_layer_it->first.c_str());
*requested_layer_it->second = true;
}
}
}
}
// Create the instance.
VkApplicationInfo application_info;
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pNext = nullptr;
application_info.pApplicationName = "Xenia";
application_info.applicationVersion = 1;
application_info.pEngineName = nullptr;
application_info.engineVersion = 0;
// "The patch version number specified in apiVersion is ignored when creating
// an instance object."
// "Vulkan 1.0 implementations were required to return
// VK_ERROR_INCOMPATIBLE_DRIVER if apiVersion was larger than 1.0."
application_info.apiVersion =
vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)
? VulkanDevice::kHighestUsedApiMinorVersion
: VK_MAKE_API_VERSION(0, 1, 0, 0);
VkInstanceCreateInfo instance_create_info;
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pNext = nullptr;
instance_create_info.flags = 0;
// VK_KHR_get_physical_device_properties2 is needed to get the portability
// subset features.
if (vulkan_instance->extensions_.ext_KHR_portability_enumeration &&
vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2) {
instance_create_info.flags |=
VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
instance_create_info.pApplicationInfo = &application_info;
instance_create_info.enabledLayerCount = uint32_t(enabled_layers.size());
instance_create_info.ppEnabledLayerNames = enabled_layers.data();
instance_create_info.enabledExtensionCount =
uint32_t(enabled_extensions.size());
instance_create_info.ppEnabledExtensionNames = enabled_extensions.data();
VkResult instance_create_result = ifn.vkCreateInstance(
&instance_create_info, nullptr, &vulkan_instance->instance_);
if (instance_create_result == VK_ERROR_LAYER_NOT_PRESENT ||
instance_create_result == VK_ERROR_EXTENSION_NOT_PRESENT) {
// A layer was possibly removed. Try without layers.
for (bool* const extension_enablement :
enabled_layer_extension_enablement_bools) {
*extension_enablement = false;
}
for (const std::pair<std::string, bool*>& requested_layer :
requested_layers) {
*requested_layer.second = false;
}
instance_create_info.enabledLayerCount = 0;
instance_create_info.enabledExtensionCount =
uint32_t(enabled_implementation_extension_count);
instance_create_result = ifn.vkCreateInstance(
&instance_create_info, nullptr, &vulkan_instance->instance_);
}
if (instance_create_result != VK_SUCCESS) {
XELOGE("Failed to create a Vulkan instance: {}",
vk::to_string(vk::Result(instance_create_result)));
return nullptr;
}
// Load instance functions.
#define XE_UI_VULKAN_FUNCTION(name) \
functions_loaded &= (ifn.name = PFN_##name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #name))) != nullptr;
// Vulkan 1.0.
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
// Extensions promoted to a Vulkan version supported by the instance.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(ifn.core_name = PFN_##core_name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #core_name))) != nullptr;
if (vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
#include "xenia/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
// Non-promoted extensions, and extensions promoted to a Vulkan version not
// supported by the instance.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(ifn.core_name = PFN_##core_name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #extension_name))) != nullptr;
if (vulkan_instance->api_version_ < VK_MAKE_API_VERSION(0, 1, 1, 0)) {
if (vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2) {
#include "xenia/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc"
}
}
#ifdef VK_USE_PLATFORM_XCB_KHR
if (vulkan_instance->extensions_.ext_KHR_xcb_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
}
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
if (vulkan_instance->extensions_.ext_KHR_android_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
}
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
if (vulkan_instance->extensions_.ext_KHR_win32_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
}
#endif
if (vulkan_instance->extensions_.ext_KHR_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
}
if (vulkan_instance->extensions_.ext_EXT_debug_utils) {
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
if (!functions_loaded) {
XELOGE("Failed to get all Vulkan instance function pointers");
return nullptr;
}
// Check whether a surface can be created.
if (with_surface && !VulkanPresenter::GetSurfaceTypesSupportedByInstance(
vulkan_instance->extensions_)) {
XELOGE("The Vulkan instance doesn't support surface types used by Xenia");
return nullptr;
}
// Log instance properties.
XELOGI("Vulkan instance API version {}.{}.{}. Enabled layers and extensions:",
VK_VERSION_MAJOR(vulkan_instance->api_version_),
VK_VERSION_MINOR(vulkan_instance->api_version_),
VK_VERSION_PATCH(vulkan_instance->api_version_));
for (uint32_t enabled_layer_index = 0;
enabled_layer_index < instance_create_info.enabledLayerCount;
++enabled_layer_index) {
XELOGI("* {}",
instance_create_info.ppEnabledLayerNames[enabled_layer_index]);
}
for (uint32_t enabled_extension_index = 0;
enabled_extension_index < instance_create_info.enabledExtensionCount;
++enabled_extension_index) {
XELOGI(
"* {}",
instance_create_info.ppEnabledExtensionNames[enabled_extension_index]);
}
// Create the debug messenger if requested and available.
if (vulkan_instance->extensions_.ext_EXT_debug_utils &&
cvars::vulkan_log_debug_messages) {
VkDebugUtilsMessengerCreateInfoEXT debug_utils_messenger_create_info = {
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
if (xe::logging::ShouldLog(xe::LogLevel::Debug)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Info)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Warning)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Error)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
}
// VUID-VkDebugUtilsMessengerCreateInfoEXT-messageSeverity-requiredbitmask:
// "messageSeverity must not be 0"
if (debug_utils_messenger_create_info.messageSeverity) {
debug_utils_messenger_create_info.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debug_utils_messenger_create_info.pfnUserCallback =
DebugUtilsMessengerCallback;
debug_utils_messenger_create_info.pUserData = vulkan_instance.get();
const VkResult debug_utils_messenger_create_result =
ifn.vkCreateDebugUtilsMessengerEXT(
vulkan_instance->instance_, &debug_utils_messenger_create_info,
nullptr, &vulkan_instance->debug_utils_messenger_);
if (debug_utils_messenger_create_result != VK_SUCCESS) {
XELOGW("Failed to create the Vulkan debug utils messenger: {}",
vk::to_string(vk::Result(debug_utils_messenger_create_result)));
}
}
}
return vulkan_instance;
}
VulkanInstance::~VulkanInstance() {
if (instance_) {
if (debug_utils_messenger_ != VK_NULL_HANDLE) {
functions_.vkDestroyDebugUtilsMessengerEXT(
instance_, debug_utils_messenger_, nullptr);
}
functions_.vkDestroyInstance(instance_, nullptr);
}
#if XE_PLATFORM_LINUX
if (loader_) {
dlclose(loader_);
}
#elif XE_PLATFORM_WIN32
if (loader_) {
FreeLibrary(loader_);
}
#endif
}
void VulkanInstance::EnumeratePhysicalDevices(
std::vector<VkPhysicalDevice>& physical_devices_out) const {
physical_devices_out.clear();
while (true) {
uint32_t physical_device_count = 0;
const VkResult get_physical_device_count_result =
functions_.vkEnumeratePhysicalDevices(instance_, &physical_device_count,
nullptr);
if ((get_physical_device_count_result != VK_SUCCESS &&
get_physical_device_count_result != VK_INCOMPLETE) ||
!physical_device_count) {
return;
}
physical_devices_out.resize(physical_device_count);
const VkResult get_physical_devices_result =
functions_.vkEnumeratePhysicalDevices(instance_, &physical_device_count,
physical_devices_out.data());
if (get_physical_devices_result == VK_INCOMPLETE) {
continue;
}
physical_devices_out.resize(
get_physical_devices_result == VK_SUCCESS ? physical_device_count : 0);
return;
}
}
VkBool32 VulkanInstance::DebugUtilsMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
[[maybe_unused]] void* user_data) {
xe::LogLevel log_level;
char log_prefix_char;
if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
log_level = xe::LogLevel::Error;
log_prefix_char = xe::logging::kPrefixCharError;
} else if (message_severity >=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
log_level = xe::LogLevel::Warning;
log_prefix_char = xe::logging::kPrefixCharWarning;
} else if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
log_level = xe::LogLevel::Info;
log_prefix_char = xe::logging::kPrefixCharInfo;
} else {
log_level = xe::LogLevel::Debug;
log_prefix_char = xe::logging::kPrefixCharDebug;
}
std::ostringstream log_str;
log_str << "Vulkan "
<< vk::to_string(
vk::DebugUtilsMessageSeverityFlagBitsEXT(message_severity))
<< " ("
<< vk::to_string(vk::DebugUtilsMessageTypeFlagsEXT(message_types))
<< ", ID " << callback_data->messageIdNumber;
if (callback_data->pMessageIdName) {
log_str << ": " << callback_data->pMessageIdName;
}
log_str << ')';
if (callback_data->pMessage) {
log_str << ": " << callback_data->pMessage;
}
bool annotations_begun = false;
const auto begin_annotation = [&log_str, &annotations_begun]() {
log_str << (annotations_begun ? ", " : " (");
annotations_begun = true;
};
for (uint32_t queue_label_index = 0;
queue_label_index < callback_data->queueLabelCount;
++queue_label_index) {
begin_annotation();
log_str << "queue label " << queue_label_index << ": "
<< callback_data->pQueueLabels[queue_label_index].pLabelName;
}
for (uint32_t cmd_buf_label_index = 0;
cmd_buf_label_index < callback_data->cmdBufLabelCount;
++cmd_buf_label_index) {
begin_annotation();
log_str << "command buffer label " << cmd_buf_label_index << ": "
<< callback_data->pCmdBufLabels[cmd_buf_label_index].pLabelName;
}
for (uint32_t object_index = 0; object_index < callback_data->objectCount;
++object_index) {
begin_annotation();
const VkDebugUtilsObjectNameInfoEXT& object_info =
callback_data->pObjects[object_index];
// Lowercase hexadecimal digits in the handle to match the default Vulkan
// debug utils messenger.
log_str << "object " << object_index << ": "
<< vk::to_string(vk::ObjectType(object_info.objectType)) << " 0x"
<< std::hex << object_info.objectHandle << std::dec;
if (object_info.pObjectName) {
log_str << " '" << object_info.pObjectName << '\'';
}
}
if (annotations_begun) {
log_str << ')';
}
xe::logging::AppendLogLine(log_level, log_prefix_char, log_str.str());
return VK_FALSE;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,145 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_INSTANCE_H_
#define XENIA_UI_VULKAN_VULKAN_INSTANCE_H_
#include <memory>
#include <vector>
#include "xenia/base/platform.h"
#include "xenia/ui/renderdoc_api.h"
#include "xenia/ui/vulkan/vulkan_api.h"
#if XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#endif
namespace xe {
namespace ui {
namespace vulkan {
class VulkanInstance {
public:
static std::unique_ptr<VulkanInstance> Create(bool with_surface,
bool try_enable_validation);
VulkanInstance(const VulkanInstance&) = delete;
VulkanInstance& operator=(const VulkanInstance&) = delete;
VulkanInstance(VulkanInstance&&) = delete;
VulkanInstance& operator=(VulkanInstance&&) = delete;
~VulkanInstance();
// nullptr if RenderDoc is not connected.
RenderDocAPI* renderdoc_api() const { return renderdoc_api_.get(); }
struct Functions {
// From the loader module.
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
PFN_vkDestroyInstance vkDestroyInstance = nullptr;
// From vkGetInstanceProcAddr for nullptr.
PFN_vkCreateInstance vkCreateInstance = nullptr;
PFN_vkEnumerateInstanceExtensionProperties
vkEnumerateInstanceExtensionProperties = nullptr;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties =
nullptr;
// Vulkan 1.1.
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = nullptr;
// From vkGetInstanceProcAddr for the instance.
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name = nullptr;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##core_name core_name = nullptr;
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
// VK_KHR_surface (#1)
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
// VK_KHR_xcb_surface (#6)
#ifdef VK_USE_PLATFORM_XCB_KHR
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
#endif
// VK_KHR_android_surface (#9)
#ifdef VK_USE_PLATFORM_ANDROID_KHR
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
#endif
// VK_KHR_win32_surface (#10)
#ifdef VK_USE_PLATFORM_WIN32_KHR
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
#endif
// VK_KHR_get_physical_device_properties2 (#60, promoted to 1.1)
#include "xenia/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc"
// VK_EXT_debug_utils (#129)
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const Functions& functions() const { return functions_; }
uint32_t api_version() const { return api_version_; }
// Also set to true if the version of the Vulkan API they were promoted to it
// supported (with the `ext_major_minor_` prefix rather than `ext_`).
struct Extensions {
bool ext_KHR_surface = false; // #1
#ifdef VK_USE_PLATFORM_XCB_KHR
bool ext_KHR_xcb_surface = false; // #6
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
bool ext_KHR_android_surface = false; // #9
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
bool ext_KHR_win32_surface = false; // #10
#endif
bool ext_1_1_KHR_get_physical_device_properties2 = false; // #60
bool ext_EXT_debug_utils = false; // #129
bool ext_KHR_portability_enumeration = false; // #395
};
const Extensions& extensions() const { return extensions_; }
VkInstance instance() const { return instance_; }
void EnumeratePhysicalDevices(
std::vector<VkPhysicalDevice>& physical_devices_out) const;
private:
explicit VulkanInstance() = default;
std::unique_ptr<RenderDocAPI> renderdoc_api_;
#if XE_PLATFORM_LINUX
void* loader_ = nullptr;
#elif XE_PLATFORM_WIN32
HMODULE loader_ = nullptr;
#endif
Functions functions_;
uint32_t api_version_ = VK_MAKE_API_VERSION(0, 1, 0, 0);
Extensions extensions_;
VkInstance instance_ = nullptr;
static VkBool32 DebugUtilsMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
VkDebugUtilsMessengerEXT debug_utils_messenger_ = VK_NULL_HANDLE;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_INSTANCE_H_

View File

@@ -14,25 +14,24 @@
#include <cstring>
#include "xenia/base/logging.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
bool externally_synchronized) {
const VulkanProvider::LibraryFunctions& lfn = provider.lfn();
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
const VulkanProvider::InstanceExtensions& instance_extensions =
provider.instance_extensions();
const VulkanProvider::DeviceInfo& device_info = provider.device_info();
VmaAllocator CreateVmaAllocator(const VulkanDevice* const vulkan_device,
const bool externally_synchronized) {
assert_not_null(vulkan_device);
const VulkanInstance* const vulkan_instance =
vulkan_device->vulkan_instance();
const VulkanInstance::Functions& ifn = vulkan_instance->functions();
const VulkanDevice::Functions& dfn = vulkan_device->functions();
VmaVulkanFunctions vma_vulkan_functions = {};
VmaAllocatorCreateInfo allocator_create_info = {};
vma_vulkan_functions.vkGetInstanceProcAddr = lfn.vkGetInstanceProcAddr;
vma_vulkan_functions.vkGetInstanceProcAddr = ifn.vkGetInstanceProcAddr;
vma_vulkan_functions.vkGetDeviceProcAddr = ifn.vkGetDeviceProcAddr;
vma_vulkan_functions.vkGetPhysicalDeviceProperties =
ifn.vkGetPhysicalDeviceProperties;
@@ -57,29 +56,30 @@ VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
vma_vulkan_functions.vkCreateImage = dfn.vkCreateImage;
vma_vulkan_functions.vkDestroyImage = dfn.vkDestroyImage;
vma_vulkan_functions.vkCmdCopyBuffer = dfn.vkCmdCopyBuffer;
if (device_info.ext_1_1_VK_KHR_get_memory_requirements2) {
if (vulkan_device->extensions().ext_1_1_KHR_get_memory_requirements2) {
vma_vulkan_functions.vkGetBufferMemoryRequirements2KHR =
dfn.vkGetBufferMemoryRequirements2;
vma_vulkan_functions.vkGetImageMemoryRequirements2KHR =
dfn.vkGetImageMemoryRequirements2;
if (device_info.ext_1_1_VK_KHR_dedicated_allocation) {
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
allocator_create_info.flags |=
VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
}
}
if (device_info.ext_1_1_VK_KHR_bind_memory2) {
if (vulkan_device->extensions().ext_1_1_KHR_bind_memory2) {
vma_vulkan_functions.vkBindBufferMemory2KHR = dfn.vkBindBufferMemory2;
vma_vulkan_functions.vkBindImageMemory2KHR = dfn.vkBindImageMemory2;
allocator_create_info.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT;
}
if (instance_extensions.khr_get_physical_device_properties2) {
if (vulkan_instance->extensions()
.ext_1_1_KHR_get_physical_device_properties2) {
vma_vulkan_functions.vkGetPhysicalDeviceMemoryProperties2KHR =
ifn.vkGetPhysicalDeviceMemoryProperties2;
if (device_info.ext_VK_EXT_memory_budget) {
if (vulkan_device->extensions().ext_EXT_memory_budget) {
allocator_create_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
}
if (device_info.ext_1_3_VK_KHR_maintenance4) {
if (vulkan_device->extensions().ext_1_3_KHR_maintenance4) {
vma_vulkan_functions.vkGetDeviceBufferMemoryRequirements =
dfn.vkGetDeviceBufferMemoryRequirements;
vma_vulkan_functions.vkGetDeviceImageMemoryRequirements =
@@ -90,11 +90,12 @@ VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
allocator_create_info.flags |=
VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
}
allocator_create_info.physicalDevice = provider.physical_device();
allocator_create_info.device = provider.device();
allocator_create_info.physicalDevice = vulkan_device->physical_device();
allocator_create_info.device = vulkan_device->device();
allocator_create_info.pVulkanFunctions = &vma_vulkan_functions;
allocator_create_info.instance = provider.instance();
allocator_create_info.vulkanApiVersion = device_info.apiVersion;
allocator_create_info.instance = vulkan_instance->instance();
allocator_create_info.vulkanApiVersion =
vulkan_device->properties().apiVersion;
VmaAllocator allocator;
if (vmaCreateAllocator(&allocator_create_info, &allocator) != VK_SUCCESS) {
XELOGE("Failed to create a Vulkan Memory Allocator instance");

View File

@@ -13,7 +13,7 @@
// Make sure vulkan.h is included from third_party (rather than from the system
// include directory) before vk_mem_alloc.h.
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#define VMA_STATIC_VULKAN_FUNCTIONS 0
// Work around the pointer nullability completeness warnings on Clang.
@@ -29,7 +29,7 @@ namespace xe {
namespace ui {
namespace vulkan {
VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
VmaAllocator CreateVmaAllocator(const VulkanDevice* vulkan_device,
bool externally_synchronized);
} // namespace vulkan

View File

@@ -73,8 +73,8 @@ namespace shaders {
} // namespace shaders
VulkanPresenter::PaintContext::Submission::~Submission() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (draw_command_pool_ != VK_NULL_HANDLE) {
dfn.vkDestroyCommandPool(device, draw_command_pool_, nullptr);
@@ -89,8 +89,8 @@ VulkanPresenter::PaintContext::Submission::~Submission() {
}
bool VulkanPresenter::PaintContext::Submission::Initialize() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkSemaphoreCreateInfo semaphore_create_info;
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
@@ -116,7 +116,7 @@ bool VulkanPresenter::PaintContext::Submission::Initialize() {
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex =
provider_.queue_family_graphics_compute();
vulkan_device_->queue_family_graphics_compute();
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr,
&draw_command_pool_) != VK_SUCCESS) {
XELOGE(
@@ -161,8 +161,8 @@ VulkanPresenter::~VulkanPresenter() {
ui_submission_tracker_.Shutdown();
guest_output_image_refresher_submission_tracker_.Shutdown();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (paint_context_.swapchain_render_pass != VK_NULL_HANDLE) {
dfn.vkDestroyRenderPass(device, paint_context_.swapchain_render_pass,
@@ -207,11 +207,36 @@ VulkanPresenter::~VulkanPresenter() {
guest_output_paint_image_descriptor_set_layout_);
}
Surface::TypeFlags VulkanPresenter::GetSupportedSurfaceTypes() const {
if (!provider_.device_info().ext_VK_KHR_swapchain) {
Surface::TypeFlags VulkanPresenter::GetSurfaceTypesSupportedByInstance(
const VulkanInstance::Extensions& instance_extensions) {
if (!instance_extensions.ext_KHR_surface) {
return 0;
}
return GetSurfaceTypesSupportedByInstance(provider_.instance_extensions());
Surface::TypeFlags type_flags = 0;
#if XE_PLATFORM_ANDROID
if (instance_extensions.ext_KHR_android_surface) {
type_flags |= Surface::kTypeFlag_AndroidNativeWindow;
}
#endif
#if XE_PLATFORM_GNU_LINUX
if (instance_extensions.ext_KHR_xcb_surface) {
type_flags |= Surface::kTypeFlag_XcbWindow;
}
#endif
#if XE_PLATFORM_WIN32
if (instance_extensions.ext_KHR_win32_surface) {
type_flags |= Surface::kTypeFlag_Win32Hwnd;
}
#endif
return type_flags;
}
Surface::TypeFlags VulkanPresenter::GetSupportedSurfaceTypes() const {
if (!vulkan_device_->extensions().ext_KHR_swapchain) {
return 0;
}
return GetSurfaceTypesSupportedByInstance(
vulkan_device_->vulkan_instance()->extensions());
}
bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
@@ -239,14 +264,14 @@ bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
VkBuffer buffer;
VkDeviceMemory buffer_memory;
if (!util::CreateDedicatedAllocationBuffer(
provider_, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
vulkan_device_, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
util::MemoryPurpose::kReadback, buffer, buffer_memory)) {
XELOGE("VulkanPresenter: Failed to create the guest output capture buffer");
return false;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
{
VkCommandPoolCreateInfo command_pool_create_info;
@@ -254,7 +279,7 @@ bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex =
provider_.queue_family_graphics_compute();
vulkan_device_->queue_family_graphics_compute();
VkCommandPool command_pool;
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr,
&command_pool) != VK_SUCCESS) {
@@ -361,7 +386,7 @@ bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
VulkanSubmissionTracker submission_tracker(provider_);
VulkanSubmissionTracker submission_tracker(vulkan_device_);
{
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
submission_tracker.AcquireFenceToAdvanceSubmission());
@@ -377,11 +402,12 @@ bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
}
VkResult submit_result;
{
VulkanProvider::QueueAcquisition queue_acquisition(
provider_.AcquireQueue(provider_.queue_family_graphics_compute(),
0));
submit_result = dfn.vkQueueSubmit(
queue_acquisition.queue, 1, &submit_info, fence_acqusition.fence());
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(
vulkan_device_->queue_family_graphics_compute(), 0);
submit_result =
dfn.vkQueueSubmit(queue_acquisition.queue(), 1, &submit_info,
fence_acqusition.fence());
}
if (submit_result != VK_SUCCESS) {
XELOGE(
@@ -442,8 +468,8 @@ VkCommandBuffer VulkanPresenter::AcquireUISetupCommandBufferFromUIThread() {
.command_buffer;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkCommandBufferBeginInfo command_buffer_begin_info;
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@@ -488,7 +514,7 @@ VkCommandBuffer VulkanPresenter::AcquireUISetupCommandBufferFromUIThread() {
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex =
provider_.queue_family_graphics_compute();
vulkan_device_->queue_family_graphics_compute();
VkCommandPool new_command_pool;
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr,
&new_command_pool) != VK_SUCCESS) {
@@ -529,11 +555,12 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(
Surface& new_surface, uint32_t new_surface_width,
uint32_t new_surface_height, bool was_paintable,
bool& is_vsync_implicit_out) {
const VulkanProvider::InstanceFunctions& ifn = provider_.ifn();
VkInstance instance = provider_.instance();
VkPhysicalDevice physical_device = provider_.physical_device();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanInstance* const vulkan_instance =
vulkan_device_->vulkan_instance();
const VulkanInstance::Functions& ifn = vulkan_instance->functions();
const VkInstance instance = vulkan_instance->instance();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkFormat new_swapchain_format;
@@ -550,7 +577,7 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(
paint_context_.PrepareForSwapchainRetirement();
bool surface_unusable;
paint_context_.swapchain = PaintContext::CreateSwapchainForVulkanSurface(
provider_, paint_context_.vulkan_surface, new_surface_width,
vulkan_device_, paint_context_.vulkan_surface, new_surface_width,
new_surface_height, old_swapchain, paint_context_.present_queue_family,
new_swapchain_format, paint_context_.swapchain_extent,
paint_context_.swapchain_is_fifo, surface_unusable);
@@ -641,7 +668,7 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(
}
bool surface_unusable;
paint_context_.swapchain = PaintContext::CreateSwapchainForVulkanSurface(
provider_, paint_context_.vulkan_surface, new_surface_width,
vulkan_device_, paint_context_.vulkan_surface, new_surface_width,
new_surface_height, VK_NULL_HANDLE, paint_context_.present_queue_family,
new_swapchain_format, paint_context_.swapchain_extent,
paint_context_.swapchain_is_fifo, surface_unusable);
@@ -837,7 +864,7 @@ bool VulkanPresenter::RefreshGuestOutputImpl(
assert_not_zero(frontbuffer_width);
assert_not_zero(frontbuffer_height);
VkExtent2D max_framebuffer_extent =
util::GetMax2DFramebufferExtent(provider_);
util::GetMax2DFramebufferExtent(vulkan_device_->properties());
if (frontbuffer_width > max_framebuffer_extent.width ||
frontbuffer_height > max_framebuffer_extent.height) {
// Writing the guest output isn't supposed to rescale, and a guest texture
@@ -856,7 +883,7 @@ bool VulkanPresenter::RefreshGuestOutputImpl(
}
if (!image_instance.image) {
std::unique_ptr<GuestOutputImage> new_image = GuestOutputImage::Create(
provider_, frontbuffer_width, frontbuffer_height);
vulkan_device_, frontbuffer_width, frontbuffer_height);
if (!new_image) {
return false;
}
@@ -883,14 +910,15 @@ bool VulkanPresenter::RefreshGuestOutputImpl(
// "Fence signal operations that are defined by vkQueueSubmit additionally
// include in the first synchronization scope all commands that occur earlier
// in submission order."
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
{
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
guest_output_image_refresher_submission_tracker_
.AcquireFenceToAdvanceSubmission());
VulkanProvider::QueueAcquisition queue_acquisition(
provider_.AcquireQueue(provider_.queue_family_graphics_compute(), 0));
if (dfn.vkQueueSubmit(queue_acquisition.queue, 0, nullptr,
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(
vulkan_device_->queue_family_graphics_compute(), 0);
if (dfn.vkQueueSubmit(queue_acquisition.queue(), 0, nullptr,
fence_acqusition.fence()) != VK_SUCCESS) {
fence_acqusition.SubmissionSucceededSignalFailed();
}
@@ -900,18 +928,18 @@ bool VulkanPresenter::RefreshGuestOutputImpl(
}
VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
const VulkanProvider& provider, VkSurfaceKHR surface, uint32_t width,
const VulkanDevice* vulkan_device, VkSurfaceKHR surface, uint32_t width,
uint32_t height, VkSwapchainKHR old_swapchain,
uint32_t& present_queue_family_out, VkFormat& image_format_out,
VkExtent2D& image_extent_out, bool& is_fifo_out,
bool& ui_surface_unusable_out) {
ui_surface_unusable_out = false;
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
VkInstance instance = provider.instance();
VkPhysicalDevice physical_device = provider.physical_device();
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VulkanInstance::Functions& ifn =
vulkan_device->vulkan_instance()->functions();
const VkPhysicalDevice physical_device = vulkan_device->physical_device();
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
// Get surface capabilities.
VkSurfaceCapabilitiesKHR surface_capabilities;
@@ -934,7 +962,8 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
// requirements - the maximum 2D framebuffer size on the specific physical
// device, and the minimum swap chain size on the whole instance - fail to
// create until the surface becomes smaller).
VkExtent2D max_framebuffer_extent = util::GetMax2DFramebufferExtent(provider);
VkExtent2D max_framebuffer_extent =
util::GetMax2DFramebufferExtent(vulkan_device->properties());
VkExtent2D image_extent;
image_extent.width =
std::min(std::max(std::min(width, max_framebuffer_extent.width),
@@ -952,17 +981,17 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
// Get the queue family for presentation.
uint32_t queue_family_index_present = UINT32_MAX;
const std::vector<VulkanProvider::QueueFamily>& queue_families =
provider.queue_families();
const std::vector<VulkanDevice::QueueFamily>& queue_families =
vulkan_device->queue_families();
VkBool32 queue_family_present_supported;
// First try the graphics and compute queue, prefer it to avoid the concurrent
// image sharing mode.
uint32_t queue_family_index_graphics_compute =
provider.queue_family_graphics_compute();
const VulkanProvider::QueueFamily& queue_family_graphics_compute =
vulkan_device->queue_family_graphics_compute();
const VulkanDevice::QueueFamily& queue_family_graphics_compute =
queue_families[queue_family_index_graphics_compute];
if (queue_family_graphics_compute.potentially_supports_present &&
queue_family_graphics_compute.queue_count &&
if (queue_family_graphics_compute.may_support_presentation &&
!queue_family_graphics_compute.queues.empty() &&
ifn.vkGetPhysicalDeviceSurfaceSupportKHR(
physical_device, queue_family_index_graphics_compute, surface,
&queue_family_present_supported) == VK_SUCCESS &&
@@ -970,9 +999,9 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
queue_family_index_present = queue_family_index_graphics_compute;
} else {
for (uint32_t i = 0; i < uint32_t(queue_families.size()); ++i) {
const VulkanProvider::QueueFamily& queue_family = queue_families[i];
if (queue_family.potentially_supports_present &&
queue_family.queue_count &&
const VulkanDevice::QueueFamily& queue_family = queue_families[i];
if (!queue_family.queues.empty() &&
queue_family.may_support_presentation &&
ifn.vkGetPhysicalDeviceSurfaceSupportKHR(
physical_device, i, surface, &queue_family_present_supported) ==
VK_SUCCESS &&
@@ -1228,7 +1257,7 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
XELOGE("VulkanPresenter: Failed to create a swapchain");
return VK_NULL_HANDLE;
}
XELOGVK(
XELOGI(
"VulkanPresenter: Created {}x{} swapchain with format {}, color space "
"{}, presentation mode {}",
swapchain_create_info.imageExtent.width,
@@ -1250,8 +1279,8 @@ VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() {
if (swapchain != VK_NULL_HANDLE) {
submission_tracker.AwaitAllSubmissionsCompletion();
}
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
for (const SwapchainFramebuffer& framebuffer : swapchain_framebuffers) {
dfn.vkDestroyFramebuffer(device, framebuffer.framebuffer, nullptr);
dfn.vkDestroyImageView(device, framebuffer.image_view, nullptr);
@@ -1269,22 +1298,21 @@ VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() {
void VulkanPresenter::PaintContext::DestroySwapchainAndVulkanSurface() {
VkSwapchainKHR old_swapchain = PrepareForSwapchainRetirement();
if (old_swapchain != VK_NULL_HANDLE) {
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
dfn.vkDestroySwapchainKHR(device, old_swapchain, nullptr);
vulkan_device->functions().vkDestroySwapchainKHR(vulkan_device->device(),
old_swapchain, nullptr);
}
present_queue_family = UINT32_MAX;
if (vulkan_surface != VK_NULL_HANDLE) {
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
VkInstance instance = provider.instance();
ifn.vkDestroySurfaceKHR(instance, vulkan_surface, nullptr);
const VulkanInstance* vulkan_instance = vulkan_device->vulkan_instance();
vulkan_instance->functions().vkDestroySurfaceKHR(
vulkan_instance->instance(), vulkan_surface, nullptr);
vulkan_surface = VK_NULL_HANDLE;
}
}
VulkanPresenter::GuestOutputImage::~GuestOutputImage() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (view_ != VK_NULL_HANDLE) {
dfn.vkDestroyImageView(device, view_, nullptr);
}
@@ -1318,14 +1346,14 @@ bool VulkanPresenter::GuestOutputImage::Initialize() {
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (!ui::vulkan::util::CreateDedicatedAllocationImage(
provider_, image_create_info,
vulkan_device_, image_create_info,
ui::vulkan::util::MemoryPurpose::kDeviceLocal, image_, memory_)) {
XELOGE("VulkanPresenter: Failed to create a guest output image");
return false;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkImageViewCreateInfo image_view_create_info;
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -1368,8 +1396,8 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
*paint_context_.submissions[current_paint_submission_index %
paint_submission_count];
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkCommandPool draw_command_pool = paint_submission.draw_command_pool();
if (dfn.vkResetCommandPool(device, draw_command_pool, 0) != VK_SUCCESS) {
@@ -1414,7 +1442,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
// Not an error, reporting just as info (may normally occur while resizing
// on some platforms).
XELOGVK(
XELOGI(
"VulkanPresenter: Presentation to the swapchain image has been "
"dropped as the swapchain or the surface has become outdated");
return PaintResult::kNotPresentedConnectionOutdated;
@@ -1482,7 +1510,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
if (guest_output_image) {
VkExtent2D max_framebuffer_extent =
util::GetMax2DFramebufferExtent(provider_);
util::GetMax2DFramebufferExtent(vulkan_device_->properties());
GuestOutputPaintFlow guest_output_flow = GetGuestOutputPaintFlow(
guest_output_properties, paint_context_.swapchain_extent.width,
paint_context_.swapchain_extent.height, max_framebuffer_extent.width,
@@ -1599,7 +1627,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
}
// Image.
intermediate_image_ptr_ref = GuestOutputImage::Create(
provider_, intermediate_needed_size.first,
vulkan_device_, intermediate_needed_size.first,
intermediate_needed_size.second);
if (!intermediate_image_ptr_ref) {
// Don't display the guest output, and don't try to create more
@@ -2029,13 +2057,14 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
}
VkResult submit_result;
{
VulkanProvider::QueueAcquisition queue_acquisition(
provider_.AcquireQueue(provider_.queue_family_graphics_compute(), 0));
submit_result = dfn.vkQueueSubmit(queue_acquisition.queue, 1,
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(
vulkan_device_->queue_family_graphics_compute(), 0);
submit_result = dfn.vkQueueSubmit(queue_acquisition.queue(), 1,
&submit_info, fence_acqusition.fence());
if (ui_fence_acquisition.fence() != VK_NULL_HANDLE &&
submit_result == VK_SUCCESS) {
if (dfn.vkQueueSubmit(queue_acquisition.queue, 0, nullptr,
if (dfn.vkQueueSubmit(queue_acquisition.queue(), 0, nullptr,
ui_fence_acquisition.fence()) != VK_SUCCESS) {
ui_fence_acquisition.SubmissionSucceededSignalFailed();
}
@@ -2072,10 +2101,10 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
present_info.pResults = nullptr;
VkResult present_result;
{
VulkanProvider::QueueAcquisition queue_acquisition(
provider_.AcquireQueue(paint_context_.present_queue_family, 0));
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(paint_context_.present_queue_family, 0);
present_result =
dfn.vkQueuePresentKHR(queue_acquisition.queue, &present_info);
dfn.vkQueuePresentKHR(queue_acquisition.queue(), &present_info);
}
switch (present_result) {
case VK_SUCCESS:
@@ -2092,7 +2121,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
// Not an error, reporting just as info (may normally occur while resizing
// on some platforms).
XELOGVK(
XELOGI(
"VulkanPresenter: Presentation to the swapchain image has been "
"dropped as the swapchain or the surface has become outdated");
// Note that the semaphore wait (followed by reset) has been enqueued,
@@ -2108,8 +2137,8 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(
}
bool VulkanPresenter::InitializeSurfaceIndependent() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkDescriptorSetLayoutBinding guest_output_image_sampler_bindings[2];
guest_output_image_sampler_bindings[0].binding = 0;
@@ -2119,8 +2148,8 @@ bool VulkanPresenter::InitializeSurfaceIndependent() {
guest_output_image_sampler_bindings[0].stageFlags =
VK_SHADER_STAGE_FRAGMENT_BIT;
guest_output_image_sampler_bindings[0].pImmutableSamplers = nullptr;
VkSampler sampler_linear_clamp =
provider_.GetHostSampler(VulkanProvider::HostSampler::kLinearClamp);
const VkSampler sampler_linear_clamp =
ui_samplers_->samplers()[UISamplers::kSamplerIndexLinearClampToEdge];
guest_output_image_sampler_bindings[1].binding = 1;
guest_output_image_sampler_bindings[1].descriptorType =
VK_DESCRIPTOR_TYPE_SAMPLER;
@@ -2371,7 +2400,8 @@ bool VulkanPresenter::InitializeSurfaceIndependent() {
// Initialize connection-independent parts of the painting context.
for (size_t i = 0; i < paint_context_.submissions.size(); ++i) {
paint_context_.submissions[i] = PaintContext::Submission::Create(provider_);
paint_context_.submissions[i] =
PaintContext::Submission::Create(vulkan_device_);
if (!paint_context_.submissions[i]) {
return false;
}
@@ -2536,8 +2566,8 @@ VkPipeline VulkanPresenter::CreateGuestOutputPaintPipeline(
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
pipeline_create_info.basePipelineIndex = -1;
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkPipeline pipeline;
if (dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1,

View File

@@ -19,10 +19,11 @@
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/surface.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_instance.h"
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
namespace xe {
@@ -90,7 +91,7 @@ class VulkanPresenter final : public Presenter {
static constexpr VkImageLayout kGuestOutputInternalLayout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// The callback must use the graphics and compute queue 0 of the provider.
// The callback must use the graphics and compute queue 0 of the device.
class VulkanGuestOutputRefreshContext final
: public GuestOutputRefreshContext {
public:
@@ -123,9 +124,10 @@ class VulkanPresenter final : public Presenter {
};
static std::unique_ptr<VulkanPresenter> Create(
HostGpuLossCallback host_gpu_loss_callback, VulkanProvider& provider) {
auto presenter = std::unique_ptr<VulkanPresenter>(
new VulkanPresenter(host_gpu_loss_callback, provider));
HostGpuLossCallback host_gpu_loss_callback,
const VulkanDevice* vulkan_device, const UISamplers* ui_samplers) {
auto presenter = std::unique_ptr<VulkanPresenter>(new VulkanPresenter(
host_gpu_loss_callback, vulkan_device, ui_samplers));
if (!presenter->InitializeSurfaceIndependent()) {
return nullptr;
}
@@ -134,29 +136,10 @@ class VulkanPresenter final : public Presenter {
~VulkanPresenter();
VulkanProvider& provider() const { return provider_; }
const VulkanDevice* vulkan_device() const { return vulkan_device_; }
static Surface::TypeFlags GetSurfaceTypesSupportedByInstance(
const VulkanProvider::InstanceExtensions& instance_extensions) {
if (!instance_extensions.khr_surface) {
return 0;
}
Surface::TypeFlags type_flags = 0;
#if XE_PLATFORM_ANDROID
if (instance_extensions.khr_android_surface) {
type_flags |= Surface::kTypeFlag_AndroidNativeWindow;
}
#elif XE_PLATFORM_GNU_LINUX
if (instance_extensions.khr_xcb_surface) {
type_flags |= Surface::kTypeFlag_XcbWindow;
}
#elif XE_PLATFORM_WIN32
if (instance_extensions.khr_win32_surface) {
type_flags |= Surface::kTypeFlag_Win32Hwnd;
}
#endif
return type_flags;
}
const VulkanInstance::Extensions& instance_extensions);
Surface::TypeFlags GetSupportedSurfaceTypes() const override;
bool CaptureGuestOutput(RawImage& image_out) override;
@@ -186,11 +169,12 @@ class VulkanPresenter final : public Presenter {
class GuestOutputImage {
public:
static std::unique_ptr<GuestOutputImage> Create(
const VulkanProvider& provider, uint32_t width, uint32_t height) {
const VulkanDevice* const vulkan_device, const uint32_t width,
const uint32_t height) {
assert_not_zero(width);
assert_not_zero(height);
auto image = std::unique_ptr<GuestOutputImage>(
new GuestOutputImage(provider, width, height));
new GuestOutputImage(vulkan_device, width, height));
if (!image->Initialize()) {
return nullptr;
}
@@ -208,16 +192,16 @@ class VulkanPresenter final : public Presenter {
VkImageView view() const { return view_; }
private:
GuestOutputImage(const VulkanProvider& provider, uint32_t width,
uint32_t height)
: provider_(provider) {
GuestOutputImage(const VulkanDevice* const vulkan_device,
const uint32_t width, const uint32_t height)
: vulkan_device_(vulkan_device) {
extent_.width = width;
extent_.height = height;
}
bool Initialize();
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
VkExtent2D extent_;
VkImage image_ = VK_NULL_HANDLE;
@@ -299,8 +283,9 @@ class VulkanPresenter final : public Presenter {
class Submission {
public:
static std::unique_ptr<Submission> Create(
const VulkanProvider& provider) {
auto submission = std::unique_ptr<Submission>(new Submission(provider));
const VulkanDevice* const vulkan_device) {
auto submission =
std::unique_ptr<Submission>(new Submission(vulkan_device));
if (!submission->Initialize()) {
return nullptr;
}
@@ -319,11 +304,11 @@ class VulkanPresenter final : public Presenter {
}
private:
explicit Submission(const VulkanProvider& provider)
: provider_(provider) {}
explicit Submission(const VulkanDevice* const vulkan_device)
: vulkan_device_(vulkan_device) {}
bool Initialize();
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
VkSemaphore acquire_semaphore_ = VK_NULL_HANDLE;
VkSemaphore present_semaphore_ = VK_NULL_HANDLE;
VkCommandPool draw_command_pool_ = VK_NULL_HANDLE;
@@ -376,8 +361,8 @@ class VulkanPresenter final : public Presenter {
VkFramebuffer framebuffer;
};
explicit PaintContext(VulkanProvider& provider)
: provider(provider), submission_tracker(provider) {}
explicit PaintContext(const VulkanDevice* const vulkan_device)
: vulkan_device(vulkan_device), submission_tracker(vulkan_device) {}
PaintContext(const PaintContext& paint_context) = delete;
PaintContext& operator=(const PaintContext& paint_context) = delete;
@@ -386,7 +371,7 @@ class VulkanPresenter final : public Presenter {
// technically retire it, so it will be in an undefined state), and needs to
// be destroyed externally no matter what the result is.
static VkSwapchainKHR CreateSwapchainForVulkanSurface(
const VulkanProvider& provider, VkSurfaceKHR surface, uint32_t width,
const VulkanDevice* vulkan_device, VkSurfaceKHR surface, uint32_t width,
uint32_t height, VkSwapchainKHR old_swapchain,
uint32_t& present_queue_family_out, VkFormat& image_format_out,
VkExtent2D& image_extent_out, bool& is_fifo_out,
@@ -401,7 +386,7 @@ class VulkanPresenter final : public Presenter {
// Connection-indepedent.
const VulkanProvider& provider;
const VulkanDevice* vulkan_device;
std::array<std::unique_ptr<PaintContext::Submission>, kSubmissionCount>
submissions;
@@ -460,19 +445,25 @@ class VulkanPresenter final : public Presenter {
};
explicit VulkanPresenter(HostGpuLossCallback host_gpu_loss_callback,
VulkanProvider& provider)
const VulkanDevice* vulkan_device,
const UISamplers* ui_samplers)
: Presenter(host_gpu_loss_callback),
provider_(provider),
guest_output_image_refresher_submission_tracker_(provider),
ui_submission_tracker_(provider),
paint_context_(provider) {}
vulkan_device_(vulkan_device),
ui_samplers_(ui_samplers),
guest_output_image_refresher_submission_tracker_(vulkan_device),
ui_submission_tracker_(vulkan_device),
paint_context_(vulkan_device) {
assert_not_null(vulkan_device);
assert_not_null(ui_samplers);
}
bool InitializeSurfaceIndependent();
[[nodiscard]] VkPipeline CreateGuestOutputPaintPipeline(
GuestOutputPaintEffect effect, VkRenderPass render_pass);
VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
const UISamplers* ui_samplers_;
// Static objects for guest output presentation, used only when painting the
// main target (can be destroyed only after awaiting main target usage

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,46 +10,12 @@
#ifndef XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
#define XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/renderdoc_api.h"
#if XE_PLATFORM_ANDROID
#ifndef VK_USE_PLATFORM_ANDROID_KHR
#define VK_USE_PLATFORM_ANDROID_KHR 1
#endif
#elif XE_PLATFORM_GNU_LINUX
#ifndef VK_USE_PLATFORM_XCB_KHR
#define VK_USE_PLATFORM_XCB_KHR 1
#endif
#elif XE_PLATFORM_WIN32
// Must be included before vulkan.h with VK_USE_PLATFORM_WIN32_KHR because it
// includes Windows.h too.
#include "xenia/base/platform_win.h"
#ifndef VK_USE_PLATFORM_WIN32_KHR
#define VK_USE_PLATFORM_WIN32_KHR 1
#endif
#endif
#ifndef VK_ENABLE_BETA_EXTENSIONS
#define VK_ENABLE_BETA_EXTENSIONS 1
#endif
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES 1
#endif
#include "third_party/Vulkan-Headers/include/vulkan/vulkan.h"
#define XELOGVK XELOGI
#define XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES 1
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_instance.h"
namespace xe {
namespace ui {
@@ -57,163 +23,15 @@ namespace vulkan {
class VulkanProvider : public GraphicsProvider {
public:
struct DeviceInfo {
// "ext_1_X"-prefixed extension fields are set to true not only if the
// extension itself is actually exposed, but also if it was promoted to the
// device's API version. Therefore, merely the field being set to true
// doesn't imply that all the required features in the extension are
// supported - actual properties and features must be checked rather than
// the extension itself where they matter.
static std::unique_ptr<VulkanProvider> Create(bool with_gpu_emulation,
bool with_presentation);
// Vulkan 1.0.
VulkanInstance* vulkan_instance() const { return vulkan_instance_.get(); }
uint32_t memory_types_device_local;
uint32_t memory_types_host_visible;
uint32_t memory_types_host_coherent;
uint32_t memory_types_host_cached;
VulkanDevice* vulkan_device() const { return vulkan_device_.get(); }
uint32_t apiVersion;
uint32_t maxImageDimension2D;
uint32_t maxImageDimension3D;
uint32_t maxImageDimensionCube;
uint32_t maxImageArrayLayers;
uint32_t maxStorageBufferRange;
uint32_t maxSamplerAllocationCount;
uint32_t maxPerStageDescriptorSamplers;
uint32_t maxPerStageDescriptorStorageBuffers;
uint32_t maxPerStageDescriptorSampledImages;
uint32_t maxPerStageResources;
uint32_t maxVertexOutputComponents;
uint32_t maxTessellationEvaluationOutputComponents;
uint32_t maxGeometryInputComponents;
uint32_t maxGeometryOutputComponents;
uint32_t maxGeometryTotalOutputComponents;
uint32_t maxFragmentInputComponents;
uint32_t maxFragmentCombinedOutputResources;
float maxSamplerAnisotropy;
uint32_t maxViewportDimensions[2];
float viewportBoundsRange[2];
VkDeviceSize minUniformBufferOffsetAlignment;
VkDeviceSize minStorageBufferOffsetAlignment;
uint32_t maxFramebufferWidth;
uint32_t maxFramebufferHeight;
VkSampleCountFlags framebufferColorSampleCounts;
VkSampleCountFlags framebufferDepthSampleCounts;
VkSampleCountFlags framebufferStencilSampleCounts;
VkSampleCountFlags framebufferNoAttachmentsSampleCounts;
VkSampleCountFlags sampledImageColorSampleCounts;
VkSampleCountFlags sampledImageIntegerSampleCounts;
VkSampleCountFlags sampledImageDepthSampleCounts;
VkSampleCountFlags sampledImageStencilSampleCounts;
VkSampleCountFlags standardSampleLocations;
VkDeviceSize optimalBufferCopyOffsetAlignment;
VkDeviceSize optimalBufferCopyRowPitchAlignment;
VkDeviceSize nonCoherentAtomSize;
bool fullDrawIndexUint32;
bool independentBlend;
bool geometryShader;
bool tessellationShader;
bool sampleRateShading;
bool depthClamp;
bool fillModeNonSolid;
bool samplerAnisotropy;
bool vertexPipelineStoresAndAtomics;
bool fragmentStoresAndAtomics;
bool shaderClipDistance;
bool shaderCullDistance;
bool sparseBinding;
bool sparseResidencyBuffer;
// VK_KHR_swapchain (#2).
bool ext_VK_KHR_swapchain;
// VK_KHR_sampler_mirror_clamp_to_edge (#15, Vulkan 1.2).
bool ext_1_2_VK_KHR_sampler_mirror_clamp_to_edge;
bool samplerMirrorClampToEdge;
// VK_KHR_dedicated_allocation (#128, Vulkan 1.1).
bool ext_1_1_VK_KHR_dedicated_allocation;
// VK_EXT_shader_stencil_export (#141).
bool ext_VK_EXT_shader_stencil_export;
// VK_KHR_get_memory_requirements2 (#147, Vulkan 1.1).
bool ext_1_1_VK_KHR_get_memory_requirements2;
// VK_KHR_image_format_list (#148, Vulkan 1.2).
bool ext_1_2_VK_KHR_image_format_list;
// VK_KHR_sampler_ycbcr_conversion (#157, Vulkan 1.1).
bool ext_1_1_VK_KHR_sampler_ycbcr_conversion;
// VK_KHR_bind_memory2 (#158, Vulkan 1.1).
bool ext_1_1_VK_KHR_bind_memory2;
// VK_KHR_portability_subset (#164).
bool ext_VK_KHR_portability_subset;
bool constantAlphaColorBlendFactors;
bool imageViewFormatReinterpretation;
bool imageViewFormatSwizzle;
bool pointPolygons;
bool separateStencilMaskRef;
bool shaderSampleRateInterpolationFunctions;
bool triangleFans;
// VK_KHR_shader_float_controls (#198, Vulkan 1.2).
bool ext_1_2_VK_KHR_shader_float_controls;
bool shaderSignedZeroInfNanPreserveFloat32;
bool shaderDenormFlushToZeroFloat32;
bool shaderRoundingModeRTEFloat32;
// VK_KHR_spirv_1_4 (#237, Vulkan 1.2).
bool ext_1_2_VK_KHR_spirv_1_4;
// VK_EXT_memory_budget (#238).
bool ext_VK_EXT_memory_budget;
// VK_EXT_fragment_shader_interlock (#252).
bool ext_VK_EXT_fragment_shader_interlock;
bool fragmentShaderSampleInterlock;
bool fragmentShaderPixelInterlock;
// VK_EXT_shader_demote_to_helper_invocation (#277, Vulkan 1.3).
bool ext_1_3_VK_EXT_shader_demote_to_helper_invocation;
bool shaderDemoteToHelperInvocation;
// VK_KHR_maintenance4 (#414, Vulkan 1.3).
bool ext_1_3_VK_KHR_maintenance4;
// VK_EXT_non_seamless_cube_map (#423).
bool ext_VK_EXT_non_seamless_cube_map;
bool nonSeamlessCubeMap;
};
~VulkanProvider();
static std::unique_ptr<VulkanProvider> Create(bool is_surface_required);
// nullptr if created without presentation support.
const UISamplers* ui_samplers() const { return ui_samplers_.get(); }
std::unique_ptr<Presenter> CreatePresenter(
Presenter::HostGpuLossCallback host_gpu_loss_callback =
@@ -221,209 +39,16 @@ class VulkanProvider : public GraphicsProvider {
std::unique_ptr<ImmediateDrawer> CreateImmediateDrawer() override;
const RenderdocApi& renderdoc_api() const { return renderdoc_api_; }
struct LibraryFunctions {
// From the module.
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkDestroyInstance vkDestroyInstance;
// From vkGetInstanceProcAddr.
PFN_vkCreateInstance vkCreateInstance;
PFN_vkEnumerateInstanceExtensionProperties
vkEnumerateInstanceExtensionProperties;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
struct {
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
} v_1_1;
};
const LibraryFunctions& lfn() const { return lfn_; }
struct InstanceExtensions {
bool ext_debug_utils;
// Core since 1.1.0.
bool khr_get_physical_device_properties2;
// Surface extensions.
bool khr_surface;
#if XE_PLATFORM_ANDROID
bool khr_android_surface;
#elif XE_PLATFORM_GNU_LINUX
bool khr_xcb_surface;
#elif XE_PLATFORM_WIN32
bool khr_win32_surface;
#endif
};
const InstanceExtensions& instance_extensions() const {
return instance_extensions_;
}
VkInstance instance() const { return instance_; }
struct InstanceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##core_name core_name;
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
#include "xenia/ui/vulkan/functions/instance_khr_get_physical_device_properties2.inc"
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
#if XE_PLATFORM_ANDROID
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
#elif XE_PLATFORM_GNU_LINUX
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
#elif XE_PLATFORM_WIN32
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
#endif
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const InstanceFunctions& ifn() const { return ifn_; }
VkPhysicalDevice physical_device() const { return physical_device_; }
const DeviceInfo& device_info() const { return device_info_; }
struct QueueFamily {
uint32_t queue_first_index = 0;
uint32_t queue_count = 0;
bool potentially_supports_present = false;
};
const std::vector<QueueFamily>& queue_families() const {
return queue_families_;
}
// Required.
uint32_t queue_family_graphics_compute() const {
return queue_family_graphics_compute_;
}
// Optional, if sparse binding is supported (UINT32_MAX otherwise). May be the
// same as queue_family_graphics_compute_.
uint32_t queue_family_sparse_binding() const {
return queue_family_sparse_binding_;
}
struct Queue {
VkQueue queue = VK_NULL_HANDLE;
std::recursive_mutex mutex;
};
struct QueueAcquisition {
QueueAcquisition(std::unique_lock<std::recursive_mutex>&& lock,
VkQueue queue)
: lock(std::move(lock)), queue(queue) {}
std::unique_lock<std::recursive_mutex> lock;
VkQueue queue;
};
QueueAcquisition AcquireQueue(uint32_t index) {
Queue& queue = queues_[index];
return QueueAcquisition(std::unique_lock<std::recursive_mutex>(queue.mutex),
queue.queue);
}
QueueAcquisition AcquireQueue(uint32_t family_index, uint32_t index) {
assert_true(family_index != UINT32_MAX);
return AcquireQueue(queue_families_[family_index].queue_first_index +
index);
}
VkDevice device() const { return device_; }
struct DeviceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##core_name core_name;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
#include "xenia/ui/vulkan/functions/device_khr_bind_memory2.inc"
#include "xenia/ui/vulkan/functions/device_khr_get_memory_requirements2.inc"
#include "xenia/ui/vulkan/functions/device_khr_maintenance4.inc"
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const DeviceFunctions& dfn() const { return dfn_; }
template <typename T>
void SetDeviceObjectName(VkObjectType type, T handle,
const char* name) const {
if (!debug_names_used_) {
return;
}
VkDebugUtilsObjectNameInfoEXT name_info;
name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
name_info.pNext = nullptr;
name_info.objectType = type;
name_info.objectHandle = uint64_t(handle);
name_info.pObjectName = name;
ifn_.vkSetDebugUtilsObjectNameEXT(device_, &name_info);
}
// Samplers that may be useful for host needs. Only these samplers should be
// used in host, non-emulation contexts, because the total number of samplers
// is heavily limited (4000) on Nvidia GPUs - the rest of samplers are
// allocated for emulation.
enum class HostSampler {
kNearestClamp,
kLinearClamp,
kNearestRepeat,
kLinearRepeat,
kCount,
};
VkSampler GetHostSampler(HostSampler sampler) const {
return host_samplers_[size_t(sampler)];
}
private:
explicit VulkanProvider(bool is_surface_required)
: is_surface_required_(is_surface_required) {}
explicit VulkanProvider() = default;
bool Initialize();
std::unique_ptr<VulkanInstance> vulkan_instance_;
static void AccumulateInstanceExtensions(
size_t properties_count, const VkExtensionProperties* properties,
bool request_debug_utils, InstanceExtensions& instance_extensions,
std::vector<const char*>& instance_extensions_enabled);
// Depends on the instance.
std::unique_ptr<VulkanDevice> vulkan_device_;
static VkBool32 VKAPI_CALL DebugMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
// For the current `physical_device_`, sets up the members obtained from the
// physical device info, and tries to create a device and get the needed
// queues.
// The call is successful if `device_` is not VK_NULL_HANDLE as a result.
void TryCreateDevice();
bool is_surface_required_;
RenderdocApi renderdoc_api_;
#if XE_PLATFORM_LINUX
void* library_ = nullptr;
#elif XE_PLATFORM_WIN32
HMODULE library_ = nullptr;
#endif
LibraryFunctions lfn_ = {};
InstanceExtensions instance_extensions_;
VkInstance instance_ = VK_NULL_HANDLE;
InstanceFunctions ifn_;
VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE;
bool debug_names_used_ = false;
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
DeviceInfo device_info_ = {};
std::vector<QueueFamily> queue_families_;
uint32_t queue_family_graphics_compute_;
uint32_t queue_family_sparse_binding_;
VkDevice device_ = VK_NULL_HANDLE;
DeviceFunctions dfn_ = {};
// Queues contain a mutex, can't use std::vector.
std::unique_ptr<Queue[]> queues_;
VkSampler host_samplers_[size_t(HostSampler::kCount)] = {};
// Depends on the device.
std::unique_ptr<UISamplers> ui_samplers_;
};
} // namespace vulkan

View File

@@ -40,8 +40,8 @@ VulkanSubmissionTracker::FenceAcquisition::~FenceAcquisition() {
void VulkanSubmissionTracker::Shutdown() {
AwaitAllSubmissionsCompletion();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
for (VkFence fence : fences_reclaimed_) {
dfn.vkDestroyFence(device, fence, nullptr);
}
@@ -71,8 +71,8 @@ void VulkanSubmissionTracker::FenceAcquisition::SubmissionFailedOrDropped() {
uint64_t VulkanSubmissionTracker::UpdateAndGetCompletedSubmission() {
if (!fences_pending_.empty()) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
while (!fences_pending_.empty()) {
const std::pair<uint64_t, VkFence>& pending_pair =
fences_pending_.front();
@@ -113,8 +113,8 @@ bool VulkanSubmissionTracker::AwaitSubmissionCompletion(
// in submission order."
size_t reclaim_end = fences_pending_.size();
if (reclaim_end) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
while (reclaim_end) {
const std::pair<uint64_t, VkFence>& pending_pair =
fences_pending_[reclaim_end - 1];
@@ -149,8 +149,8 @@ VulkanSubmissionTracker::AcquireFenceToAdvanceSubmission() {
// Reclaim fences if the client only gets the completed submission index or
// awaits in special cases such as shutdown.
UpdateAndGetCompletedSubmission();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (!fences_reclaimed_.empty()) {
VkFence reclaimed_fence = fences_reclaimed_.back();
if (dfn.vkResetFences(device, 1, &reclaimed_fence) == VK_SUCCESS) {

View File

@@ -15,7 +15,8 @@
#include <utility>
#include <vector>
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
@@ -87,11 +88,16 @@ class VulkanSubmissionTracker {
bool signal_failed_ = false;
};
VulkanSubmissionTracker(VulkanProvider& provider) : provider_(provider) {}
VulkanSubmissionTracker(const VulkanDevice* vulkan_device)
: vulkan_device_(vulkan_device) {
assert_not_null(vulkan_device);
}
VulkanSubmissionTracker(const VulkanSubmissionTracker& submission_tracker) =
delete;
VulkanSubmissionTracker& operator=(
const VulkanSubmissionTracker& submission_tracker) = delete;
~VulkanSubmissionTracker() { Shutdown(); }
void Shutdown();
@@ -112,7 +118,7 @@ class VulkanSubmissionTracker {
[[nodiscard]] FenceAcquisition AcquireFenceToAdvanceSubmission();
private:
VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
uint64_t submission_current_ = 1;
// Last submission with a successful fence signal as well as a successful
// fence wait / query.

View File

@@ -24,12 +24,13 @@ namespace vulkan {
// or flush range must be clamped to the actual allocation size as a special
// case, but it's still unlikely that the allocation won't be aligned to it), so
// try not to waste that padding.
VulkanUploadBufferPool::VulkanUploadBufferPool(const VulkanProvider& provider,
VkBufferUsageFlags usage,
size_t page_size)
VulkanUploadBufferPool::VulkanUploadBufferPool(
const VulkanDevice* const vulkan_device, const VkBufferUsageFlags usage,
const size_t page_size)
: GraphicsUploadBufferPool(size_t(
util::GetMappableMemorySize(provider, VkDeviceSize(page_size)))),
provider_(provider),
xe::round_up(VkDeviceSize(page_size),
vulkan_device->properties().nonCoherentAtomSize))),
vulkan_device_(vulkan_device),
usage_(usage) {}
uint8_t* VulkanUploadBufferPool::Request(uint64_t submission_index, size_t size,
@@ -72,8 +73,8 @@ VulkanUploadBufferPool::CreatePageImplementation() {
return nullptr;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkBufferCreateInfo buffer_create_info;
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -94,8 +95,9 @@ VulkanUploadBufferPool::CreatePageImplementation() {
if (memory_type_ == kMemoryTypeUnknown) {
VkMemoryRequirements memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
memory_type_ = util::ChooseHostMemoryType(
provider_, memory_requirements.memoryTypeBits, false);
memory_type_ =
util::ChooseHostMemoryType(vulkan_device_->memory_types(),
memory_requirements.memoryTypeBits, false);
if (memory_type_ == UINT32_MAX) {
XELOGE(
"No host-visible memory types can store an Vulkan upload buffer with "
@@ -117,7 +119,8 @@ VulkanUploadBufferPool::CreatePageImplementation() {
dfn.vkGetBufferMemoryRequirements(device, buffer_expanded,
&memory_requirements_expanded);
uint32_t memory_type_expanded = util::ChooseHostMemoryType(
provider_, memory_requirements.memoryTypeBits, false);
vulkan_device_->memory_types(), memory_requirements.memoryTypeBits,
false);
if (memory_requirements_expanded.size <= allocation_size_ &&
memory_type_expanded != UINT32_MAX) {
page_size_ = size_t(allocation_size_);
@@ -139,7 +142,7 @@ VulkanUploadBufferPool::CreatePageImplementation() {
memory_allocate_info.allocationSize = allocation_size_;
memory_allocate_info.memoryTypeIndex = memory_type_;
VkMemoryDedicatedAllocateInfo memory_dedicated_allocate_info;
if (provider_.device_info().ext_1_1_VK_KHR_dedicated_allocation) {
if (vulkan_device_->extensions().ext_1_1_KHR_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
@@ -176,19 +179,19 @@ VulkanUploadBufferPool::CreatePageImplementation() {
return nullptr;
}
return new VulkanPage(provider_, buffer, memory, mapping);
return new VulkanPage(vulkan_device_, buffer, memory, mapping);
}
void VulkanUploadBufferPool::FlushPageWrites(Page* page, size_t offset,
size_t size) {
util::FlushMappedMemoryRange(
provider_, static_cast<const VulkanPage*>(page)->memory_, memory_type_,
VkDeviceSize(offset), allocation_size_, VkDeviceSize(size));
vulkan_device_, static_cast<const VulkanPage*>(page)->memory_,
memory_type_, VkDeviceSize(offset), allocation_size_, VkDeviceSize(size));
}
VulkanUploadBufferPool::VulkanPage::~VulkanPage() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
dfn.vkDestroyBuffer(device, buffer_, nullptr);
// Unmapping is done implicitly when the memory is freed.
dfn.vkFreeMemory(device, memory_, nullptr);

View File

@@ -11,7 +11,7 @@
#define XENIA_UI_VULKAN_VULKAN_UPLOAD_BUFFER_POOL_H_
#include "xenia/ui/graphics_upload_buffer_pool.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
@@ -19,7 +19,7 @@ namespace vulkan {
class VulkanUploadBufferPool : public GraphicsUploadBufferPool {
public:
VulkanUploadBufferPool(const VulkanProvider& provider,
VulkanUploadBufferPool(const VulkanDevice* vulkan_device,
VkBufferUsageFlags usage,
size_t page_size = kDefaultPageSize);
@@ -37,20 +37,20 @@ class VulkanUploadBufferPool : public GraphicsUploadBufferPool {
private:
struct VulkanPage : public Page {
// Takes ownership of the buffer and its memory and mapping.
VulkanPage(const VulkanProvider& provider, VkBuffer buffer,
VulkanPage(const VulkanDevice* vulkan_device, VkBuffer buffer,
VkDeviceMemory memory, void* mapping)
: provider_(provider),
: vulkan_device_(vulkan_device),
buffer_(buffer),
memory_(memory),
mapping_(mapping) {}
~VulkanPage() override;
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
VkBuffer buffer_;
VkDeviceMemory memory_;
void* mapping_;
};
const VulkanProvider& provider_;
const VulkanDevice* vulkan_device_;
VkDeviceSize allocation_size_;
static constexpr uint32_t kMemoryTypeUnknown = UINT32_MAX;

View File

@@ -13,21 +13,23 @@
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
namespace vulkan {
namespace util {
void FlushMappedMemoryRange(const VulkanProvider& provider,
VkDeviceMemory memory, uint32_t memory_type,
VkDeviceSize offset, VkDeviceSize memory_size,
VkDeviceSize size) {
void FlushMappedMemoryRange(const VulkanDevice* const vulkan_device,
const VkDeviceMemory memory,
const uint32_t memory_type,
const VkDeviceSize offset,
const VkDeviceSize memory_size,
const VkDeviceSize size) {
assert_false(size != VK_WHOLE_SIZE && memory_size == VK_WHOLE_SIZE);
assert_true(memory_size == VK_WHOLE_SIZE || offset <= memory_size);
assert_true(memory_size == VK_WHOLE_SIZE || size <= memory_size - offset);
if (!size || (provider.device_info().memory_types_host_coherent &
if (!size || (vulkan_device->memory_types().host_coherent &
(uint32_t(1) << memory_type))) {
return;
}
@@ -37,27 +39,25 @@ void FlushMappedMemoryRange(const VulkanProvider& provider,
range.memory = memory;
range.offset = offset;
range.size = size;
VkDeviceSize non_coherent_atom_size =
provider.device_info().nonCoherentAtomSize;
// On some Android implementations, nonCoherentAtomSize is 0, not 1.
if (non_coherent_atom_size > 1) {
range.offset = offset / non_coherent_atom_size * non_coherent_atom_size;
if (size != VK_WHOLE_SIZE) {
range.size = std::min(xe::round_up(offset + size, non_coherent_atom_size),
memory_size) -
range.offset;
}
const VkDeviceSize non_coherent_atom_size =
vulkan_device->properties().nonCoherentAtomSize;
range.offset = offset / non_coherent_atom_size * non_coherent_atom_size;
if (size != VK_WHOLE_SIZE) {
range.size = std::min(xe::round_up(offset + size, non_coherent_atom_size),
memory_size) -
range.offset;
}
provider.dfn().vkFlushMappedMemoryRanges(provider.device(), 1, &range);
vulkan_device->functions().vkFlushMappedMemoryRanges(vulkan_device->device(),
1, &range);
}
bool CreateDedicatedAllocationBuffer(
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
VkDeviceMemory& memory_out, uint32_t* memory_type_out,
VkDeviceSize* memory_size_out) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VulkanDevice* const vulkan_device, const VkDeviceSize size,
const VkBufferUsageFlags usage, const MemoryPurpose memory_purpose,
VkBuffer& buffer_out, VkDeviceMemory& memory_out,
uint32_t* const memory_type_out, VkDeviceSize* const memory_size_out) {
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VkBufferCreateInfo buffer_create_info;
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -76,24 +76,23 @@ bool CreateDedicatedAllocationBuffer(
VkMemoryRequirements memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
uint32_t memory_type = ChooseMemoryType(
provider, memory_requirements.memoryTypeBits, memory_purpose);
uint32_t memory_type =
ChooseMemoryType(vulkan_device->memory_types(),
memory_requirements.memoryTypeBits, memory_purpose);
if (memory_type == UINT32_MAX) {
dfn.vkDestroyBuffer(device, buffer, nullptr);
return false;
}
VkMemoryAllocateInfo memory_allocate_info;
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = memory_type;
VkMemoryDedicatedAllocateInfo memory_dedicated_allocate_info;
if (provider.device_info().ext_1_1_VK_KHR_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
memory_dedicated_allocate_info.pNext = memory_allocate_info.pNext;
memory_allocate_info.pNext = &memory_dedicated_allocate_info;
memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
memory_dedicated_allocate_info.pNext = nullptr;
@@ -124,15 +123,15 @@ bool CreateDedicatedAllocationBuffer(
return true;
}
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
bool CreateDedicatedAllocationImage(const VulkanDevice* const vulkan_device,
const VkImageCreateInfo& create_info,
MemoryPurpose memory_purpose,
const MemoryPurpose memory_purpose,
VkImage& image_out,
VkDeviceMemory& memory_out,
uint32_t* memory_type_out,
VkDeviceSize* memory_size_out) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
uint32_t* const memory_type_out,
VkDeviceSize* const memory_size_out) {
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VkImage image;
if (dfn.vkCreateImage(device, &create_info, nullptr, &image) != VK_SUCCESS) {
@@ -141,24 +140,23 @@ bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
VkMemoryRequirements memory_requirements;
dfn.vkGetImageMemoryRequirements(device, image, &memory_requirements);
uint32_t memory_type = ChooseMemoryType(
provider, memory_requirements.memoryTypeBits, memory_purpose);
uint32_t memory_type =
ChooseMemoryType(vulkan_device->memory_types(),
memory_requirements.memoryTypeBits, memory_purpose);
if (memory_type == UINT32_MAX) {
dfn.vkDestroyImage(device, image, nullptr);
return false;
}
VkMemoryAllocateInfo memory_allocate_info;
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = memory_type;
VkMemoryDedicatedAllocateInfo memory_dedicated_allocate_info;
if (provider.device_info().ext_1_1_VK_KHR_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
memory_dedicated_allocate_info.pNext = memory_allocate_info.pNext;
memory_allocate_info.pNext = &memory_dedicated_allocate_info;
memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
memory_dedicated_allocate_info.pNext = nullptr;
@@ -190,11 +188,10 @@ bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
}
VkPipeline CreateComputePipeline(
const VulkanProvider& provider, VkPipelineLayout layout,
VkShaderModule shader, const VkSpecializationInfo* specialization_info,
const char* entry_point) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VulkanDevice* const vulkan_device, const VkPipelineLayout layout,
const VkShaderModule shader,
const VkSpecializationInfo* const specialization_info,
const char* const entry_point) {
VkComputePipelineCreateInfo pipeline_create_info;
pipeline_create_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
pipeline_create_info.pNext = nullptr;
@@ -211,28 +208,27 @@ VkPipeline CreateComputePipeline(
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
pipeline_create_info.basePipelineIndex = -1;
VkPipeline pipeline;
if (dfn.vkCreateComputePipelines(device, VK_NULL_HANDLE, 1,
&pipeline_create_info, nullptr,
&pipeline) != VK_SUCCESS) {
if (vulkan_device->functions().vkCreateComputePipelines(
vulkan_device->device(), VK_NULL_HANDLE, 1, &pipeline_create_info,
nullptr, &pipeline) != VK_SUCCESS) {
return VK_NULL_HANDLE;
}
return pipeline;
}
VkPipeline CreateComputePipeline(
const VulkanProvider& provider, VkPipelineLayout layout,
const VulkanDevice* const vulkan_device, VkPipelineLayout layout,
const uint32_t* shader_code, size_t shader_code_size_bytes,
const VkSpecializationInfo* specialization_info, const char* entry_point) {
VkShaderModule shader =
CreateShaderModule(provider, shader_code, shader_code_size_bytes);
const VkShaderModule shader =
CreateShaderModule(vulkan_device, shader_code, shader_code_size_bytes);
if (shader == VK_NULL_HANDLE) {
return VK_NULL_HANDLE;
}
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
VkPipeline pipeline = CreateComputePipeline(provider, layout, shader,
specialization_info, entry_point);
dfn.vkDestroyShaderModule(device, shader, nullptr);
const VkPipeline pipeline = CreateComputePipeline(
vulkan_device, layout, shader, specialization_info, entry_point);
vulkan_device->functions().vkDestroyShaderModule(vulkan_device->device(),
shader, nullptr);
return pipeline;
}

View File

@@ -14,15 +14,16 @@
#include <cstdint>
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_device.h"
namespace xe {
namespace ui {
namespace vulkan {
namespace util {
template <typename F, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, T& handle) {
template <typename DestroyFunction, typename Object>
inline bool DestroyAndNullHandle(DestroyFunction* const destroy_function,
Object& handle) {
if (handle != VK_NULL_HANDLE) {
destroy_function(handle, nullptr);
handle = VK_NULL_HANDLE;
@@ -31,8 +32,9 @@ inline bool DestroyAndNullHandle(F* destroy_function, T& handle) {
return false;
}
template <typename F, typename P, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, P parent, T& handle) {
template <typename DestroyFunction, typename Parent, typename Object>
inline bool DestroyAndNullHandle(DestroyFunction* const destroy_function,
const Parent parent, Object& handle) {
if (handle != VK_NULL_HANDLE) {
destroy_function(parent, handle, nullptr);
handle = VK_NULL_HANDLE;
@@ -47,28 +49,17 @@ enum class MemoryPurpose {
kReadback,
};
inline VkDeviceSize GetMappableMemorySize(const VulkanProvider& provider,
VkDeviceSize size) {
VkDeviceSize non_coherent_atom_size =
provider.device_info().nonCoherentAtomSize;
// On some Android implementations, nonCoherentAtomSize is 0, not 1.
if (non_coherent_atom_size > 1) {
size = xe::round_up(size, non_coherent_atom_size, false);
}
return size;
}
inline uint32_t ChooseHostMemoryType(const VulkanProvider& provider,
uint32_t supported_types,
bool is_readback) {
supported_types &= provider.device_info().memory_types_host_visible;
uint32_t host_cached = provider.device_info().memory_types_host_cached;
inline uint32_t ChooseHostMemoryType(
const VulkanDevice::MemoryTypes& memory_types, uint32_t supported_types,
const bool is_readback) {
supported_types &= memory_types.host_visible;
uint32_t memory_type;
// For upload, uncached is preferred so writes do not pollute the CPU cache.
// For readback, cached is preferred so multiple CPU reads are fast.
// If the preferred caching behavior is not available, pick any host-visible.
if (xe::bit_scan_forward(
supported_types & (is_readback ? host_cached : ~host_cached),
supported_types & (is_readback ? memory_types.host_cached
: ~memory_types.host_cached),
&memory_type) ||
xe::bit_scan_forward(supported_types, &memory_type)) {
return memory_type;
@@ -76,18 +67,24 @@ inline uint32_t ChooseHostMemoryType(const VulkanProvider& provider,
return UINT32_MAX;
}
inline uint32_t ChooseMemoryType(const VulkanProvider& provider,
uint32_t supported_types,
MemoryPurpose purpose) {
inline uint32_t ChooseMemoryType(const VulkanDevice::MemoryTypes& memory_types,
const uint32_t supported_types,
const MemoryPurpose purpose) {
switch (purpose) {
case MemoryPurpose::kDeviceLocal: {
uint32_t memory_type;
return xe::bit_scan_forward(supported_types, &memory_type) ? memory_type
: UINT32_MAX;
if (xe::bit_scan_forward(supported_types & memory_types.device_local,
&memory_type)) {
return memory_type;
}
if (xe::bit_scan_forward(supported_types, &memory_type)) {
return memory_type;
}
return UINT32_MAX;
} break;
case MemoryPurpose::kUpload:
case MemoryPurpose::kReadback:
return ChooseHostMemoryType(provider, supported_types,
return ChooseHostMemoryType(memory_types, supported_types,
purpose == MemoryPurpose::kReadback);
default:
assert_unhandled_case(purpose);
@@ -100,27 +97,28 @@ inline uint32_t ChooseMemoryType(const VulkanProvider& provider,
// size (offset + size passed to vkFlushMappedMemoryRanges inside this function
// must be either a multiple of nonCoherentAtomSize (but not exceeding the
// memory size) or equal to the memory size).
void FlushMappedMemoryRange(const VulkanProvider& provider,
void FlushMappedMemoryRange(const VulkanDevice* vulkan_device,
VkDeviceMemory memory, uint32_t memory_type,
VkDeviceSize offset = 0,
VkDeviceSize memory_size = VK_WHOLE_SIZE,
VkDeviceSize size = VK_WHOLE_SIZE);
inline VkExtent2D GetMax2DFramebufferExtent(const VulkanProvider& provider) {
const VulkanProvider::DeviceInfo& device_info = provider.device_info();
inline VkExtent2D GetMax2DFramebufferExtent(
const VulkanDevice::Properties& device_properties) {
VkExtent2D max_extent;
max_extent.width = std::min(device_info.maxFramebufferWidth,
device_info.maxImageDimension2D);
max_extent.height = std::min(device_info.maxFramebufferHeight,
device_info.maxImageDimension2D);
max_extent.width = std::min(device_properties.maxFramebufferWidth,
device_properties.maxImageDimension2D);
max_extent.height = std::min(device_properties.maxFramebufferHeight,
device_properties.maxImageDimension2D);
return max_extent;
}
inline VkImageSubresourceRange InitializeSubresourceRange(
VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT,
uint32_t base_mip_level = 0, uint32_t level_count = VK_REMAINING_MIP_LEVELS,
uint32_t base_array_layer = 0,
uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS) {
const VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT,
const uint32_t base_mip_level = 0,
const uint32_t level_count = VK_REMAINING_MIP_LEVELS,
const uint32_t base_array_layer = 0,
const uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS) {
VkImageSubresourceRange range;
range.aspectMask = aspect_mask;
range.baseMipLevel = base_mip_level;
@@ -133,13 +131,16 @@ inline VkImageSubresourceRange InitializeSubresourceRange(
// Creates a buffer backed by a dedicated allocation. The allocation size will
// NOT be aligned to nonCoherentAtomSize - if mapping or flushing not the whole
// size, memory_size_out must be used for clamping the range.
bool CreateDedicatedAllocationBuffer(
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
VkDeviceMemory& memory_out, uint32_t* memory_type_out = nullptr,
VkDeviceSize* memory_size_out = nullptr);
bool CreateDedicatedAllocationBuffer(const VulkanDevice* vulkan_device,
VkDeviceSize size,
VkBufferUsageFlags usage,
MemoryPurpose memory_purpose,
VkBuffer& buffer_out,
VkDeviceMemory& memory_out,
uint32_t* memory_type_out = nullptr,
VkDeviceSize* memory_size_out = nullptr);
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
bool CreateDedicatedAllocationImage(const VulkanDevice* vulkan_device,
const VkImageCreateInfo& create_info,
MemoryPurpose memory_purpose,
VkImage& image_out,
@@ -149,9 +150,9 @@ bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
// Explicitly accepting const uint32_t* to make sure attention is paid to the
// alignment where this is called for safety on different host architectures.
inline VkShaderModule CreateShaderModule(const VulkanProvider& provider,
const uint32_t* code,
size_t code_size_bytes) {
inline VkShaderModule CreateShaderModule(
const VulkanDevice* const vulkan_device, const uint32_t* const code,
const size_t code_size_bytes) {
VkShaderModuleCreateInfo shader_module_create_info;
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.pNext = nullptr;
@@ -159,20 +160,20 @@ inline VkShaderModule CreateShaderModule(const VulkanProvider& provider,
shader_module_create_info.codeSize = code_size_bytes;
shader_module_create_info.pCode = code;
VkShaderModule shader_module;
return provider.dfn().vkCreateShaderModule(
provider.device(), &shader_module_create_info, nullptr,
return vulkan_device->functions().vkCreateShaderModule(
vulkan_device->device(), &shader_module_create_info, nullptr,
&shader_module) == VK_SUCCESS
? shader_module
: VK_NULL_HANDLE;
}
VkPipeline CreateComputePipeline(
const VulkanProvider& provider, VkPipelineLayout layout,
const VulkanDevice* vulkan_device, VkPipelineLayout layout,
VkShaderModule shader,
const VkSpecializationInfo* specialization_info = nullptr,
const char* entry_point = "main");
VkPipeline CreateComputePipeline(
const VulkanProvider& provider, VkPipelineLayout layout,
const VulkanDevice* vulkan_device, VkPipelineLayout layout,
const uint32_t* shader_code, size_t shader_code_size_bytes,
const VkSpecializationInfo* specialization_info = nullptr,
const char* entry_point = "main");

View File

@@ -34,7 +34,7 @@ class VulkanWindowDemoApp final : public WindowDemoApp {
std::unique_ptr<GraphicsProvider> VulkanWindowDemoApp::CreateGraphicsProvider()
const {
return VulkanProvider::Create(true);
return VulkanProvider::Create(false, true);
}
} // namespace vulkan