From c717ad0f59408b7d7ec4d4cc14ca32ee9ce131ad Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 05:32:16 -0400 Subject: [PATCH 1/8] [vulkan, qcom] Extending handling of shader stages + handling of float controls --- .../backend/spirv/emit_spirv.cpp | 2 +- .../backend/spirv/emit_spirv_warp.cpp | 42 ++++++++++++++++++- .../backend/spirv/spirv_emit_context.cpp | 9 ++-- src/shader_recompiler/profile.h | 8 ++++ .../renderer_vulkan/vk_pipeline_cache.cpp | 18 +++++++- .../vulkan_common/vulkan_device.cpp | 3 +- src/video_core/vulkan_common/vulkan_device.h | 5 +++ 7 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 0447dfe90d..38bce7f82e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -432,7 +432,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles) && - profile.support_vote) { + profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); if (!profile.warp_size_potentially_larger_than_guest) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 77ff8c5731..242426ec08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } +bool StageSupportsSubgroups(EmitContext& ctx) { + return ctx.profile.SupportsSubgroupStage(ctx.stage); +} + Id GetThreadId(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -68,6 +78,9 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask } Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { + if (!StageSupportsSubgroups(ctx)) { + return value; + } return ctx.OpSelect( ctx.U32[1], in_range, ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); @@ -89,6 +102,9 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -102,6 +118,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -115,6 +134,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.true_value; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -129,6 +151,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); + } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -137,22 +162,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -222,7 +262,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + Id mask{GetThreadId(ctx)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 169e83d9fd..9078cc22e5 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1438,7 +1438,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask) { + if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1452,9 +1452,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) { + if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) && + profile.SupportsSubgroupStage(stage)) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index bd7bc6ac7b..8b6f7453d0 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,6 +10,8 @@ namespace Shader { +enum class Stage : u32; + struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; @@ -30,6 +32,8 @@ struct Profile { bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool support_vote{}; + /// Bitmask over Shader::Stage of stages where the host supports subgroup operations + u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; @@ -93,6 +97,10 @@ struct Profile { u64 min_ssbo_alignment{}; u32 max_user_clip_distances{}; + + bool SupportsSubgroupStage(Stage stage) const { + return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; + } }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 15f7e9bf43..61ca64bf7e 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -378,6 +378,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; + const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { + return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; + }}; + const u32 supported_subgroup_stages{ + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + Shader::Stage::TessellationControl) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + Shader::Stage::TessellationEval) | + subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | + subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, @@ -403,6 +417,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), + .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), @@ -433,7 +448,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .has_broken_spirv_position_input = driver_id == false, .has_broken_unsigned_image_offsets = false, .has_broken_signed_operations = false, - .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY, + .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY || + driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, .ignore_nan_fp_comparisons = false, .has_broken_spirv_subgroup_mask_vector_extract_dynamic = false, .has_broken_robust = diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7bf73dcead..5c9bcf175a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -502,8 +502,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR "Qualcomm drivers have slow push descriptor implementation"); RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, - "Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers"); - RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + "Disabling 64-bit integer features on Qualcomm proprietary drivers"); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 110d0c1199..e0e6dde015 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -403,6 +403,11 @@ FN_MAX_LIMIT_LIST return properties.subgroup_properties.supportedOperations & feature; } + /// Returns the shader stages that support subgroup operations. + VkShaderStageFlags GetSubgroupSupportedStages() const { + return properties.subgroup_properties.supportedStages; + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; From 84b78abe1cfa2ae0c5290a119d7c423300ccd693 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 12:55:04 -0400 Subject: [PATCH 2/8] [qcom] Removal of SignedZeroInfNan feature bit from stock drivers --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 61ca64bf7e..9a29599531 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -378,6 +378,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const bool has_broken_sz_inf_nan{driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY}; const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; @@ -410,11 +411,14 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_fp16_denorm_flush = float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE, .support_fp32_denorm_flush = float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE, .support_fp16_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE, + float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE && + !has_broken_sz_inf_nan, .support_fp32_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE, + float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE && + !has_broken_sz_inf_nan, .support_fp64_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, + float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE && + !has_broken_sz_inf_nan, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), .supported_subgroup_stages = supported_subgroup_stages, From 1ba77052798d23810039a41322215a8679a9563f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 13:37:17 -0400 Subject: [PATCH 3/8] [vulkan] Relaxing Workgroup Memory Explicit Layout flags + INT64 Atomics --- .../backend/spirv/emit_spirv_atomic.cpp | 7 ++-- .../spirv/emit_spirv_shared_memory.cpp | 27 +++++++++------- .../backend/spirv/spirv_emit_context.cpp | 8 +++-- .../backend/spirv/spirv_emit_context.h | 4 +++ src/shader_recompiler/profile.h | 4 ++- .../renderer_vulkan/vk_pipeline_cache.cpp | 5 +++ .../vulkan_common/vulkan_device.cpp | 32 ++----------------- src/video_core/vulkan_common/vulkan_device.h | 23 +++++++++++-- 8 files changed, 62 insertions(+), 48 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 0ce73f289b..d1c08c1131 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) { if (index_offset > 0) { index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset)); } - return ctx.profile.support_explicit_workgroup_layout + return ctx.uses_explicit_workgroup_layout ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index) : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); } @@ -155,7 +158,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) { } Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) { + if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) { const Id shift_id{ctx.Const(3U)}; const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id pointer{ diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index df05dad74a..dc398aa296 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -28,7 +31,7 @@ std::pair ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count) } // Anonymous namespace Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -39,7 +42,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -50,7 +53,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -60,7 +63,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -70,7 +73,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; return ctx.OpLoad(ctx.U32[1], pointer); } else { @@ -79,7 +82,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; return ctx.OpLoad(ctx.U32[2], pointer); } else { @@ -94,7 +97,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; return ctx.OpLoad(ctx.U32[4], pointer); } @@ -110,7 +113,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { } void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value)); @@ -120,7 +123,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); } else { @@ -130,7 +133,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { Id pointer{}; - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); } else { const Id shift{ctx.Const(2U)}; @@ -141,7 +144,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; ctx.OpStore(pointer, value); return; @@ -156,7 +159,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; ctx.OpStore(pointer, value); return; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 9078cc22e5..5b92744806 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) { Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer, Id value_type, Id memory_type, spv::Scope scope) { const bool is_shared{scope == spv::Scope::Workgroup}; - const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout}; + const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout}; const Id cas_func{CasFunction(ctx, operation, value_type)}; const Id zero{ctx.u32_zero_value}; const Id scope_id{ctx.Const(static_cast(scope))}; @@ -620,7 +620,11 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) { return std::make_tuple(variable, element_pointer, pointer); }}; - if (profile.support_explicit_workgroup_layout) { + uses_explicit_workgroup_layout = + profile.support_explicit_workgroup_layout && + (!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) && + (!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access); + if (uses_explicit_workgroup_layout) { AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); if (program.info.uses_int8) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 55385de0b0..e2d37a34c8 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -310,6 +310,10 @@ public: Id local_memory{}; + /// True when this shader's shared memory uses SPV_KHR_workgroup_memory_explicit_layout. + /// False when the host lacks the extension or a width this shader accesses natively. + bool uses_explicit_workgroup_layout{}; + Id shared_memory_u8{}; Id shared_memory_u16{}; Id shared_memory_u32{}; diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 8b6f7453d0..2699d5da8c 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -31,14 +31,16 @@ struct Profile { bool support_fp32_signed_zero_nan_preserve{}; bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; + bool support_workgroup_layout_8bit_access{}; + bool support_workgroup_layout_16bit_access{}; bool support_vote{}; - /// Bitmask over Shader::Stage of stages where the host supports subgroup operations u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; bool support_demote_to_helper_invocation{}; bool support_int64_atomics{}; + bool support_shared_int64_atomics{}; bool support_derivative_control{}; bool support_geometry_shader_passthrough{}; bool support_native_ndc{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 9a29599531..c8aa2f7b40 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -420,6 +420,10 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE && !has_broken_sz_inf_nan, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), + .support_workgroup_layout_8bit_access = + device.IsWorkgroupMemoryExplicitLayout8BitSupported(), + .support_workgroup_layout_16bit_access = + device.IsWorkgroupMemoryExplicitLayout16BitSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = @@ -429,6 +433,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_demote_to_helper_invocation = device.IsExtShaderDemoteToHelperInvocationSupported(), .support_int64_atomics = device.IsExtShaderAtomicInt64Supported(), + .support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(), .support_derivative_control = true, .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_native_ndc = device.IsExtDepthClipControlSupported(), diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 5c9bcf175a..cce4c7b6c7 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -501,13 +501,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation"); RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, - "Disabling 64-bit integer features on Qualcomm proprietary drivers"); - RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, - VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); - features.shader_atomic_int64.shaderBufferInt64Atomics = false; - features.shader_atomic_int64.shaderSharedInt64Atomics = false; - features.features.shaderInt64 = false; #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. @@ -560,7 +553,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_float16_int8.shaderFloat16 = false; } - // Mali/ NVIDIA proprietary drivers: Shader stencil export not supported // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { LOG_INFO(Render_Vulkan, @@ -573,8 +565,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (!is_blit_depth24_stencil8_supported && !is_blit_depth32_stencil8_supported) { LOG_WARNING(Render_Vulkan, - "NVIDIA: Neither shader export nor hardware blits available for " - "depth/stencil. Performance may be degraded."); + "Neither shader export nor hardware blits available for " + "depth/stencil."); } } } @@ -656,17 +648,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } const auto dyna_state = Settings::values.dyna_state.GetValue(); - - // Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp - // This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs: - // Level 0 = Core Dynamic States only (Vulkan 1.0) - // Level 1 = Core + VK_EXT_extended_dynamic_state - // Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 - // Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3 - switch (dyna_state) { case Settings::ExtendedDynamicState::Disabled: - // Level 0: Disable all extended dynamic state extensions RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, @@ -677,7 +660,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR dynamic_state3_enables = false; break; case Settings::ExtendedDynamicState::EDS1: - // Level 1: Enable EDS1, disable EDS2 and EDS3 RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, @@ -686,7 +668,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR dynamic_state3_enables = false; break; case Settings::ExtendedDynamicState::EDS2: - // Level 2: Enable EDS1 + EDS2, disable EDS3 RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); dynamic_state3_blending = false; @@ -694,12 +675,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR break; case Settings::ExtendedDynamicState::EDS3: default: - // Level 3: Enable all (EDS1 + EDS2 + EDS3) break; } - // VK_EXT_vertex_input_dynamic_state is independent from EDS - // It can be enabled even without extended_dynamic_state if (!Settings::values.vertex_input_dynamic_state.GetValue()) { RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } @@ -1281,8 +1259,7 @@ void Device::RemoveUnsuitableExtensions() { VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME); // VK_KHR_shader_atomic_int64 - extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && - features.shader_atomic_int64.shaderSharedInt64Atomics; + extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics; RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); @@ -1331,10 +1308,7 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_workgroup_memory_explicit_layout extensions.workgroup_memory_explicit_layout = - features.features.shaderInt16 && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index e0e6dde015..d941ca5b30 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -488,6 +488,18 @@ FN_MAX_LIMIT_LIST return extensions.workgroup_memory_explicit_layout; } + /// Returns true if the device supports 8-bit accesses to workgroup explicit layout memory. + bool IsWorkgroupMemoryExplicitLayout8BitSupported() const { + return extensions.workgroup_memory_explicit_layout && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess; + } + + /// Returns true if the device supports 16-bit accesses to workgroup explicit layout memory. + bool IsWorkgroupMemoryExplicitLayout16BitSupported() const { + return extensions.workgroup_memory_explicit_layout && features.features.shaderInt16 && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess; + } + /// Returns true if the device supports VK_KHR_image_format_list. bool IsKhrImageFormatListSupported() const { return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2; @@ -716,9 +728,16 @@ FN_MAX_LIMIT_LIST features.provoking_vertex.transformFeedbackPreservesProvokingVertex; } - /// Returns true if the device supports VK_KHR_shader_atomic_int64. + /// Returns true if the device supports int64 atomics on storage buffers. bool IsExtShaderAtomicInt64Supported() const { - return extensions.shader_atomic_int64; + return extensions.shader_atomic_int64 && + features.shader_atomic_int64.shaderBufferInt64Atomics; + } + + /// Returns true if the device supports int64 atomics on workgroup (shared) memory. + bool IsSharedInt64AtomicsSupported() const { + return extensions.shader_atomic_int64 && + features.shader_atomic_int64.shaderSharedInt64Atomics; } bool IsExtConditionalRendering() const { From 148586d2f6542fd7892d9f1a0b49b2065e4040ed Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 14:09:58 -0400 Subject: [PATCH 4/8] [texture_cache, pipeline_cache] Maintenance for some clamps + promoting to general purpose --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 6 +----- src/video_core/texture_cache/texture_cache.h | 5 ++++- src/video_core/vulkan_common/vulkan_device.cpp | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index c8aa2f7b40..04d34ac92b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -929,12 +929,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline( } auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; - const VkDriverIdKHR driver_id = device.GetDriverID(); - const bool needs_shared_mem_clamp = - driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || - driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize(); - if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) { + if (program.shared_memory_size > max_shared_memory) { LOG_WARNING(Render_Vulkan, "Compute shader 0x{:016x} requests {}KB shared memory but device max is {}KB - clamping", key.unique_hash, diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2cfdb94359..060e75ff47 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1915,13 +1915,16 @@ void TextureCache

::TrimInactiveSamplers(size_t budget) { ankerl::unordered_dense::set active_sampler_ids; for (auto const& e : channel_state->sampler_ids) active_sampler_ids.insert(e.second); + if constexpr (requires { runtime.Finish(); }) { + runtime.Finish(); + } // Elements in the map must be necesarily valid size_t removed = 0; for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) { const SamplerId sampler_id = it->second; if (!sampler_id || sampler_id == CORRUPT_ID) { it = channel_state->samplers.erase(it); - } else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) { + } else if (active_sampler_ids.contains(sampler_id)) { ++it; } else { slot_samplers.erase(sampler_id); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index cce4c7b6c7..82588a8028 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -644,7 +644,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_turnip || is_qualcomm) { LOG_WARNING(Render_Vulkan, "Driver requires higher-than-reported binding limits"); - properties.properties.limits.maxVertexInputBindings = 32; + properties.properties.limits.maxVertexInputBindings = + (std::max)(properties.properties.limits.maxVertexInputBindings, 32U); } const auto dyna_state = Settings::values.dyna_state.GetValue(); From 9b760e7c99d8b247967305b162e08ab06123f51d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 16:41:51 -0400 Subject: [PATCH 5/8] [TEST] Loggins for depth floats vs missing/ unsupported DepthBiasControl --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 863ebb72ec..e98fc4be14 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1255,6 +1255,20 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) { } } + const bool is_float_depth = + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT || + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT || + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X16V8X8_UINT || + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X16V8S8_UINT; + if (is_float_depth && units != 0.0f && !device.IsExtDepthBiasControlSupported()) { + static bool logged_float_bias_warning = false; + if (!logged_float_bias_warning) { + logged_float_bias_warning = true; + LOG_WARNING(Render_Vulkan, + "Depth bias on a float depth target without VK_EXT_depth_bias_control"); + } + } + scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, factor = regs.slope_scale_depth_bias, this](vk::CommandBuffer cmdbuf) { if (device.IsExtDepthBiasControlSupported()) { From a67eaaaacebf593879ff1b7cafa0b7742fdc4652 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 17:12:35 -0400 Subject: [PATCH 6/8] Removal of unneeded depthFormats --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e98fc4be14..a7b433f716 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1257,9 +1257,7 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) { const bool is_float_depth = regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT || - regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT || - regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X16V8X8_UINT || - regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X16V8S8_UINT; + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT; if (is_float_depth && units != 0.0f && !device.IsExtDepthBiasControlSupported()) { static bool logged_float_bias_warning = false; if (!logged_float_bias_warning) { From 538b535676703a1334924ff33fd2c5e4e98bbbf8 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 18:03:41 -0400 Subject: [PATCH 7/8] Quick review over workgroup changes --- .../backend/spirv/emit_spirv_memory.cpp | 12 ++++++------ .../backend/spirv/spirv_emit_context.cpp | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp index bdcbccfde9..cca1c911dc 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp @@ -92,7 +92,7 @@ void EmitLoadGlobalS16(EmitContext&) { } Id EmitLoadGlobal32(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { return ctx.OpFunctionCall(ctx.U32[1], ctx.load_global_func_u32, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -100,7 +100,7 @@ Id EmitLoadGlobal32(EmitContext& ctx, Id address) { } Id EmitLoadGlobal64(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { return ctx.OpFunctionCall(ctx.U32[2], ctx.load_global_func_u32x2, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -108,7 +108,7 @@ Id EmitLoadGlobal64(EmitContext& ctx, Id address) { } Id EmitLoadGlobal128(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { return ctx.OpFunctionCall(ctx.U32[4], ctx.load_global_func_u32x4, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -132,7 +132,7 @@ void EmitWriteGlobalS16(EmitContext&) { } void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32, address, value); return; } @@ -140,7 +140,7 @@ void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) { } void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x2, address, value); return; } @@ -148,7 +148,7 @@ void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) { } void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64) { + if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x4, address, value); return; } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 5b92744806..62b7cb147a 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -939,7 +939,8 @@ void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) { } void EmitContext::DefineGlobalMemoryFunctions(const Info& info) { - if (!info.uses_global_memory || !profile.support_int64) { + if (!info.uses_global_memory || !profile.support_int64 || + !profile.support_descriptor_aliasing) { return; } using DefPtr = Id StorageDefinitions::*; From ee833674fb07b99538b35cd2797038843d406e92 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 2 Jul 2026 19:37:50 -0400 Subject: [PATCH 8/8] Shader OpCapability fallback + fp16 ban adjustments --- .../backend/spirv/emit_spirv.cpp | 3 + .../backend/spirv/emit_spirv_memory.cpp | 15 ++- .../backend/spirv/spirv_emit_context.cpp | 112 +++++++++++++++++- .../backend/spirv/spirv_emit_context.h | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 10 +- 5 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 38bce7f82e..53582a28c7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -332,6 +332,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { void SetupDenormControl(const Profile& profile, const IR::Program& program, EmitContext& ctx, Id main_func) { const Info& info{program.info}; + if (profile.has_broken_fp16_float_controls && info.uses_fp16) { + return; + } if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) { LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader"); } else if (info.uses_fp32_denorms_flush) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp index cca1c911dc..b94389bb9f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -92,7 +95,7 @@ void EmitLoadGlobalS16(EmitContext&) { } Id EmitLoadGlobal32(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { return ctx.OpFunctionCall(ctx.U32[1], ctx.load_global_func_u32, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -100,7 +103,7 @@ Id EmitLoadGlobal32(EmitContext& ctx, Id address) { } Id EmitLoadGlobal64(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { return ctx.OpFunctionCall(ctx.U32[2], ctx.load_global_func_u32x2, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -108,7 +111,7 @@ Id EmitLoadGlobal64(EmitContext& ctx, Id address) { } Id EmitLoadGlobal128(EmitContext& ctx, Id address) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { return ctx.OpFunctionCall(ctx.U32[4], ctx.load_global_func_u32x4, address); } LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation"); @@ -132,7 +135,7 @@ void EmitWriteGlobalS16(EmitContext&) { } void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32, address, value); return; } @@ -140,7 +143,7 @@ void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) { } void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x2, address, value); return; } @@ -148,7 +151,7 @@ void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) { } void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) { - if (ctx.profile.support_int64 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int64) { ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x4, address, value); return; } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 62b7cb147a..35747424f9 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -939,8 +939,11 @@ void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) { } void EmitContext::DefineGlobalMemoryFunctions(const Info& info) { - if (!info.uses_global_memory || !profile.support_int64 || - !profile.support_descriptor_aliasing) { + if (!info.uses_global_memory || !profile.support_int64) { + return; + } + if (!profile.support_descriptor_aliasing) { + DefineGlobalMemoryFunctionsU32Fallback(info); return; } using DefPtr = Id StorageDefinitions::*; @@ -1020,6 +1023,111 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) { define(&StorageDefinitions::U32x4, storage_types.U32x4, U32[4], sizeof(u32[4])); } +void EmitContext::DefineGlobalMemoryFunctionsU32Fallback(const Info& info) { + const Id zero{u32_zero_value}; + const auto define_body{[&](Id addr, u32 num_words, auto&& callback) { + AddLabel(); + const size_t num_buffers{info.storage_buffers_descriptors.size()}; + for (size_t index = 0; index < num_buffers; ++index) { + if (!info.nvn_buffer_used[index]) { + continue; + } + const auto& ssbo{info.storage_buffers_descriptors[index]}; + const u32 addr_word{ssbo.cbuf_offset / 4}; + const u32 addr_lo_comp{addr_word % 4}; + const Id cbuf{cbufs[ssbo.cbuf_index].U32x4}; + const Id addr_vec_pointer{ + OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(addr_word / 4))}; + const Id addr_vec{OpLoad(U32[4], addr_vec_pointer)}; + const Id addr_lo{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp)}; + const Id addr_hi{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp + 1U)}; + const Id unaligned_addr{ + OpBitcast(U64, OpCompositeConstruct(U32[2], addr_lo, addr_hi))}; + + const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)}; + const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))}; + + const u32 size_word{addr_word + 2}; + Id size_vec{addr_vec}; + if (size_word / 4 != addr_word / 4) { + const Id size_vec_pointer{ + OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(size_word / 4))}; + size_vec = OpLoad(U32[4], size_vec_pointer); + } + const Id ssbo_size{ + OpUConvert(U64, OpCompositeExtract(U32[1], size_vec, size_word % 4))}; + const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)}; + const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr), + OpULessThan(U1, addr, ssbo_end))}; + const Id then_label{OpLabel()}; + const Id else_label{OpLabel()}; + OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone); + OpBranchConditional(cond, then_label, else_label); + AddLabel(then_label); + const Id ssbo_id{ssbos[index].U32}; + const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))}; + const Id base_word{OpShiftRightLogical(U32[1], ssbo_offset, Const(2U))}; + std::array word_pointers{}; + for (u32 word = 0; word < num_words; ++word) { + const Id word_index{word == 0 ? base_word + : OpIAdd(U32[1], base_word, Const(word))}; + word_pointers[word] = + OpAccessChain(storage_types.U32.element, ssbo_id, zero, word_index); + } + callback(word_pointers); + AddLabel(else_label); + } + }}; + const auto define_load{[&](Id type, u32 num_words) { + const Id function_type{TypeFunction(type, U64)}; + const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)}; + const Id addr{OpFunctionParameter(U64)}; + define_body(addr, num_words, [&](const std::array& pointers) { + std::array words{}; + for (u32 word = 0; word < num_words; ++word) { + words[word] = OpLoad(U32[1], pointers[word]); + } + switch (num_words) { + case 1: + OpReturnValue(words[0]); + break; + case 2: + OpReturnValue(OpCompositeConstruct(type, words[0], words[1])); + break; + default: + OpReturnValue( + OpCompositeConstruct(type, words[0], words[1], words[2], words[3])); + break; + } + }); + OpReturnValue(ConstantNull(type)); + OpFunctionEnd(); + return func_id; + }}; + const auto define_write{[&](Id type, u32 num_words) { + const Id function_type{TypeFunction(void_id, U64, type)}; + const Id func_id{OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)}; + const Id addr{OpFunctionParameter(U64)}; + const Id data{OpFunctionParameter(type)}; + define_body(addr, num_words, [&](const std::array& pointers) { + for (u32 word = 0; word < num_words; ++word) { + const Id value{num_words == 1 ? data : OpCompositeExtract(U32[1], data, word)}; + OpStore(pointers[word], value); + } + OpReturn(); + }); + OpReturn(); + OpFunctionEnd(); + return func_id; + }}; + load_global_func_u32 = define_load(U32[1], 1); + load_global_func_u32x2 = define_load(U32[2], 2); + load_global_func_u32x4 = define_load(U32[4], 4); + write_global_func_u32 = define_write(U32[1], 1); + write_global_func_u32x2 = define_write(U32[2], 2); + write_global_func_u32x4 = define_write(U32[4], 4); +} + void EmitContext::DefineRescalingInput(const Info& info) { if (!info.uses_rescaling_uniform) { return; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index e2d37a34c8..fe3d2051ad 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -391,6 +391,7 @@ private: void DefineAttributeMemAccess(const Info& info); void DefineWriteStorageCasLoopFunction(const Info& info); void DefineGlobalMemoryFunctions(const Info& info); + void DefineGlobalMemoryFunctionsU32Fallback(const Info& info); void DefineRescalingInput(const Info& info); void DefineRescalingInputPushConstant(); void DefineRescalingInputUniformConstant(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 04d34ac92b..e98f6a204b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -378,7 +378,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; - const bool has_broken_sz_inf_nan{driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY}; const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; @@ -411,14 +410,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_fp16_denorm_flush = float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE, .support_fp32_denorm_flush = float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE, .support_fp16_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE && - !has_broken_sz_inf_nan, + float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE, .support_fp32_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE && - !has_broken_sz_inf_nan, + float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE, .support_fp64_signed_zero_nan_preserve = - float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE && - !has_broken_sz_inf_nan, + float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_workgroup_layout_8bit_access = device.IsWorkgroupMemoryExplicitLayout8BitSupported(),