[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

@@ -51,15 +51,15 @@ VulkanPipelineCache::VulkanPipelineCache(
VulkanPipelineCache::~VulkanPipelineCache() { Shutdown(); }
bool VulkanPipelineCache::Initialize() {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
bool edram_fragment_shader_interlock =
render_target_cache_.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
shader_translator_ = std::make_unique<SpirvShaderTranslator>(
SpirvShaderTranslator::Features(provider.device_info()),
SpirvShaderTranslator::Features(vulkan_device),
render_target_cache_.msaa_2x_attachments_supported(),
render_target_cache_.msaa_2x_no_attachments_supported(),
edram_fragment_shader_interlock);
@@ -68,7 +68,7 @@ bool VulkanPipelineCache::Initialize() {
std::vector<uint8_t> depth_only_fragment_shader_code =
shader_translator_->CreateDepthOnlyFragmentShader();
depth_only_fragment_shader_ = ui::vulkan::util::CreateShaderModule(
provider,
vulkan_device,
reinterpret_cast<const uint32_t*>(
depth_only_fragment_shader_code.data()),
depth_only_fragment_shader_code.size());
@@ -85,10 +85,10 @@ bool VulkanPipelineCache::Initialize() {
}
void VulkanPipelineCache::Shutdown() {
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();
// Destroy all pipelines.
last_pipeline_ = nullptr;
@@ -136,7 +136,7 @@ VulkanShader* VulkanPipelineCache::LoadShader(xenos::ShaderType shader_type,
// We need to track it even if it fails translation so we know not to try
// again.
VulkanShader* shader =
new VulkanShader(command_processor_.GetVulkanProvider(), shader_type,
new VulkanShader(command_processor_.GetVulkanDevice(), shader_type,
data_hash, host_address, dword_count);
shaders_.emplace(data_hash, shader);
return shader;
@@ -268,9 +268,9 @@ bool VulkanPipelineCache::ConfigurePipeline(
VulkanRenderTargetCache::RenderPassKey render_pass_key,
VkPipeline& pipeline_out,
const PipelineLayoutProvider*& pipeline_layout_out) {
#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
// Ensure shaders are translated - needed now for GetCurrentStateDescription.
if (!EnsureShadersTranslated(vertex_shader, pixel_shader)) {
@@ -471,8 +471,8 @@ void VulkanPipelineCache::WritePipelineRenderTargetDescription(
render_target_out.dst_alpha_blend_factor =
kBlendFactorMap[uint32_t(blend_control.alpha_destblend)];
render_target_out.alpha_blend_op = blend_control.alpha_comb_fcn;
if (!command_processor_.GetVulkanProvider()
.device_info()
if (!command_processor_.GetVulkanDevice()
->properties()
.constantAlphaColorBlendFactors) {
if (blend_control.color_srcblend == xenos::BlendFactor::kConstantAlpha) {
render_target_out.src_color_blend_factor =
@@ -512,8 +512,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
PipelineDescription& description_out) const {
description_out.Reset();
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
command_processor_.GetVulkanProvider().device_info();
const ui::vulkan::VulkanDevice::Properties& device_properties =
command_processor_.GetVulkanDevice()->properties();
const RegisterFile& regs = register_file_;
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
@@ -548,7 +548,7 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
break;
case xenos::PrimitiveType::kTriangleFan:
// The check should be performed at primitive processing time.
assert_true(device_info.triangleFans);
assert_true(device_properties.triangleFans);
primitive_topology = PipelinePrimitiveTopology::kTriangleFan;
break;
case xenos::PrimitiveType::kTriangleStrip:
@@ -572,7 +572,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
primitive_processing_result.host_primitive_reset_enabled;
description_out.depth_clamp_enable =
device_info.depthClamp && regs.Get<reg::PA_CL_CLIP_CNTL>().clip_disable;
device_properties.depthClamp &&
regs.Get<reg::PA_CL_CLIP_CNTL>().clip_disable;
// TODO(Triang3l): Tessellation.
bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs);
@@ -587,7 +588,7 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
bool cull_back = pa_su_sc_mode_cntl.cull_back;
description_out.cull_front = cull_front;
description_out.cull_back = cull_back;
if (device_info.fillModeNonSolid) {
if (device_properties.fillModeNonSolid) {
xenos::PolygonType polygon_type = xenos::PolygonType::kTriangles;
if (!cull_front) {
polygon_type =
@@ -604,7 +605,7 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
case xenos::PolygonType::kPoints:
// When points are not supported, use lines instead, preserving
// debug-like purpose.
description_out.polygon_mode = device_info.pointPolygons
description_out.polygon_mode = device_properties.pointPolygons
? PipelinePolygonMode::kPoint
: PipelinePolygonMode::kLine;
break;
@@ -671,83 +672,17 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
// Color blending and write masks (filled only for the attachments present
// in the render pass object).
uint32_t render_pass_color_rts = render_pass_key.depth_and_color_used >> 1;
if (device_info.independentBlend) {
uint32_t render_pass_color_rts_remaining = render_pass_color_rts;
uint32_t color_rt_index;
while (xe::bit_scan_forward(render_pass_color_rts_remaining,
&color_rt_index)) {
render_pass_color_rts_remaining &= ~(uint32_t(1) << color_rt_index);
WritePipelineRenderTargetDescription(
regs.Get<reg::RB_BLENDCONTROL>(
reg::RB_BLENDCONTROL::rt_register_indices[color_rt_index]),
(normalized_color_mask >> (color_rt_index * 4)) & 0b1111,
description_out.render_targets[color_rt_index]);
}
} else {
// Take the blend control for the first render target that the guest wants
// to write to (consider it the most important) and use it for all render
// targets, if any.
// TODO(Triang3l): Implement an option for independent blending via
// replaying the render pass for each set of render targets with unique
// blending parameters, with depth / stencil saved before the first and
// restored before each of the rest maybe? Though independent blending
// support is pretty wide, with a quite prominent exception of Adreno 4xx
// apparently.
uint32_t render_pass_color_rts_remaining = render_pass_color_rts;
uint32_t render_pass_first_color_rt_index;
if (xe::bit_scan_forward(render_pass_color_rts_remaining,
&render_pass_first_color_rt_index)) {
render_pass_color_rts_remaining &=
~(uint32_t(1) << render_pass_first_color_rt_index);
PipelineRenderTarget& render_pass_first_color_rt =
description_out.render_targets[render_pass_first_color_rt_index];
uint32_t common_blend_rt_index;
if (xe::bit_scan_forward(normalized_color_mask,
&common_blend_rt_index)) {
common_blend_rt_index >>= 2;
// If a common write mask will be used for multiple render targets,
// use the original RB_COLOR_MASK instead of the normalized color mask
// as the normalized color mask has non-existent components forced to
// written (don't need reading to be preserved), while the number of
// components may vary between render targets. The attachments in the
// pass that must not be written to at all will be excluded via a
// shader modification.
WritePipelineRenderTargetDescription(
regs.Get<reg::RB_BLENDCONTROL>(
reg::RB_BLENDCONTROL::rt_register_indices
[common_blend_rt_index]),
(((normalized_color_mask &
~(uint32_t(0b1111) << (4 * common_blend_rt_index)))
? regs[XE_GPU_REG_RB_COLOR_MASK]
: normalized_color_mask) >>
(4 * common_blend_rt_index)) &
0b1111,
render_pass_first_color_rt);
} else {
// No render targets are written to, though the render pass still may
// contain color attachments - set them to not written and not
// blending.
render_pass_first_color_rt.src_color_blend_factor =
PipelineBlendFactor::kOne;
render_pass_first_color_rt.dst_color_blend_factor =
PipelineBlendFactor::kZero;
render_pass_first_color_rt.color_blend_op = xenos::BlendOp::kAdd;
render_pass_first_color_rt.src_alpha_blend_factor =
PipelineBlendFactor::kOne;
render_pass_first_color_rt.dst_alpha_blend_factor =
PipelineBlendFactor::kZero;
render_pass_first_color_rt.alpha_blend_op = xenos::BlendOp::kAdd;
}
// Reuse the same blending settings for all render targets in the pass,
// for description consistency.
uint32_t color_rt_index;
while (xe::bit_scan_forward(render_pass_color_rts_remaining,
&color_rt_index)) {
render_pass_color_rts_remaining &= ~(uint32_t(1) << color_rt_index);
description_out.render_targets[color_rt_index] =
render_pass_first_color_rt;
}
}
assert_true(device_properties.independentBlend);
uint32_t render_pass_color_rts_remaining = render_pass_color_rts;
uint32_t color_rt_index;
while (xe::bit_scan_forward(render_pass_color_rts_remaining,
&color_rt_index)) {
render_pass_color_rts_remaining &= ~(uint32_t(1) << color_rt_index);
WritePipelineRenderTargetDescription(
regs.Get<reg::RB_BLENDCONTROL>(
reg::RB_BLENDCONTROL::rt_register_indices[color_rt_index]),
(normalized_color_mask >> (color_rt_index * 4)) & 0b1111,
description_out.render_targets[color_rt_index]);
}
}
@@ -767,65 +702,37 @@ bool VulkanPipelineCache::ArePipelineRequirementsMet(
return false;
}
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
command_processor_.GetVulkanProvider().device_info();
const ui::vulkan::VulkanDevice::Properties& device_properties =
command_processor_.GetVulkanDevice()->properties();
if (!device_info.geometryShader &&
if (!device_properties.geometryShader &&
description.geometry_shader != PipelineGeometryShader::kNone) {
return false;
}
if (!device_info.triangleFans &&
if (!device_properties.triangleFans &&
description.primitive_topology ==
PipelinePrimitiveTopology::kTriangleFan) {
return false;
}
if (!device_info.depthClamp && description.depth_clamp_enable) {
if (!device_properties.depthClamp && description.depth_clamp_enable) {
return false;
}
if (!device_info.pointPolygons &&
if (!device_properties.pointPolygons &&
description.polygon_mode == PipelinePolygonMode::kPoint) {
return false;
}
if (!device_info.fillModeNonSolid &&
if (!device_properties.fillModeNonSolid &&
description.polygon_mode != PipelinePolygonMode::kFill) {
return false;
}
if (!device_info.independentBlend) {
uint32_t color_rts_remaining =
description.render_pass_key.depth_and_color_used >> 1;
uint32_t first_color_rt_index;
if (xe::bit_scan_forward(color_rts_remaining, &first_color_rt_index)) {
color_rts_remaining &= ~(uint32_t(1) << first_color_rt_index);
const PipelineRenderTarget& first_color_rt =
description.render_targets[first_color_rt_index];
uint32_t color_rt_index;
while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) {
color_rts_remaining &= ~(uint32_t(1) << color_rt_index);
const PipelineRenderTarget& color_rt =
description.render_targets[color_rt_index];
if (color_rt.src_color_blend_factor !=
first_color_rt.src_color_blend_factor ||
color_rt.dst_color_blend_factor !=
first_color_rt.dst_color_blend_factor ||
color_rt.color_blend_op != first_color_rt.color_blend_op ||
color_rt.src_alpha_blend_factor !=
first_color_rt.src_alpha_blend_factor ||
color_rt.dst_alpha_blend_factor !=
first_color_rt.dst_alpha_blend_factor ||
color_rt.alpha_blend_op != first_color_rt.alpha_blend_op ||
color_rt.color_write_mask != first_color_rt.color_write_mask) {
return false;
}
}
}
}
assert_true(device_properties.independentBlend);
if (!device_info.constantAlphaColorBlendFactors) {
if (!device_properties.constantAlphaColorBlendFactors) {
uint32_t color_rts_remaining =
description.render_pass_key.depth_and_color_used >> 1;
uint32_t color_rt_index;
@@ -1849,10 +1756,9 @@ VkShaderModule VulkanPipelineCache::GetGeometryShader(GeometryShaderKey key) {
// Create the shader module, and store the handle even if creation fails not
// to try to create it again later.
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
VkShaderModule shader_module = ui::vulkan::util::CreateShaderModule(
provider, reinterpret_cast<const uint32_t*>(shader_code.data()),
command_processor_.GetVulkanDevice(),
reinterpret_cast<const uint32_t*>(shader_code.data()),
sizeof(uint32_t) * shader_code.size());
if (shader_module == VK_NULL_HANDLE) {
XELOGE(
@@ -1892,10 +1798,8 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
return false;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceInfo& device_info =
provider.device_info();
const ui::vulkan::VulkanDevice* const vulkan_device =
command_processor_.GetVulkanDevice();
bool edram_fragment_shader_interlock =
render_target_cache_.GetPath() ==
@@ -2174,6 +2078,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
VK_BLEND_OP_ADD,
VK_BLEND_OP_ADD,
VK_BLEND_OP_ADD};
assert_true(vulkan_device->properties().independentBlend);
uint32_t color_rts_remaining = color_rts_used;
uint32_t color_rt_index;
while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) {
@@ -2204,28 +2109,10 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
}
color_blend_attachment.colorWriteMask =
VkColorComponentFlags(color_rt.color_write_mask);
if (!device_info.independentBlend) {
// For non-independent blend, the pAttachments element for the first
// actually used color will be replicated into all.
break;
}
}
}
color_blend_state.attachmentCount = 32 - xe::lzcnt(color_rts_used);
color_blend_state.pAttachments = color_blend_attachments;
if (color_rts_used && !device_info.independentBlend) {
// "If the independent blending feature is not enabled, all elements of
// pAttachments must be identical."
uint32_t first_color_rt_index;
xe::bit_scan_forward(color_rts_used, &first_color_rt_index);
for (uint32_t i = 0; i < color_blend_state.attachmentCount; ++i) {
if (i == first_color_rt_index) {
continue;
}
color_blend_attachments[i] =
color_blend_attachments[first_color_rt_index];
}
}
}
std::array<VkDynamicState, 7> dynamic_states;
@@ -2276,8 +2163,8 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
pipeline_create_info.basePipelineIndex = -1;
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();
VkPipeline pipeline;
if (dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1,
&pipeline_create_info, nullptr,