Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental

This commit is contained in:
Gliniak
2025-08-15 15:37:50 +02:00
78 changed files with 3438 additions and 2888 deletions

View File

@@ -18,8 +18,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();
@@ -49,8 +49,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

@@ -27,6 +27,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);
@@ -42,8 +56,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_);
@@ -85,9 +99,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;
@@ -125,7 +147,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];
@@ -191,8 +213,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();) {
@@ -269,7 +291,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;
@@ -316,7 +338,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 =
@@ -407,7 +429,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());
@@ -525,8 +547,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_) {
@@ -563,8 +585,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.
@@ -578,8 +600,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;
@@ -587,8 +609,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");
@@ -750,8 +772,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;
@@ -861,8 +883,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.
@@ -887,7 +909,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(
@@ -907,7 +929,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;
@@ -937,15 +959,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;
@@ -971,7 +994,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(
@@ -1000,7 +1023,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);
}
@@ -1027,8 +1050,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

@@ -68,8 +68,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);
@@ -84,8 +84,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;
@@ -111,7 +111,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(
@@ -156,8 +156,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,
@@ -202,11 +202,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) {
@@ -234,14 +259,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;
@@ -249,7 +274,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) {
@@ -356,7 +381,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());
@@ -372,11 +397,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(
@@ -437,8 +463,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;
@@ -483,7 +509,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) {
@@ -524,11 +550,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;
@@ -545,7 +572,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);
@@ -636,7 +663,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);
@@ -832,7 +859,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
@@ -851,7 +878,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;
}
@@ -878,14 +905,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();
}
@@ -895,18 +923,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;
@@ -929,7 +957,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),
@@ -947,17 +976,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 &&
@@ -965,9 +994,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 &&
@@ -1223,7 +1252,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,
@@ -1245,8 +1274,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);
@@ -1264,22 +1293,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);
}
@@ -1313,14 +1341,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;
@@ -1363,8 +1391,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) {
@@ -1409,7 +1437,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;
@@ -1477,7 +1505,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,
@@ -1594,7 +1622,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
@@ -2024,13 +2052,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();
}
@@ -2067,10 +2096,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:
@@ -2087,7 +2116,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,
@@ -2103,8 +2132,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;
@@ -2114,8 +2143,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;
@@ -2366,7 +2395,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;
}
@@ -2531,8 +2561,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

@@ -22,12 +22,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,
@@ -70,8 +71,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;
@@ -92,8 +93,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 "
@@ -115,7 +117,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_);
@@ -137,7 +140,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);
@@ -174,19 +177,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