[Vulkan] Refactoring and fixes for VulkanProvider and related areas

Enable portability subset physical device enumeration.

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

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

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

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

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

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

Log Vulkan driver info.

Improve Vulkan debug message logging, enabled by default.

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

View File

@@ -22,6 +22,7 @@
#include "xenia/gpu/texture_util.h"
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_util.h"
@@ -423,10 +424,10 @@ const VulkanTextureCache::HostFormatPair
true};
VulkanTextureCache::~VulkanTextureCache() {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
for (const std::pair<const SamplerParameters, Sampler>& sampler_pair :
samplers_) {
@@ -523,9 +524,9 @@ void VulkanTextureCache::BeginSubmission(uint64_t new_submission_index) {
}
void VulkanTextureCache::RequestTextures(uint32_t used_texture_mask) {
#if XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
TextureCache::RequestTextures(used_texture_mask);
@@ -718,10 +719,10 @@ VkSampler VulkanTextureCache::UseSampler(SamplerParameters parameters,
return sampler.second.sampler;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
// See if an existing sampler can be destroyed to create space for the new
// one.
@@ -761,7 +762,7 @@ VkSampler VulkanTextureCache::UseSampler(SamplerParameters parameters,
// GetSamplerParameters.
VkSamplerCreateInfo sampler_create_info = {};
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
if (provider.device_info().nonSeamlessCubeMap &&
if (vulkan_device->properties().nonSeamlessCubeMap &&
cvars::non_seamless_cube_map) {
sampler_create_info.flags |=
VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT;
@@ -940,17 +941,17 @@ uint32_t VulkanTextureCache::GetHostFormatSwizzle(TextureKey key) const {
uint32_t VulkanTextureCache::GetMaxHostTextureWidthHeight(
xenos::DataDimension dimension) const {
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
command_processor_.GetVulkanProvider().device_info();
const ui::vulkan::VulkanDevice::Properties& device_properties =
command_processor_.GetVulkanDevice()->properties();
switch (dimension) {
case xenos::DataDimension::k1D:
case xenos::DataDimension::k2DOrStacked:
// 1D and 2D are emulated as 2D arrays.
return device_info.maxImageDimension2D;
return device_properties.maxImageDimension2D;
case xenos::DataDimension::k3D:
return device_info.maxImageDimension3D;
return device_properties.maxImageDimension3D;
case xenos::DataDimension::kCube:
return device_info.maxImageDimensionCube;
return device_properties.maxImageDimensionCube;
default:
assert_unhandled_case(dimension);
return 0;
@@ -959,15 +960,15 @@ uint32_t VulkanTextureCache::GetMaxHostTextureWidthHeight(
uint32_t VulkanTextureCache::GetMaxHostTextureDepthOrArraySize(
xenos::DataDimension dimension) const {
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
command_processor_.GetVulkanProvider().device_info();
const ui::vulkan::VulkanDevice::Properties& device_properties =
command_processor_.GetVulkanDevice()->properties();
switch (dimension) {
case xenos::DataDimension::k1D:
case xenos::DataDimension::k2DOrStacked:
// 1D and 2D are emulated as 2D arrays.
return device_info.maxImageArrayLayers;
return device_properties.maxImageArrayLayers;
case xenos::DataDimension::k3D:
return device_info.maxImageDimension3D;
return device_properties.maxImageDimension3D;
case xenos::DataDimension::kCube:
// Not requesting the imageCubeArray feature, and the Xenos doesn't
// support cube map arrays.
@@ -1009,10 +1010,10 @@ std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
return nullptr;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
bool is_3d = key.dimension == xenos::DataDimension::k3D;
uint32_t depth_or_array_size = key.GetDepthOrArraySize();
@@ -1049,7 +1050,7 @@ std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageFormatListCreateInfo image_format_list_create_info;
if (formats[1] != VK_FORMAT_UNDEFINED &&
provider.device_info().ext_1_2_VK_KHR_image_format_list) {
vulkan_device->extensions().ext_1_2_KHR_image_format_list) {
image_create_info_last->pNext = &image_format_list_create_info;
image_create_info_last =
reinterpret_cast<VkImageCreateInfo*>(&image_format_list_create_info);
@@ -1224,10 +1225,10 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
// Begin loading.
// TODO(Triang3l): Going from one descriptor to another on per-array-layer
// or even per-8-depth-slices level to stay within maxStorageBufferRange.
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VulkanSharedMemory& vulkan_shared_memory =
static_cast<VulkanSharedMemory&>(shared_memory());
std::array<VkWriteDescriptorSet, 3> write_descriptor_sets;
@@ -1590,10 +1591,10 @@ VulkanTextureCache::VulkanTexture::VulkanTexture(
VulkanTextureCache::VulkanTexture::~VulkanTexture() {
const VulkanTextureCache& vulkan_texture_cache =
static_cast<const VulkanTextureCache&>(texture_cache());
const ui::vulkan::VulkanProvider& provider =
vulkan_texture_cache.command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice* const vulkan_device =
vulkan_texture_cache.command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
for (const auto& view_pair : views_) {
dfn.vkDestroyImageView(device, view_pair.second, nullptr);
}
@@ -1631,9 +1632,10 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
is_signed && (host_format_pair.format_signed.format !=
host_format_pair.format_unsigned.format);
const ui::vulkan::VulkanProvider& provider =
vulkan_texture_cache.command_processor_.GetVulkanProvider();
if (!provider.device_info().imageViewFormatSwizzle) {
const ui::vulkan::VulkanDevice* const vulkan_device =
vulkan_texture_cache.command_processor_.GetVulkanDevice();
if (!vulkan_device->properties().imageViewFormatSwizzle) {
host_swizzle = xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA;
}
view_key.host_swizzle = host_swizzle;
@@ -1647,8 +1649,8 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
}
// Create a new view.
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VkImageViewCreateInfo view_create_info;
view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_create_info.pNext = nullptr;
@@ -1704,18 +1706,19 @@ VulkanTextureCache::VulkanTextureCache(
}
bool VulkanTextureCache::Initialize() {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::InstanceFunctions& ifn = provider.ifn();
VkPhysicalDevice physical_device = provider.physical_device();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
provider.device_info();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
const ui::vulkan::VulkanInstance::Functions& ifn =
vulkan_device->vulkan_instance()->functions();
const VkPhysicalDevice physical_device = vulkan_device->physical_device();
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
const ui::vulkan::VulkanDevice::Properties& device_properties =
vulkan_device->properties();
// Vulkan Memory Allocator.
vma_allocator_ = ui::vulkan::CreateVmaAllocator(provider, true);
vma_allocator_ = ui::vulkan::CreateVmaAllocator(vulkan_device, true);
if (vma_allocator_ == VK_NULL_HANDLE) {
return false;
}
@@ -2323,7 +2326,7 @@ bool VulkanTextureCache::Initialize() {
load_shader_code[i];
assert_not_null(current_load_shader_code.first);
load_pipelines_[i] = ui::vulkan::util::CreateComputePipeline(
provider, load_pipeline_layout_, current_load_shader_code.first,
vulkan_device, load_pipeline_layout_, current_load_shader_code.first,
current_load_shader_code.second);
if (load_pipelines_[i] == VK_NULL_HANDLE) {
XELOGE(
@@ -2337,7 +2340,7 @@ bool VulkanTextureCache::Initialize() {
current_load_shader_code_scaled = load_shader_code_scaled[i];
if (current_load_shader_code_scaled.first) {
load_pipelines_scaled_[i] = ui::vulkan::util::CreateComputePipeline(
provider, load_pipeline_layout_,
vulkan_device, load_pipeline_layout_,
current_load_shader_code_scaled.first,
current_load_shader_code_scaled.second);
if (load_pipelines_scaled_[i] == VK_NULL_HANDLE) {
@@ -2402,7 +2405,7 @@ bool VulkanTextureCache::Initialize() {
dfn.vkGetImageMemoryRequirements(device, null_image_3d_,
&null_image_memory_requirements_3d_);
uint32_t null_image_memory_type_common = ui::vulkan::util::ChooseMemoryType(
provider,
vulkan_device->memory_types(),
null_image_memory_requirements_2d_array_cube_.memoryTypeBits &
null_image_memory_requirements_3d_.memoryTypeBits,
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
@@ -2443,16 +2446,18 @@ bool VulkanTextureCache::Initialize() {
}
} else {
// Place each null image in separate allocations.
uint32_t null_image_memory_type_2d_array_cube_ =
const uint32_t null_image_memory_type_2d_array_cube =
ui::vulkan::util::ChooseMemoryType(
provider,
vulkan_device->memory_types(),
null_image_memory_requirements_2d_array_cube_.memoryTypeBits,
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
uint32_t null_image_memory_type_3d_ = ui::vulkan::util::ChooseMemoryType(
provider, null_image_memory_requirements_3d_.memoryTypeBits,
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
if (null_image_memory_type_2d_array_cube_ == UINT32_MAX ||
null_image_memory_type_3d_ == UINT32_MAX) {
const uint32_t null_image_memory_type_3d =
ui::vulkan::util::ChooseMemoryType(
vulkan_device->memory_types(),
null_image_memory_requirements_3d_.memoryTypeBits,
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
if (null_image_memory_type_2d_array_cube == UINT32_MAX ||
null_image_memory_type_3d == UINT32_MAX) {
XELOGE(
"VulkanTextureCache: Failed to get the memory types for the null "
"images");
@@ -2468,9 +2473,9 @@ bool VulkanTextureCache::Initialize() {
null_image_memory_allocate_info.allocationSize =
null_image_memory_requirements_2d_array_cube_.size;
null_image_memory_allocate_info.memoryTypeIndex =
null_image_memory_type_2d_array_cube_;
null_image_memory_type_2d_array_cube;
VkMemoryDedicatedAllocateInfo null_image_memory_dedicated_allocate_info;
if (device_info.ext_1_1_VK_KHR_dedicated_allocation) {
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
null_image_memory_allocate_info_last->pNext =
&null_image_memory_dedicated_allocate_info;
null_image_memory_allocate_info_last =
@@ -2500,8 +2505,7 @@ bool VulkanTextureCache::Initialize() {
null_image_memory_allocate_info.allocationSize =
null_image_memory_requirements_3d_.size;
null_image_memory_allocate_info.memoryTypeIndex =
null_image_memory_type_3d_;
null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_3d;
null_image_memory_dedicated_allocate_info.image = null_image_3d_;
if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
&null_images_memory_[1]) != VK_SUCCESS) {
@@ -2531,8 +2535,8 @@ bool VulkanTextureCache::Initialize() {
// constant components instead of the real texels. The image will be cleared
// to (0, 0, 0, 0) anyway.
VkComponentSwizzle null_image_view_swizzle =
device_info.imageViewFormatSwizzle ? VK_COMPONENT_SWIZZLE_ZERO
: VK_COMPONENT_SWIZZLE_IDENTITY;
device_properties.imageViewFormatSwizzle ? VK_COMPONENT_SWIZZLE_ZERO
: VK_COMPONENT_SWIZZLE_IDENTITY;
null_image_view_create_info.components.r = null_image_view_swizzle;
null_image_view_create_info.components.g = null_image_view_swizzle;
null_image_view_create_info.components.b = null_image_view_swizzle;
@@ -2571,15 +2575,15 @@ bool VulkanTextureCache::Initialize() {
// VkDevice (true in a regular emulation scenario), so taking over all the
// allocation slots exclusively.
// Also leaving a few slots for use by things like overlay applications.
sampler_max_count_ =
device_info.maxSamplerAllocationCount -
uint32_t(ui::vulkan::VulkanProvider::HostSampler::kCount) - 16;
sampler_max_count_ = device_properties.maxSamplerAllocationCount -
ui::vulkan::UISamplers::kSamplerCount - 16;
if (device_info.samplerAnisotropy) {
if (device_properties.samplerAnisotropy) {
max_anisotropy_ = xenos::AnisoFilter(
uint32_t(xenos::AnisoFilter::kMax_1_1) +
(31 - xe::lzcnt(uint32_t(std::min(
16.0f, std::max(1.0f, device_info.maxSamplerAnisotropy))))));
(31 -
xe::lzcnt(uint32_t(std::min(
16.0f, std::max(1.0f, device_properties.maxSamplerAnisotropy))))));
} else {
max_anisotropy_ = xenos::AnisoFilter::kDisabled;
}
@@ -2643,8 +2647,8 @@ xenos::ClampMode VulkanTextureCache::NormalizeClampMode(
clamp_mode == xenos::ClampMode::kMirrorClampToHalfway ||
clamp_mode == xenos::ClampMode::kMirrorClampToBorder) {
// No equivalents for anything other than kMirrorClampToEdge in Vulkan.
return command_processor_.GetVulkanProvider()
.device_info()
return command_processor_.GetVulkanDevice()
->properties()
.samplerMirrorClampToEdge
? xenos::ClampMode::kMirrorClampToEdge
: xenos::ClampMode::kMirroredRepeat;