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 73a977bb1f..cabea73353 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,6 +647,15 @@ 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 6d769b2e15..c43de4d5c7 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,6 +267,7 @@ 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/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 440e7ce399..db4cc0f60e 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,6 +94,8 @@ 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 f872875f99..4e90cad570 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,15 +6,8 @@ #include -#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" @@ -29,12 +22,6 @@ 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; - 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 = {}; - m_pending_frame_rate_since = {}; return; } @@ -45,7 +32,6 @@ 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) { @@ -65,9 +51,6 @@ void EmuWindow_Android::OnTouchReleased(int id) { } void EmuWindow_Android::OnFrameDisplayed() { - UpdateObservedFrameRate(); - UpdateFrameRateHint(); - if (!m_first_frame) { Common::Android::RunJNIOnFiber( [&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); }); @@ -75,166 +58,6 @@ 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 >= 1.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 <= 0.0f) { - return 0.0f; - } - - frame_rate = std::clamp(frame_rate, 1.0f, 240.0f); - - constexpr float Step = 0.5f; - return std::round(frame_rate / Step) * Step; -} - -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); - return QuantizeFrameRateHint(verified_rate); -} - -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) { - return m_last_frame_rate_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; - } - - const u16 speed_limit = Settings::SpeedLimit(); - if (speed_limit == 0) { - return 0.0f; - } - - const float speed_limited_rate = - NominalFrameRate * (static_cast(std::min(speed_limit, 100)) / 100.0f); - return QuantizeFrameRateHint(speed_limited_rate); -} - -void EmuWindow_Android::UpdateFrameRateHint() { - auto* const surface = reinterpret_cast(window_info.render_surface); - if (!surface) { - 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; - 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 = - 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; - 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, 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 b73e8b9b4d..d7b5fc6dac 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,13 +1,8 @@ -// 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 @@ -55,24 +50,10 @@ public: }; private: - using Clock = std::chrono::steady_clock; - - void UpdateFrameRateHint(); - void UpdateObservedFrameRate(); - [[nodiscard]] float GetFrameRateHint() const; - [[nodiscard]] float GetFrameTimeVerifiedHint() const; - [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); - 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; - 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; }; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 3fd45f8695..97124c0bfa 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -11,15 +11,159 @@ #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" #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 { 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; @@ -503,12 +647,14 @@ 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); 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); @@ -516,6 +662,12 @@ 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(); @@ -535,6 +687,11 @@ 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 6851860edf..1957c26df9 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -4,14 +4,57 @@ // 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 957fe6e15b..7b422a4a41 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -474,7 +474,44 @@ 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}, texture_rescaling_index{bindings.texture_scaling_index}, + 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}, 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 396022eddf..21151bab38 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -216,6 +216,8 @@ 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 7be51c2d66..1836a18bd3 100644 --- a/src/shader_recompiler/frontend/ir/program.h +++ b/src/shader_recompiler/frontend/ir/program.h @@ -23,6 +23,7 @@ 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 f156192c13..6cca023330 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -5,10 +5,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#include #include #include +#include "common/logging/log.h" #include "common/settings.h" #include "shader_recompiler/exception.h" #include "shader_recompiler/frontend/ir/basic_block.h" @@ -22,6 +25,214 @@ 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) { @@ -247,6 +458,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& inst_pool, ObjectPool 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.Value()), - 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}; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index dba18a91cb..710bfddc74 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -168,11 +168,6 @@ 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 47948ddc64..81cd4d9631 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -94,21 +94,6 @@ 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); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 46b98f6cc1..cd8f948d8b 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -170,9 +170,6 @@ 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]); @@ -190,7 +187,6 @@ bool Swapchain::AcquireNextImage() { scheduler.Wait(resource_ticks[image_index], 120.0); break; } -#endif resource_ticks[image_index] = scheduler.CurrentTick(); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 9131cdbfe2..f2fe444e05 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -502,7 +502,6 @@ 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;