From d6a889828fa8038dfbef3c8245cb815e778b496f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:02:37 -0400 Subject: [PATCH 01/16] [vulkan] Invalidate scheduler state after rendering in multiple Vulkan draw functions --- src/video_core/renderer_vulkan/blit_image.cpp | 1 + src/video_core/renderer_vulkan/present/fsr.cpp | 1 + src/video_core/renderer_vulkan/present/fxaa.cpp | 1 + src/video_core/renderer_vulkan/present/smaa.cpp | 1 + src/video_core/renderer_vulkan/present/window_adapt_pass.cpp | 1 + 5 files changed, 5 insertions(+) diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index e22cf72c2a..144409bd59 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -602,6 +602,7 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView cmdbuf.Draw(3, 1, 0, 0); cmdbuf.EndRenderPass(); }); + scheduler.InvalidateState(); } void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, diff --git a/src/video_core/renderer_vulkan/present/fsr.cpp b/src/video_core/renderer_vulkan/present/fsr.cpp index ba6252ed95..b7c920a2f0 100644 --- a/src/video_core/renderer_vulkan/present/fsr.cpp +++ b/src/video_core/renderer_vulkan/present/fsr.cpp @@ -206,6 +206,7 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_i TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL); }); + scheduler.InvalidateState(); return *images.image_views[Rcas]; } diff --git a/src/video_core/renderer_vulkan/present/fxaa.cpp b/src/video_core/renderer_vulkan/present/fxaa.cpp index bdafd1f4d0..10c0a7c02a 100644 --- a/src/video_core/renderer_vulkan/present/fxaa.cpp +++ b/src/video_core/renderer_vulkan/present/fxaa.cpp @@ -140,6 +140,7 @@ void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, cmdbuf.EndRenderPass(); TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); }); + scheduler.InvalidateState(); *inout_image = *image.image; *inout_image_view = *image.image_view; diff --git a/src/video_core/renderer_vulkan/present/smaa.cpp b/src/video_core/renderer_vulkan/present/smaa.cpp index 686112f2f0..a9e30a813b 100644 --- a/src/video_core/renderer_vulkan/present/smaa.cpp +++ b/src/video_core/renderer_vulkan/present/smaa.cpp @@ -272,6 +272,7 @@ void SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, cmdbuf.EndRenderPass(); TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); }); + scheduler.InvalidateState(); *inout_image = *images.images[Output]; *inout_image_view = *images.image_views[Output]; diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp index 22ffacf119..5f415c3c3f 100644 --- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp @@ -101,6 +101,7 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s cmdbuf.EndRenderPass(); }); + scheduler.InvalidateState(); } VkDescriptorSetLayout WindowAdaptPass::GetDescriptorSetLayout() { From 042a10cf719930c98bc0ccf11032f2d24e0c74bd Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:04:14 -0400 Subject: [PATCH 02/16] Revert "[test] shader float control returned to Adreno" --- src/video_core/vulkan_common/vulkan_device.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index f2fe444e05..9131cdbfe2 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -502,6 +502,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { must_emulate_scaled_formats = true; + RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; From 02f9c5ffd204fb1641c6b5cedc1bc30529cfe3c3 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:07:59 -0400 Subject: [PATCH 03/16] Revert "[test] Histogram debug - shader float control -> initial target: Adreno" --- .../frontend/maxwell/translate_program.cpp | 219 ------------------ 1 file changed, 219 deletions(-) diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index 6cca023330..b1c1fc528b 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -5,9 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include -#include #include #include @@ -25,214 +23,6 @@ namespace Shader::Maxwell { namespace { -struct FpControlHistogram { - std::array total{}; - std::array no_contraction{}; - std::array, 2> rounding{}; - std::array, 2> fmz{}; - std::array, 5>, 2> combos{}; -}; - -[[nodiscard]] constexpr std::string_view StageName(Stage stage) noexcept { - switch (stage) { - case Stage::VertexA: - return "VertexA"; - case Stage::VertexB: - return "VertexB"; - case Stage::TessellationControl: - return "TessellationControl"; - case Stage::TessellationEval: - return "TessellationEval"; - case Stage::Geometry: - return "Geometry"; - case Stage::Fragment: - return "Fragment"; - case Stage::Compute: - return "Compute"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr std::string_view RoundingName(IR::FpRounding rounding) noexcept { - switch (rounding) { - case IR::FpRounding::DontCare: - return "DontCare"; - case IR::FpRounding::RN: - return "RN"; - case IR::FpRounding::RM: - return "RM"; - case IR::FpRounding::RP: - return "RP"; - case IR::FpRounding::RZ: - return "RZ"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr std::string_view FmzName(IR::FmzMode fmz_mode) noexcept { - switch (fmz_mode) { - case IR::FmzMode::DontCare: - return "DontCare"; - case IR::FmzMode::FTZ: - return "FTZ"; - case IR::FmzMode::FMZ: - return "FMZ"; - case IR::FmzMode::None: - return "None"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr std::optional FpControlBucket(const IR::Opcode opcode) noexcept { - switch (opcode) { - case IR::Opcode::FPAdd16: - case IR::Opcode::FPFma16: - case IR::Opcode::FPMul16: - case IR::Opcode::FPRoundEven16: - case IR::Opcode::FPFloor16: - case IR::Opcode::FPCeil16: - case IR::Opcode::FPTrunc16: - return 0; - case IR::Opcode::FPAdd32: - case IR::Opcode::FPFma32: - case IR::Opcode::FPMul32: - case IR::Opcode::FPRoundEven32: - case IR::Opcode::FPFloor32: - case IR::Opcode::FPCeil32: - case IR::Opcode::FPTrunc32: - case IR::Opcode::FPOrdEqual32: - case IR::Opcode::FPUnordEqual32: - case IR::Opcode::FPOrdNotEqual32: - case IR::Opcode::FPUnordNotEqual32: - case IR::Opcode::FPOrdLessThan32: - case IR::Opcode::FPUnordLessThan32: - case IR::Opcode::FPOrdGreaterThan32: - case IR::Opcode::FPUnordGreaterThan32: - case IR::Opcode::FPOrdLessThanEqual32: - case IR::Opcode::FPUnordLessThanEqual32: - case IR::Opcode::FPOrdGreaterThanEqual32: - case IR::Opcode::FPUnordGreaterThanEqual32: - case IR::Opcode::ConvertF16F32: - case IR::Opcode::ConvertF64F32: - return 1; - default: - return std::nullopt; - } -} - -FpControlHistogram CollectFpControlHistogram(const IR::Program& program) { - FpControlHistogram histogram{}; - for (const IR::Block* const block : program.post_order_blocks) { - for (const IR::Inst& inst : block->Instructions()) { - const std::optional bucket{FpControlBucket(inst.GetOpcode())}; - if (!bucket) { - continue; - } - const auto flags{inst.Flags()}; - ++histogram.total[*bucket]; - if (flags.no_contraction) { - ++histogram.no_contraction[*bucket]; - } - ++histogram.rounding[*bucket][static_cast(flags.rounding)]; - ++histogram.fmz[*bucket][static_cast(flags.fmz_mode)]; - ++histogram.combos[*bucket][static_cast(flags.rounding)] - [static_cast(flags.fmz_mode)]; - } - } - return histogram; -} - -void LogRzFpControlTrace(Environment& env, const IR::Program& program) { - std::array totals{}; - for (const IR::Block* const block : program.post_order_blocks) { - for (const IR::Inst& inst : block->Instructions()) { - const std::optional bucket{FpControlBucket(inst.GetOpcode())}; - if (!bucket) { - continue; - } - const auto flags{inst.Flags()}; - if (flags.rounding != IR::FpRounding::RZ) { - continue; - } - ++totals[*bucket]; - } - } - - if (totals[0] == 0 && totals[1] == 0) { - return; - } - - constexpr std::array precision_names{"fp16", "fp32"}; - LOG_INFO(Shader, - "FP_RZ {} shader start={:#010x} blocks={} post_order_blocks={} fp16={} fp32={}", - StageName(program.stage), env.StartAddress(), program.blocks.size(), - program.post_order_blocks.size(), totals[0], totals[1]); - - for (const IR::Block* const block : program.post_order_blocks) { - u32 inst_index{}; - for (const IR::Inst& inst : block->Instructions()) { - const std::optional bucket{FpControlBucket(inst.GetOpcode())}; - if (!bucket) { - ++inst_index; - continue; - } - const auto flags{inst.Flags()}; - if (flags.rounding != IR::FpRounding::RZ) { - ++inst_index; - continue; - } - LOG_INFO(Shader, - "FP_RZ {} start={:#010x} block_order={} inst_index={} precision={} opcode={} no_contraction={} fmz={}", - StageName(program.stage), env.StartAddress(), block->GetOrder(), inst_index, - precision_names[*bucket], inst.GetOpcode(), flags.no_contraction, - FmzName(flags.fmz_mode)); - ++inst_index; - } - } -} - -void LogFpControlHistogram(const IR::Program& program) { - const FpControlHistogram histogram{CollectFpControlHistogram(program)}; - if (histogram.total[0] == 0 && histogram.total[1] == 0) { - return; - } - - LOG_INFO(Shader, "FP_HIST {} shader blocks={} post_order_blocks={}", - StageName(program.stage), program.blocks.size(), program.post_order_blocks.size()); - - constexpr std::array precision_names{"fp16", "fp32"}; - for (size_t bucket = 0; bucket < precision_names.size(); ++bucket) { - if (histogram.total[bucket] == 0) { - continue; - } - - LOG_INFO(Shader, - "FP_HIST {} total={} no_contraction={} rounding[DontCare={}, RN={}, RM={}, RP={}, RZ={}] fmz[DontCare={}, FTZ={}, FMZ={}, None={}]", - precision_names[bucket], histogram.total[bucket], histogram.no_contraction[bucket], - histogram.rounding[bucket][static_cast(IR::FpRounding::DontCare)], - histogram.rounding[bucket][static_cast(IR::FpRounding::RN)], - histogram.rounding[bucket][static_cast(IR::FpRounding::RM)], - histogram.rounding[bucket][static_cast(IR::FpRounding::RP)], - histogram.rounding[bucket][static_cast(IR::FpRounding::RZ)], - histogram.fmz[bucket][static_cast(IR::FmzMode::DontCare)], - histogram.fmz[bucket][static_cast(IR::FmzMode::FTZ)], - histogram.fmz[bucket][static_cast(IR::FmzMode::FMZ)], - histogram.fmz[bucket][static_cast(IR::FmzMode::None)]); - - for (size_t rounding = 0; rounding < histogram.combos[bucket].size(); ++rounding) { - for (size_t fmz = 0; fmz < histogram.combos[bucket][rounding].size(); ++fmz) { - const u32 count{histogram.combos[bucket][rounding][fmz]}; - if (count == 0) { - continue; - } - LOG_INFO(Shader, "FP_HIST {} combo {} / {} = {}", precision_names[bucket], - RoundingName(static_cast(rounding)), - FmzName(static_cast(fmz)), count); - } - } - } -} - IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) { size_t num_syntax_blocks{}; for (const auto& node : syntax_list) { @@ -527,11 +317,6 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool Date: Sun, 8 Mar 2026 01:10:06 -0400 Subject: [PATCH 04/16] Revert "[debug] fix logging entries for histogram" --- src/shader_recompiler/frontend/maxwell/translate_program.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index b1c1fc528b..6f135971bf 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -9,7 +9,6 @@ #include #include -#include "common/logging/log.h" #include "common/settings.h" #include "shader_recompiler/exception.h" #include "shader_recompiler/frontend/ir/basic_block.h" From 73c16c3d45ad26afde466bf503a77321a474ce19 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:17:13 -0400 Subject: [PATCH 05/16] Revert "[debug] Added extra logging/ address for shader info -> FP32Mul Optimize Path" --- .../backend/spirv/emit_spirv.cpp | 155 ------------------ .../spirv/emit_spirv_floating_point.cpp | 43 ----- .../backend/spirv/spirv_emit_context.cpp | 39 +---- .../backend/spirv/spirv_emit_context.h | 2 - src/shader_recompiler/frontend/ir/program.h | 1 - .../frontend/maxwell/translate_program.cpp | 2 - 6 files changed, 1 insertion(+), 241 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 97124c0bfa..5de453b97b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -11,7 +11,6 @@ #include #include -#include "common/logging/log.h" #include "common/settings.h" #include "shader_recompiler/backend/spirv/emit_spirv.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" @@ -22,148 +21,6 @@ namespace Shader::Backend::SPIRV { namespace { -[[nodiscard]] constexpr std::string_view StageName(Stage stage) noexcept { - switch (stage) { - case Stage::VertexA: - return "VertexA"; - case Stage::VertexB: - return "VertexB"; - case Stage::TessellationControl: - return "TessellationControl"; - case Stage::TessellationEval: - return "TessellationEval"; - case Stage::Geometry: - return "Geometry"; - case Stage::Fragment: - return "Fragment"; - case Stage::Compute: - return "Compute"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr std::string_view DenormModeName(bool flush, bool preserve) noexcept { - if (flush && preserve) { - return "Flush+Preserve"; - } - if (flush) { - return "Flush"; - } - if (preserve) { - return "Preserve"; - } - return "None"; -} - -[[nodiscard]] constexpr bool IsFp32RoundingRelevantOpcode(IR::Opcode opcode) noexcept { - switch (opcode) { - case IR::Opcode::FPAdd32: - case IR::Opcode::FPFma32: - case IR::Opcode::FPMul32: - case IR::Opcode::FPRoundEven32: - case IR::Opcode::FPFloor32: - case IR::Opcode::FPCeil32: - case IR::Opcode::FPTrunc32: - case IR::Opcode::FPOrdEqual32: - case IR::Opcode::FPUnordEqual32: - case IR::Opcode::FPOrdNotEqual32: - case IR::Opcode::FPUnordNotEqual32: - case IR::Opcode::FPOrdLessThan32: - case IR::Opcode::FPUnordLessThan32: - case IR::Opcode::FPOrdGreaterThan32: - case IR::Opcode::FPUnordGreaterThan32: - case IR::Opcode::FPOrdLessThanEqual32: - case IR::Opcode::FPUnordLessThanEqual32: - case IR::Opcode::FPOrdGreaterThanEqual32: - case IR::Opcode::FPUnordGreaterThanEqual32: - case IR::Opcode::ConvertF16F32: - case IR::Opcode::ConvertF64F32: - return true; - default: - return false; - } -} - -struct Fp32RoundingUsage { - u32 rz_count{}; - bool has_conflicting_rounding{}; -}; - -Fp32RoundingUsage CollectFp32RoundingUsage(const IR::Program& program) { - Fp32RoundingUsage usage{}; - for (const IR::Block* const block : program.post_order_blocks) { - for (const IR::Inst& inst : block->Instructions()) { - if (!IsFp32RoundingRelevantOpcode(inst.GetOpcode())) { - continue; - } - switch (inst.Flags().rounding) { - case IR::FpRounding::RZ: - ++usage.rz_count; - break; - case IR::FpRounding::RN: - case IR::FpRounding::RM: - case IR::FpRounding::RP: - usage.has_conflicting_rounding = true; - break; - case IR::FpRounding::DontCare: - break; - } - } - } - return usage; -} - -void LogRzBackendSummary(const Profile& profile, const IR::Program& program, bool optimize) { - if (!Settings::values.renderer_debug) { - return; - } - const Fp32RoundingUsage usage{CollectFp32RoundingUsage(program)}; - if (usage.rz_count == 0) { - return; - } - - LOG_INFO(Shader_SPIRV, - "SPV_RZ {} start={:#010x} optimize={} support_float_controls={} separate_denorm_behavior={} separate_rounding_mode={} support_fp32_rounding_rtz={} broken_fp16_float_controls={} fp16_denorm={} fp32_denorm={} signed_nan16={} signed_nan32={} signed_nan64={} rz_inst_count={} mixed_fp32_rounding={}", - StageName(program.stage), program.start_address, optimize, - profile.support_float_controls, profile.support_separate_denorm_behavior, - profile.support_separate_rounding_mode, profile.support_fp32_rounding_rtz, - profile.has_broken_fp16_float_controls, - DenormModeName(program.info.uses_fp16_denorms_flush, - program.info.uses_fp16_denorms_preserve), - DenormModeName(program.info.uses_fp32_denorms_flush, - program.info.uses_fp32_denorms_preserve), - profile.support_fp16_signed_zero_nan_preserve, - profile.support_fp32_signed_zero_nan_preserve, - profile.support_fp64_signed_zero_nan_preserve, usage.rz_count, - usage.has_conflicting_rounding); -} - -void SetupRoundingControl(const Profile& profile, const IR::Program& program, EmitContext& ctx, - Id main_func) { - const Fp32RoundingUsage usage{CollectFp32RoundingUsage(program)}; - if (usage.rz_count == 0) { - return; - } - if (usage.has_conflicting_rounding) { - if (Settings::values.renderer_debug) { - LOG_INFO(Shader_SPIRV, - "SPV_RZ {} start={:#010x} skipping_fp32_rtz_execution_mode reason=mixed_rounding", - StageName(program.stage), program.start_address); - } - return; - } - if (!profile.support_fp32_rounding_rtz) { - if (Settings::values.renderer_debug) { - LOG_INFO(Shader_SPIRV, - "SPV_RZ {} start={:#010x} skipping_fp32_rtz_execution_mode reason=unsupported_fp32_rtz", - StageName(program.stage), program.start_address); - } - return; - } - ctx.AddCapability(spv::Capability::RoundingModeRTZ); - ctx.AddExecutionMode(main_func, spv::ExecutionMode::RoundingModeRTZ, 32U); -} - template struct FuncTraits {}; thread_local std::unique_ptr thread_optimizer; @@ -647,7 +504,6 @@ void PatchPhiNodes(IR::Program& program, EmitContext& ctx) { std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings, bool optimize) { - LogRzBackendSummary(profile, program, optimize); EmitContext ctx{profile, runtime_info, program, bindings}; const Id main{DefineMain(ctx, program)}; DefineEntryPoint(program, ctx, main); @@ -662,12 +518,6 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in PatchPhiNodes(program, ctx); if (!optimize) { - if (Settings::values.renderer_debug && ctx.log_rz_fp_controls) { - const std::vector spirv{ctx.Assemble()}; - LOG_INFO(Shader_SPIRV, "SPV_RZ {} start={:#010x} assembled_words={} optimized_words={} validator_run=false", - StageName(program.stage), program.start_address, spirv.size(), spirv.size()); - return spirv; - } return ctx.Assemble(); } else { std::vector spirv = ctx.Assemble(); @@ -687,11 +537,6 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in "Failed to optimize SPIRV shader output, continuing without optimization"); result = std::move(spirv); } - if (Settings::values.renderer_debug && ctx.log_rz_fp_controls) { - LOG_INFO(Shader_SPIRV, - "SPV_RZ {} start={:#010x} assembled_words={} optimized_words={} validator_run=false", - StageName(program.stage), program.start_address, spirv.size(), result.size()); - } return result; } } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp index 1957c26df9..6851860edf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -4,57 +4,14 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/logging/log.h" -#include "common/settings.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/frontend/ir/modifiers.h" namespace Shader::Backend::SPIRV { namespace { -[[nodiscard]] constexpr std::string_view StageName(Stage stage) noexcept { - switch (stage) { - case Stage::VertexA: - return "VertexA"; - case Stage::VertexB: - return "VertexB"; - case Stage::TessellationControl: - return "TessellationControl"; - case Stage::TessellationEval: - return "TessellationEval"; - case Stage::Geometry: - return "Geometry"; - case Stage::Fragment: - return "Fragment"; - case Stage::Compute: - return "Compute"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr std::string_view FmzName(IR::FmzMode fmz_mode) noexcept { - switch (fmz_mode) { - case IR::FmzMode::DontCare: - return "DontCare"; - case IR::FmzMode::FTZ: - return "FTZ"; - case IR::FmzMode::FMZ: - return "FMZ"; - case IR::FmzMode::None: - return "None"; - } - return "Unknown"; -} - Id Decorate(EmitContext& ctx, IR::Inst* inst, Id op) { const auto flags{inst->Flags()}; - if (Settings::values.renderer_debug && ctx.log_rz_fp_controls && - flags.rounding == IR::FpRounding::RZ) { - LOG_INFO(Shader_SPIRV, - "SPV_RZ_EMIT {} start={:#010x} ir_opcode={} spirv_op=OpFMul no_contraction={} fmz={} float_controls_ext={}", - StageName(ctx.stage), ctx.start_address, inst->GetOpcode(), - flags.no_contraction, FmzName(flags.fmz_mode), ctx.profile.support_float_controls); - } if (flags.no_contraction) { ctx.Decorate(op, spv::Decoration::NoContraction); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 7b422a4a41..957fe6e15b 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -474,44 +474,7 @@ void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_vie EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_info_, IR::Program& program, Bindings& bindings) : Sirit::Module(profile_.supported_spirv), profile{profile_}, runtime_info{runtime_info_}, - stage{program.stage}, start_address{program.start_address}, - log_rz_fp_controls{std::ranges::any_of(program.post_order_blocks, [](const IR::Block* block) { - return std::ranges::any_of(block->Instructions(), [](const IR::Inst& inst) { - switch (inst.GetOpcode()) { - case IR::Opcode::FPAdd16: - case IR::Opcode::FPFma16: - case IR::Opcode::FPMul16: - case IR::Opcode::FPRoundEven16: - case IR::Opcode::FPFloor16: - case IR::Opcode::FPCeil16: - case IR::Opcode::FPTrunc16: - case IR::Opcode::FPAdd32: - case IR::Opcode::FPFma32: - case IR::Opcode::FPMul32: - case IR::Opcode::FPRoundEven32: - case IR::Opcode::FPFloor32: - case IR::Opcode::FPCeil32: - case IR::Opcode::FPTrunc32: - case IR::Opcode::FPOrdEqual32: - case IR::Opcode::FPUnordEqual32: - case IR::Opcode::FPOrdNotEqual32: - case IR::Opcode::FPUnordNotEqual32: - case IR::Opcode::FPOrdLessThan32: - case IR::Opcode::FPUnordLessThan32: - case IR::Opcode::FPOrdGreaterThan32: - case IR::Opcode::FPUnordGreaterThan32: - case IR::Opcode::FPOrdLessThanEqual32: - case IR::Opcode::FPUnordLessThanEqual32: - case IR::Opcode::FPOrdGreaterThanEqual32: - case IR::Opcode::FPUnordGreaterThanEqual32: - case IR::Opcode::ConvertF16F32: - case IR::Opcode::ConvertF64F32: - return inst.Flags().rounding == IR::FpRounding::RZ; - default: - return false; - } - }); - })}, texture_rescaling_index{bindings.texture_scaling_index}, + stage{program.stage}, texture_rescaling_index{bindings.texture_scaling_index}, image_rescaling_index{bindings.image_scaling_index} { const bool is_unified{profile.unified_descriptor_binding}; u32& uniform_binding{is_unified ? bindings.unified : bindings.uniform_buffer}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 21151bab38..396022eddf 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -216,8 +216,6 @@ public: const Profile& profile; const RuntimeInfo& runtime_info; Stage stage{}; - u32 start_address{}; - bool log_rz_fp_controls{}; Id void_id{}; Id U1{}; diff --git a/src/shader_recompiler/frontend/ir/program.h b/src/shader_recompiler/frontend/ir/program.h index 1836a18bd3..7be51c2d66 100644 --- a/src/shader_recompiler/frontend/ir/program.h +++ b/src/shader_recompiler/frontend/ir/program.h @@ -23,7 +23,6 @@ struct Program { BlockList post_order_blocks; Info info; Stage stage{}; - u32 start_address{}; std::array workgroup_size{}; OutputTopology output_topology{}; u32 output_vertices{}; diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index 6f135971bf..f156192c13 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -247,7 +247,6 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool Date: Sun, 8 Mar 2026 01:18:06 -0400 Subject: [PATCH 06/16] Revert "fix build" --- src/shader_recompiler/backend/spirv/emit_spirv.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 5de453b97b..1fa4c76f6a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -16,7 +16,6 @@ #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/frontend/ir/basic_block.h" -#include "shader_recompiler/frontend/ir/modifiers.h" #include "shader_recompiler/frontend/ir/program.h" namespace Shader::Backend::SPIRV { From 1aae75512bf8269249d4b9f980fd25f7c382126a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:21:36 -0400 Subject: [PATCH 07/16] Revert "[vulkan] Added conservative path for RoundingModeRTZ + instrumentalization for shaders use" --- src/shader_recompiler/backend/spirv/emit_spirv.cpp | 1 - src/shader_recompiler/profile.h | 1 - src/video_core/renderer_opengl/gl_shader_cache.cpp | 1 - src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 1 - 4 files changed, 4 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 1fa4c76f6a..3fd45f8695 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -509,7 +509,6 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in if (profile.support_float_controls) { ctx.AddExtension("SPV_KHR_float_controls"); SetupDenormControl(profile, program, ctx, main); - SetupRoundingControl(profile, program, ctx, main); SetupSignedNanCapabilities(profile, program, ctx, main); } SetupCapabilities(profile, program.info, ctx); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 5a8993834c..35fcad3843 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -21,7 +21,6 @@ struct Profile { bool support_float_controls{}; bool support_separate_denorm_behavior{}; bool support_separate_rounding_mode{}; - bool support_fp32_rounding_rtz{}; bool support_fp16_denorm_preserve{}; bool support_fp32_denorm_preserve{}; bool support_fp16_denorm_flush{}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index a1d01317ba..70a13d6a69 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -194,7 +194,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_float_controls = false, .support_separate_denorm_behavior = false, .support_separate_rounding_mode = false, - .support_fp32_rounding_rtz = false, .support_fp16_denorm_preserve = false, .support_fp32_denorm_preserve = false, .support_fp16_denorm_flush = false, diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 85234838b8..2aff10e15b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -377,7 +377,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.denormBehaviorIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, .support_separate_rounding_mode = float_control.roundingModeIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, - .support_fp32_rounding_rtz = float_control.shaderRoundingModeRTZFloat32 != VK_FALSE, .support_fp16_denorm_preserve = float_control.shaderDenormPreserveFloat16 != VK_FALSE, .support_fp32_denorm_preserve = float_control.shaderDenormPreserveFloat32 != VK_FALSE, .support_fp16_denorm_flush = float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE, From 4a7aa1618a87bf2b388b8bbb65b9de3a889feb16 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:48:23 -0400 Subject: [PATCH 08/16] [vulkan] Add InvalidateState for old topology draw --- src/video_core/renderer_vulkan/vk_scheduler.cpp | 5 +++++ src/video_core/renderer_vulkan/vk_state_tracker.h | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 710bfddc74..dba18a91cb 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -168,6 +168,11 @@ bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) { return true; } + if (pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() || + pipeline->UsesExtendedDynamicState2LogicOp()) { + state_tracker.InvalidateExtendedDynamicStates(); + } + if (!pipeline->UsesExtendedDynamicState()) { state.needs_state_enable_refresh = true; } else if (state.needs_state_enable_refresh) { diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 81cd4d9631..47948ddc64 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -94,6 +94,21 @@ public: (*flags)[Dirty::StateEnable] = true; } + void InvalidateExtendedDynamicStates() { + (*flags)[Dirty::Viewports] = true; + (*flags)[Dirty::Scissors] = true; + (*flags)[Dirty::CullMode] = true; + (*flags)[Dirty::DepthCompareOp] = true; + (*flags)[Dirty::FrontFace] = true; + (*flags)[Dirty::StencilOp] = true; + (*flags)[Dirty::StateEnable] = true; + (*flags)[Dirty::PrimitiveRestartEnable] = true; + (*flags)[Dirty::RasterizerDiscardEnable] = true; + (*flags)[Dirty::DepthBiasEnable] = true; + (*flags)[Dirty::LogicOp] = true; + current_topology = INVALID_TOPOLOGY; + } + bool TouchViewports() { const bool dirty_viewports = Exchange(Dirty::Viewports, false); const bool rescale_viewports = Exchange(VideoCommon::Dirty::RescaleViewports, false); From cd25e61afaba4712f74ed2d50c5736d5bf0cd86a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 01:58:38 -0400 Subject: [PATCH 09/16] Fix license headers --- src/video_core/renderer_vulkan/present/fxaa.cpp | 3 +++ src/video_core/renderer_vulkan/present/smaa.cpp | 2 +- src/video_core/renderer_vulkan/present/window_adapt_pass.cpp | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/present/fxaa.cpp b/src/video_core/renderer_vulkan/present/fxaa.cpp index 10c0a7c02a..8aabcc57f0 100644 --- a/src/video_core/renderer_vulkan/present/fxaa.cpp +++ b/src/video_core/renderer_vulkan/present/fxaa.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/video_core/renderer_vulkan/present/smaa.cpp b/src/video_core/renderer_vulkan/present/smaa.cpp index a9e30a813b..52f623c996 100644 --- a/src/video_core/renderer_vulkan/present/smaa.cpp +++ b/src/video_core/renderer_vulkan/present/smaa.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp index 5f415c3c3f..6851e20d7b 100644 --- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later From daf972b51748cfe74f0890f04006095c5ef5976c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 03:41:42 -0400 Subject: [PATCH 10/16] [android] Replaced FramePacingMode path + Surface/EmuWindow added hints for FrameRate --- .../settings/model/view/SettingsItem.kt | 9 --- .../settings/ui/SettingsFragmentPresenter.kt | 1 - .../src/main/jni/emu_window/emu_window.cpp | 67 +++++++++++++++++++ .../app/src/main/jni/emu_window/emu_window.h | 4 ++ .../renderer_vulkan/vk_swapchain.cpp | 4 ++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index cabea73353..73a977bb1f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -647,15 +647,6 @@ abstract class SettingsItem( valuesId = R.array.dmaAccuracyValues ) ) - put( - SingleChoiceSetting( - IntSetting.FRAME_PACING_MODE, - titleId = R.string.frame_pacing_mode, - descriptionId = R.string.frame_pacing_mode_description, - choicesId = R.array.framePacingModeNames, - valuesId = R.array.framePacingModeValues - ) - ) put( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index c43de4d5c7..6d769b2e15 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -267,7 +267,6 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key) - add(IntSetting.FRAME_PACING_MODE.key) add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 4e90cad570..36615f9d96 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -6,8 +6,14 @@ #include +#include +#include +#include +#include + #include "common/android/id_cache.h" #include "common/logging/log.h" +#include "common/settings.h" #include "input_common/drivers/android.h" #include "input_common/drivers/touch_screen.h" #include "input_common/drivers/virtual_amiibo.h" @@ -22,6 +28,7 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { m_window_width = 0; m_window_height = 0; window_info.render_surface = nullptr; + m_last_frame_rate_hint = -1.0f; return; } @@ -32,6 +39,7 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { UpdateCurrentFramebufferLayout(m_window_width, m_window_height); window_info.render_surface = reinterpret_cast(surface); + UpdateFrameRateHint(); } void EmuWindow_Android::OnTouchPressed(int id, float x, float y) { @@ -51,6 +59,8 @@ void EmuWindow_Android::OnTouchReleased(int id) { } void EmuWindow_Android::OnFrameDisplayed() { + UpdateFrameRateHint(); + if (!m_first_frame) { Common::Android::RunJNIOnFiber( [&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); }); @@ -58,6 +68,63 @@ void EmuWindow_Android::OnFrameDisplayed() { } } +float EmuWindow_Android::GetFrameRateHint() const { + if (!Settings::values.use_speed_limit.GetValue()) { + return 0.0f; + } + + const u16 speed_limit = Settings::SpeedLimit(); + if (speed_limit == 0) { + return 0.0f; + } + + if (speed_limit > 100) { + return 0.0f; + } + + constexpr float NominalFrameRate = 60.0f; + const float desired_rate = NominalFrameRate * (static_cast(speed_limit) / 100.0f); + + if (desired_rate < 20.0f) { + return 0.0f; + } + + return desired_rate; +} + +void EmuWindow_Android::UpdateFrameRateHint() { + auto* const surface = reinterpret_cast(window_info.render_surface); + if (!surface) { + return; + } + + const float frame_rate_hint = GetFrameRateHint(); + if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) { + return; + } + + using SetFrameRateWithChangeStrategyFn = + int32_t (*)(ANativeWindow*, float, int8_t, int8_t); + static const auto set_frame_rate_with_change_strategy = + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRateWithChangeStrategy")); + + if (!set_frame_rate_with_change_strategy) { + return; + } + + const auto result = set_frame_rate_with_change_strategy( + surface, frame_rate_hint, + static_cast(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT), + static_cast(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS)); + if (result != 0) { + LOG_DEBUG(Frontend, "Failed to update Android surface frame rate hint: {}", result); + return; + } + + m_last_frame_rate_hint = frame_rate_hint; +} + EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, std::shared_ptr driver_library) : m_driver_library{driver_library} { diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index d7b5fc6dac..b2210e0b20 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -50,10 +50,14 @@ public: }; private: + void UpdateFrameRateHint(); + [[nodiscard]] float GetFrameRateHint() const; + float m_window_width{}; float m_window_height{}; std::shared_ptr m_driver_library; bool m_first_frame = false; + float m_last_frame_rate_hint = -1.0f; }; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index cd8f948d8b..46b98f6cc1 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -170,6 +170,9 @@ bool Swapchain::AcquireNextImage() { break; } +#ifdef __ANDROID__ + scheduler.Wait(resource_ticks[image_index]); +#else switch (Settings::values.frame_pacing_mode.GetValue()) { case Settings::FramePacingMode::Target_Auto: scheduler.Wait(resource_ticks[image_index]); @@ -187,6 +190,7 @@ bool Swapchain::AcquireNextImage() { scheduler.Wait(resource_ticks[image_index], 120.0); break; } +#endif resource_ticks[image_index] = scheduler.CurrentTick(); From ebb3cda782353f147613b520e2b9aaa752a5475c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 03:56:58 -0400 Subject: [PATCH 11/16] [android] Quantization for frames + hints Surface --- .../src/main/jni/emu_window/emu_window.cpp | 105 +++++++++++++++++- .../app/src/main/jni/emu_window/emu_window.h | 13 +++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 36615f9d96..2bd9f060d6 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,10 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { m_window_height = 0; window_info.render_surface = nullptr; m_last_frame_rate_hint = -1.0f; + m_pending_frame_rate_hint = -1.0f; + m_pending_frame_rate_hint_votes = 0; + m_smoothed_present_rate = 0.0f; + m_last_frame_display_time = {}; return; } @@ -59,6 +64,7 @@ void EmuWindow_Android::OnTouchReleased(int id) { } void EmuWindow_Android::OnFrameDisplayed() { + UpdateObservedFrameRate(); UpdateFrameRateHint(); if (!m_first_frame) { @@ -68,6 +74,44 @@ void EmuWindow_Android::OnFrameDisplayed() { } } +void EmuWindow_Android::UpdateObservedFrameRate() { + const auto now = Clock::now(); + if (m_last_frame_display_time.time_since_epoch().count() != 0) { + const auto frame_time = std::chrono::duration(now - m_last_frame_display_time); + const float seconds = frame_time.count(); + if (seconds > 0.0f) { + const float instantaneous_rate = 1.0f / seconds; + if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 10.0f && + instantaneous_rate <= 240.0f) { + constexpr float SmoothingFactor = 0.15f; + if (m_smoothed_present_rate <= 0.0f) { + m_smoothed_present_rate = instantaneous_rate; + } else { + m_smoothed_present_rate += + (instantaneous_rate - m_smoothed_present_rate) * SmoothingFactor; + } + } + } + } + m_last_frame_display_time = now; +} + +float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) { + if (!std::isfinite(frame_rate) || frame_rate < 20.0f) { + return 0.0f; + } + + constexpr std::array CandidateRates{30.0f, 45.0f, 60.0f, 90.0f, 120.0f}; + const auto best = std::min_element(CandidateRates.begin(), CandidateRates.end(), + [frame_rate](float lhs, float rhs) { + return std::fabs(frame_rate - lhs) < + std::fabs(frame_rate - rhs); + }); + const float best_rate = *best; + const float tolerance = std::max(best_rate * 0.18f, 5.0f); + return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : 0.0f; +} + float EmuWindow_Android::GetFrameRateHint() const { if (!Settings::values.use_speed_limit.GetValue()) { return 0.0f; @@ -89,7 +133,45 @@ float EmuWindow_Android::GetFrameRateHint() const { return 0.0f; } - return desired_rate; + if (m_last_frame_rate_hint > 0.0f && m_smoothed_present_rate > 0.0f) { + const float observed_rate = m_smoothed_present_rate; + switch (static_cast(m_last_frame_rate_hint)) { + case 30: + if (observed_rate < 42.0f) { + return 30.0f; + } + break; + case 45: + if (observed_rate >= 35.0f && observed_rate < 54.0f) { + return 45.0f; + } + break; + case 60: + if (observed_rate >= 48.0f && observed_rate < 75.0f) { + return 60.0f; + } + break; + case 90: + if (observed_rate >= 74.0f && observed_rate < 105.0f) { + return 90.0f; + } + break; + case 120: + if (observed_rate >= 100.0f) { + return 120.0f; + } + break; + default: + break; + } + } + + const float observed_hint = QuantizeFrameRateHint(m_smoothed_present_rate); + if (observed_hint > 0.0f) { + return observed_hint; + } + + return QuantizeFrameRateHint(desired_rate); } void EmuWindow_Android::UpdateFrameRateHint() { @@ -100,9 +182,28 @@ void EmuWindow_Android::UpdateFrameRateHint() { const float frame_rate_hint = GetFrameRateHint(); if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; return; } + if (frame_rate_hint == 0.0f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; + } else if (m_last_frame_rate_hint >= 0.0f) { + if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 1; + return; + } + + ++m_pending_frame_rate_hint_votes; + constexpr std::uint32_t StableVoteThreshold = 12; + if (m_pending_frame_rate_hint_votes < StableVoteThreshold) { + return; + } + } + using SetFrameRateWithChangeStrategyFn = int32_t (*)(ANativeWindow*, float, int8_t, int8_t); static const auto set_frame_rate_with_change_strategy = @@ -123,6 +224,8 @@ void EmuWindow_Android::UpdateFrameRateHint() { } m_last_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; } EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index b2210e0b20..3d0f74a481 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once +#include +#include #include #include @@ -50,8 +55,12 @@ public: }; private: + using Clock = std::chrono::steady_clock; + void UpdateFrameRateHint(); + void UpdateObservedFrameRate(); [[nodiscard]] float GetFrameRateHint() const; + [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); float m_window_width{}; float m_window_height{}; @@ -60,4 +69,8 @@ private: bool m_first_frame = false; float m_last_frame_rate_hint = -1.0f; + float m_pending_frame_rate_hint = -1.0f; + float m_smoothed_present_rate = 0.0f; + Clock::time_point m_last_frame_display_time{}; + std::uint32_t m_pending_frame_rate_hint_votes = 0; }; From 8c79a3a8de481c5d84cf256a092bb09d37d72dce Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 04:05:42 -0400 Subject: [PATCH 12/16] [android] Adjusted heuristics hints for SpeedLimit x ScreenRefresh + Quantized FrameRate calculation --- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 2 - .../src/main/jni/emu_window/emu_window.cpp | 79 ++++++------------- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index db4cc0f60e..440e7ce399 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -94,8 +94,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { binding = ActivityMainBinding.inflate(layoutInflater) - // Since Android 15, google automatically forces "games" to be 60 hrz - // This ensures the display's max refresh rate is actually used display?.let { val supportedModes = it.supportedModes val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate } diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 2bd9f060d6..8c2296be48 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -101,20 +101,38 @@ float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) { return 0.0f; } - constexpr std::array CandidateRates{30.0f, 45.0f, 60.0f, 90.0f, 120.0f}; + frame_rate = std::clamp(frame_rate, 20.0f, 240.0f); + + constexpr std::array CandidateRates{24.0f, 30.0f, 36.0f, 40.0f, 45.0f, 48.0f, + 60.0f, 72.0f, 80.0f, 90.0f, 96.0f, 120.0f, + 144.0f, 165.0f, 180.0f, 200.0f, 240.0f}; const auto best = std::min_element(CandidateRates.begin(), CandidateRates.end(), [frame_rate](float lhs, float rhs) { return std::fabs(frame_rate - lhs) < std::fabs(frame_rate - rhs); }); const float best_rate = *best; - const float tolerance = std::max(best_rate * 0.18f, 5.0f); - return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : 0.0f; + const float tolerance = std::max(best_rate * 0.12f, 4.0f); + return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : best_rate; } float EmuWindow_Android::GetFrameRateHint() const { + const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f); + if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) { + const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f); + if (std::fabs(observed_rate - m_last_frame_rate_hint) <= tolerance) { + return m_last_frame_rate_hint; + } + } + + const float observed_hint = QuantizeFrameRateHint(observed_rate); + if (observed_hint > 0.0f) { + return observed_hint; + } + + constexpr float NominalFrameRate = 60.0f; if (!Settings::values.use_speed_limit.GetValue()) { - return 0.0f; + return NominalFrameRate; } const u16 speed_limit = Settings::SpeedLimit(); @@ -122,56 +140,9 @@ float EmuWindow_Android::GetFrameRateHint() const { return 0.0f; } - if (speed_limit > 100) { - return 0.0f; - } - - constexpr float NominalFrameRate = 60.0f; - const float desired_rate = NominalFrameRate * (static_cast(speed_limit) / 100.0f); - - if (desired_rate < 20.0f) { - return 0.0f; - } - - if (m_last_frame_rate_hint > 0.0f && m_smoothed_present_rate > 0.0f) { - const float observed_rate = m_smoothed_present_rate; - switch (static_cast(m_last_frame_rate_hint)) { - case 30: - if (observed_rate < 42.0f) { - return 30.0f; - } - break; - case 45: - if (observed_rate >= 35.0f && observed_rate < 54.0f) { - return 45.0f; - } - break; - case 60: - if (observed_rate >= 48.0f && observed_rate < 75.0f) { - return 60.0f; - } - break; - case 90: - if (observed_rate >= 74.0f && observed_rate < 105.0f) { - return 90.0f; - } - break; - case 120: - if (observed_rate >= 100.0f) { - return 120.0f; - } - break; - default: - break; - } - } - - const float observed_hint = QuantizeFrameRateHint(m_smoothed_present_rate); - if (observed_hint > 0.0f) { - return observed_hint; - } - - return QuantizeFrameRateHint(desired_rate); + const float speed_limited_rate = + NominalFrameRate * (static_cast(std::min(speed_limit, 100)) / 100.0f); + return QuantizeFrameRateHint(speed_limited_rate); } void EmuWindow_Android::UpdateFrameRateHint() { From 5bce1993e1e7f1af5295e2230e98f2a4690ab994 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 04:17:58 -0400 Subject: [PATCH 13/16] [android] Linked Frame Time Cadence to Quantized Frames --- .../src/main/jni/emu_window/emu_window.cpp | 25 +++++++++++++++++++ .../app/src/main/jni/emu_window/emu_window.h | 1 + 2 files changed, 26 insertions(+) diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 8c2296be48..5348aa7d8c 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -116,8 +116,29 @@ float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) { return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : best_rate; } +float EmuWindow_Android::GetFrameTimeVerifiedHint() const { + if (!EmulationSession::GetInstance().IsRunning()) { + return 0.0f; + } + + const double frame_time_scale = + EmulationSession::GetInstance().System().GetPerfStats().GetLastFrameTimeScale(); + if (!std::isfinite(frame_time_scale) || frame_time_scale <= 0.0) { + return 0.0f; + } + + const float verified_rate = + std::clamp(60.0f / static_cast(frame_time_scale), 0.0f, 240.0f); + const float verified_hint = QuantizeFrameRateHint(verified_rate); + + // Frame-time verification is most useful to separate stable 30/60 FPS content. + return verified_hint <= 60.0f ? verified_hint : 0.0f; +} + float EmuWindow_Android::GetFrameRateHint() const { const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f); + const float frame_time_verified_hint = GetFrameTimeVerifiedHint(); + if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) { const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f); if (std::fabs(observed_rate - m_last_frame_rate_hint) <= tolerance) { @@ -125,6 +146,10 @@ float EmuWindow_Android::GetFrameRateHint() const { } } + if (frame_time_verified_hint > 0.0f) { + return frame_time_verified_hint; + } + const float observed_hint = QuantizeFrameRateHint(observed_rate); if (observed_hint > 0.0f) { return observed_hint; diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index 3d0f74a481..f74a0dd8d8 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -60,6 +60,7 @@ private: void UpdateFrameRateHint(); void UpdateObservedFrameRate(); [[nodiscard]] float GetFrameRateHint() const; + [[nodiscard]] float GetFrameTimeVerifiedHint() const; [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); float m_window_width{}; From c42c67ab85c6bb8eaed364fd0dfbdb7d645f9874 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 04:31:10 -0400 Subject: [PATCH 14/16] [android] Refined cadence for low frame rate; chrono + duration = clamp/ ms response extended --- .../src/main/jni/emu_window/emu_window.cpp | 59 +++++++++++-------- .../app/src/main/jni/emu_window/emu_window.h | 1 + 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 5348aa7d8c..f872875f99 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -34,6 +34,7 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { m_pending_frame_rate_hint_votes = 0; m_smoothed_present_rate = 0.0f; m_last_frame_display_time = {}; + m_pending_frame_rate_since = {}; return; } @@ -81,7 +82,7 @@ void EmuWindow_Android::UpdateObservedFrameRate() { const float seconds = frame_time.count(); if (seconds > 0.0f) { const float instantaneous_rate = 1.0f / seconds; - if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 10.0f && + if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 1.0f && instantaneous_rate <= 240.0f) { constexpr float SmoothingFactor = 0.15f; if (m_smoothed_present_rate <= 0.0f) { @@ -97,23 +98,14 @@ void EmuWindow_Android::UpdateObservedFrameRate() { } float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) { - if (!std::isfinite(frame_rate) || frame_rate < 20.0f) { + if (!std::isfinite(frame_rate) || frame_rate <= 0.0f) { return 0.0f; } - frame_rate = std::clamp(frame_rate, 20.0f, 240.0f); + frame_rate = std::clamp(frame_rate, 1.0f, 240.0f); - constexpr std::array CandidateRates{24.0f, 30.0f, 36.0f, 40.0f, 45.0f, 48.0f, - 60.0f, 72.0f, 80.0f, 90.0f, 96.0f, 120.0f, - 144.0f, 165.0f, 180.0f, 200.0f, 240.0f}; - const auto best = std::min_element(CandidateRates.begin(), CandidateRates.end(), - [frame_rate](float lhs, float rhs) { - return std::fabs(frame_rate - lhs) < - std::fabs(frame_rate - rhs); - }); - const float best_rate = *best; - const float tolerance = std::max(best_rate * 0.12f, 4.0f); - return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : best_rate; + constexpr float Step = 0.5f; + return std::round(frame_rate / Step) * Step; } float EmuWindow_Android::GetFrameTimeVerifiedHint() const { @@ -129,10 +121,7 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const { const float verified_rate = std::clamp(60.0f / static_cast(frame_time_scale), 0.0f, 240.0f); - const float verified_hint = QuantizeFrameRateHint(verified_rate); - - // Frame-time verification is most useful to separate stable 30/60 FPS content. - return verified_hint <= 60.0f ? verified_hint : 0.0f; + return QuantizeFrameRateHint(verified_rate); } float EmuWindow_Android::GetFrameRateHint() const { @@ -146,15 +135,21 @@ float EmuWindow_Android::GetFrameRateHint() const { } } - if (frame_time_verified_hint > 0.0f) { - return frame_time_verified_hint; - } - const float observed_hint = QuantizeFrameRateHint(observed_rate); if (observed_hint > 0.0f) { + if (frame_time_verified_hint > 0.0f) { + const float tolerance = std::max(observed_hint * 0.20f, 3.0f); + if (std::fabs(observed_hint - frame_time_verified_hint) <= tolerance) { + return QuantizeFrameRateHint((observed_hint + frame_time_verified_hint) * 0.5f); + } + } return observed_hint; } + if (frame_time_verified_hint > 0.0f) { + return frame_time_verified_hint; + } + constexpr float NominalFrameRate = 60.0f; if (!Settings::values.use_speed_limit.GetValue()) { return NominalFrameRate; @@ -176,28 +171,43 @@ void EmuWindow_Android::UpdateFrameRateHint() { return; } + const auto now = Clock::now(); const float frame_rate_hint = GetFrameRateHint(); if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) { m_pending_frame_rate_hint = frame_rate_hint; m_pending_frame_rate_hint_votes = 0; + m_pending_frame_rate_since = {}; return; } if (frame_rate_hint == 0.0f) { m_pending_frame_rate_hint = frame_rate_hint; m_pending_frame_rate_hint_votes = 0; + m_pending_frame_rate_since = now; } else if (m_last_frame_rate_hint >= 0.0f) { if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) { m_pending_frame_rate_hint = frame_rate_hint; m_pending_frame_rate_hint_votes = 1; + m_pending_frame_rate_since = now; return; } ++m_pending_frame_rate_hint_votes; - constexpr std::uint32_t StableVoteThreshold = 12; - if (m_pending_frame_rate_hint_votes < StableVoteThreshold) { + if (m_pending_frame_rate_since.time_since_epoch().count() == 0) { + m_pending_frame_rate_since = now; + } + + const auto stable_for = now - m_pending_frame_rate_since; + const float reference_rate = std::max(frame_rate_hint, 1.0f); + const auto stable_duration = std::chrono::duration_cast( + std::chrono::duration(std::clamp(3.0f / reference_rate, 0.15f, 0.40f))); + constexpr std::uint32_t MinStableVotes = 3; + + if (m_pending_frame_rate_hint_votes < MinStableVotes || stable_for < stable_duration) { return; } + } else { + m_pending_frame_rate_since = now; } using SetFrameRateWithChangeStrategyFn = @@ -222,6 +232,7 @@ void EmuWindow_Android::UpdateFrameRateHint() { m_last_frame_rate_hint = frame_rate_hint; m_pending_frame_rate_hint = frame_rate_hint; m_pending_frame_rate_hint_votes = 0; + m_pending_frame_rate_since = {}; } EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index f74a0dd8d8..b73e8b9b4d 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -73,5 +73,6 @@ private: float m_pending_frame_rate_hint = -1.0f; float m_smoothed_present_rate = 0.0f; Clock::time_point m_last_frame_display_time{}; + Clock::time_point m_pending_frame_rate_since{}; std::uint32_t m_pending_frame_rate_hint_votes = 0; }; From 2d4c4600113b9cec6efb374cd7500e6a255d3501 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 04:38:40 -0400 Subject: [PATCH 15/16] [debug] Instrumentalization for EDS related pipeline worker failure. --- .../renderer_vulkan/vk_pipeline_cache.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2aff10e15b..49d96e5223 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -751,6 +751,19 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key, std::move(modules), infos); +} catch (const vk::Exception& exception) { + const auto hash = key.Hash(); + LOG_ERROR( + Render_Vulkan, + "Failed to create graphics pipeline 0x{:016x}: {} (result={}, eds={}, eds2={}, " + "eds2_logic_op={}, topology={}, provoking_last={}, xfb={}, conservative={})", + hash, exception.what(), static_cast(exception.GetResult()), + key.state.extended_dynamic_state != 0, key.state.extended_dynamic_state_2 != 0, + key.state.extended_dynamic_state_2_logic_op != 0, static_cast(key.state.topology), + key.state.provoking_vertex_last != 0, key.state.xfb_enabled != 0, + key.state.conservative_raster_enable != 0); + return nullptr; + } catch (const Shader::Exception& exception) { auto hash = key.Hash(); size_t env_index{0}; From 842acf2c8675b9e3f0028b15e33388334a4b901a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 04:46:19 -0400 Subject: [PATCH 16/16] fix build --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 49d96e5223..c6e6e9286f 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -759,7 +759,7 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( "eds2_logic_op={}, topology={}, provoking_last={}, xfb={}, conservative={})", hash, exception.what(), static_cast(exception.GetResult()), key.state.extended_dynamic_state != 0, key.state.extended_dynamic_state_2 != 0, - key.state.extended_dynamic_state_2_logic_op != 0, static_cast(key.state.topology), + key.state.extended_dynamic_state_2_logic_op != 0, static_cast(key.state.topology.Value()), key.state.provoking_vertex_last != 0, key.state.xfb_enabled != 0, key.state.conservative_raster_enable != 0); return nullptr;