diff --git a/src/core/gte.cpp b/src/core/gte.cpp index 6b0dcb1ff..9ff322dff 100644 --- a/src/core/gte.cpp +++ b/src/core/gte.cpp @@ -5,17 +5,26 @@ #include "cpu_core.h" #include "cpu_core_private.h" #include "cpu_pgxp.h" +#include "host.h" #include "settings.h" #include "util/state_wrapper.h" #include "common/assert.h" #include "common/bitutils.h" +#include "common/gsvector.h" +#include "common/timer.h" + +#include "imgui.h" #include #include +#include +#include #include +LOG_CHANNEL(Host); + namespace GTE { static constexpr s64 MAC0_MIN_VALUE = -(INT64_C(1) << 31); @@ -27,14 +36,42 @@ static constexpr s32 IR0_MAX_VALUE = 0x1000; static constexpr s32 IR123_MIN_VALUE = -(INT64_C(1) << 15); static constexpr s32 IR123_MAX_VALUE = (INT64_C(1) << 15) - 1; +static constexpr float FREECAM_MIN_TRANSLATION = -40000.0f; +static constexpr float FREECAM_MAX_TRANSLATION = 40000.0f; +static constexpr float FREECAM_MIN_ROTATION = -360.0f; +static constexpr float FREECAM_MAX_ROTATION = 360.0f; +static constexpr float FREECAM_DEFAULT_MOVE_SPEED = 4096.0f; +static constexpr float FREECAM_MAX_MOVE_SPEED = 65536.0f; +static constexpr float FREECAM_DEFAULT_TURN_SPEED = 30.0f; +static constexpr float FREECAM_MAX_TURN_SPEED = 360.0f; + namespace { + struct Config { DisplayAspectRatio aspect_ratio = DisplayAspectRatio::R4_3; u32 custom_aspect_ratio_numerator; u32 custom_aspect_ratio_denominator; float custom_aspect_ratio_f; + + ////////////////////////////////////////////////////////////////////////// + + Timer::Value freecam_update_time = 0; + std::atomic_bool freecam_transform_changed{false}; + bool freecam_enabled = false; + bool freecam_active = false; + + float freecam_move_speed = FREECAM_DEFAULT_MOVE_SPEED; + float freecam_turn_speed = FREECAM_DEFAULT_TURN_SPEED; + GSVector4 freecam_move = GSVector4::cxpr(0.0f); + GSVector4 freecam_turn = GSVector4::cxpr(0.0f); + + GSVector4 freecam_rotation = GSVector4::cxpr(0.0f); + GSVector4 freecam_translation = GSVector4::cxpr(0.0f); + + ALIGN_TO_CACHE_LINE GSMatrix4x4 freecam_matrix = GSMatrix4x4::Identity(); }; + } // namespace ALIGN_TO_CACHE_LINE static Config s_config; @@ -172,6 +209,7 @@ static void PushSZ(s32 value); static void PushRGBFromMAC(); static u32 UNRDivide(u32 lhs, u32 rhs); +static void ApplyFreecam(s64& x, s64& y, s64& z); static void MulMatVec(const s16* M_, const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); static void MulMatVec(const s16* M_, const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); static void MulMatVecBuggy(const s16* M_, const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); @@ -218,6 +256,8 @@ void GTE::Initialize() void GTE::Reset() { std::memset(®S, 0, sizeof(REGS)); + SetFreecamEnabled(false); + ResetFreecam(); } bool GTE::DoState(StateWrapper& sw) @@ -644,9 +684,11 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last) // IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12) // IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12) // IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12) - const s64 x = dot3(0); - const s64 y = dot3(1); - const s64 z = dot3(2); + s64 x = dot3(0); + s64 y = dot3(1); + s64 z = dot3(2); + if (s_config.freecam_active) + ApplyFreecam(x, y, z); TruncateAndSetMAC<1>(x, shift); TruncateAndSetMAC<2>(y, shift); TruncateAndSetMAC<3>(z, shift); @@ -1373,3 +1415,269 @@ GTE::InstructionImpl GTE::GetInstructionImpl(u32 inst_bits, TickCount* ticks) Panic("Missing handler"); } } + +bool GTE::IsFreecamEnabled() +{ + return s_config.freecam_enabled; +} + +void GTE::SetFreecamEnabled(bool enabled) +{ + if (s_config.freecam_enabled == enabled) + return; + + s_config.freecam_enabled = enabled; + if (enabled) + { + s_config.freecam_transform_changed.store(true, std::memory_order_release); + s_config.freecam_update_time = Timer::GetCurrentValue(); + } +} + +void GTE::SetFreecamMoveAxis(u32 axis, float x) +{ + DebugAssert(axis < 3); + s_config.freecam_move.F32[axis] = x; + SetFreecamEnabled(true); +} + +void GTE::SetFreecamRotateAxis(u32 axis, float x) +{ + DebugAssert(axis < 3); + s_config.freecam_turn.F32[axis] = x; + SetFreecamEnabled(true); +} + +void GTE::UpdateFreecam(u64 current_time) +{ + if (!s_config.freecam_enabled) + { + s_config.freecam_active = false; + return; + } + + const float dt = std::clamp( + static_cast(Timer::ConvertValueToSeconds(current_time - s_config.freecam_update_time)), 0.0f, 1.0f); + s_config.freecam_update_time = current_time; + + bool changed = true; + s_config.freecam_transform_changed.compare_exchange_strong(changed, false, std::memory_order_acq_rel); + + if (!(s_config.freecam_move == GSVector4::zero()).alltrue()) + { + s_config.freecam_translation += s_config.freecam_move * GSVector4(s_config.freecam_move_speed * dt); + changed = true; + } + + if (!(s_config.freecam_turn == GSVector4::zero()).alltrue()) + { + s_config.freecam_rotation += s_config.freecam_turn * GSVector4(s_config.freecam_turn_speed * + static_cast(std::numbers::pi / 180.0) * dt); + + // wrap around -360 degrees/360 degrees + constexpr GSVector4 min_rot = GSVector4::cxpr(static_cast(std::numbers::pi * -2.0)); + constexpr GSVector4 max_rot = GSVector4::cxpr(static_cast(std::numbers::pi * 2.0)); + s_config.freecam_rotation = + s_config.freecam_rotation.blend32(s_config.freecam_rotation + max_rot, (s_config.freecam_rotation < min_rot)); + s_config.freecam_rotation = + s_config.freecam_rotation.blend32(s_config.freecam_rotation + min_rot, (s_config.freecam_rotation > max_rot)); + + changed = true; + } + + if (!changed) + return; + + bool any_xform = false; + s_config.freecam_matrix = GSMatrix4x4::Identity(); + + // translate than rotate, since the camera is rotating around a point + // remember, matrix transformation happens in the opposite of the multiplication order + + if (s_config.freecam_translation.x != 0.0f || s_config.freecam_translation.y != 0.0f || + s_config.freecam_translation.z != 0.0f) + { + s_config.freecam_matrix = GSMatrix4x4::Translation(s_config.freecam_translation.x, s_config.freecam_translation.y, + s_config.freecam_translation.z); + any_xform = true; + } + + if (s_config.freecam_rotation.z != 0.0f) + { + s_config.freecam_matrix *= GSMatrix4x4::RotationZ(s_config.freecam_rotation.z); + any_xform = true; + } + + if (s_config.freecam_rotation.y != 0.0f) + { + s_config.freecam_matrix *= GSMatrix4x4::RotationY(s_config.freecam_rotation.y); + any_xform = true; + } + + if (s_config.freecam_rotation.x != 0.0f) + { + s_config.freecam_matrix *= GSMatrix4x4::RotationX(s_config.freecam_rotation.x); + any_xform = true; + } + + s_config.freecam_active = any_xform; +} + +void GTE::ResetFreecam() +{ + s_config.freecam_active = false; + s_config.freecam_rotation = GSVector4::zero(); + s_config.freecam_translation = GSVector4::zero(); + s_config.freecam_transform_changed.store(false, std::memory_order_release); +} + +void GTE::ApplyFreecam(s64& x, s64& y, s64& z) +{ + constexpr double scale = 1 << 12; + + GSVector4 xyz(static_cast(static_cast(x) / scale), static_cast(static_cast(y) / scale), + static_cast(static_cast(z) / scale), 1.0f); + + xyz = s_config.freecam_matrix * xyz; + + x = static_cast(static_cast(xyz.x) * scale); + y = static_cast(static_cast(xyz.y) * scale); + z = static_cast(static_cast(xyz.z) * scale); +} + +void GTE::DrawFreecamWindow(float scale) +{ + const ImGuiStyle& style = ImGui::GetStyle(); + + bool freecam_enabled = s_config.freecam_enabled; + bool enabled_changed = false; + + const float label_width = 140.0f * scale; + const float item_width = 350.0f * scale; + const float padding_height = 5.0f * scale; + + if (ImGui::CollapsingHeader("Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + const float third_width = 50.0f * scale; + const float second_width = item_width - third_width; + + enabled_changed = ImGui::Checkbox("Enable Freecam", &freecam_enabled); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + padding_height); + + ImGui::Columns(3, "Settings", false); + ImGui::SetColumnWidth(0, label_width); + ImGui::SetColumnWidth(1, second_width); + ImGui::SetColumnWidth(2, third_width); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Movement Speed:"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(second_width); + ImGui::DragFloat("##MovementSpeed", &s_config.freecam_move_speed, 1.0f, 0.0f, FREECAM_MAX_MOVE_SPEED); + ImGui::NextColumn(); + if (ImGui::Button("Reset##ResetMovementSpeed")) + s_config.freecam_move_speed = FREECAM_DEFAULT_MOVE_SPEED; + ImGui::NextColumn(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Turning Speed:"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(second_width); + ImGui::DragFloat("##TurnSpeed", &s_config.freecam_turn_speed, 1.0f, 0.0f, FREECAM_MAX_TURN_SPEED); + ImGui::NextColumn(); + if (ImGui::Button("Reset##ResetTurnSpeed")) + s_config.freecam_turn_speed = FREECAM_DEFAULT_TURN_SPEED; + ImGui::NextColumn(); + + ImGui::Columns(1); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + padding_height); + } + + bool changed = false; + + if (ImGui::CollapsingHeader("Rotation", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Columns(2, "Rotation", false); + ImGui::SetColumnWidth(0, label_width); + ImGui::SetColumnWidth(1, item_width); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("X Rotation (Pitch):"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::SliderAngle("##XRot", &s_config.freecam_rotation.x, FREECAM_MIN_ROTATION, FREECAM_MAX_ROTATION); + ImGui::NextColumn(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Y Rotation (Yaw):"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::SliderAngle("##YRot", &s_config.freecam_rotation.y, FREECAM_MIN_ROTATION, FREECAM_MAX_ROTATION); + ImGui::NextColumn(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Z Rotation (Roll):"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::SliderAngle("##ZRot", &s_config.freecam_rotation.z, FREECAM_MIN_ROTATION, FREECAM_MAX_ROTATION); + ImGui::NextColumn(); + + ImGui::Columns(1); + + if (ImGui::Button("Reset##ResetRotation")) + { + s_config.freecam_rotation = GSVector4::zero(); + changed = true; + } + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + padding_height); + } + + if (ImGui::CollapsingHeader("Translation", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Columns(2, "Translation", false); + ImGui::SetColumnWidth(0, label_width); + ImGui::SetColumnWidth(1, item_width); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("X Offset:"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::DragFloat("##XOffset", &s_config.freecam_translation.x, 1.0f, FREECAM_MIN_TRANSLATION, + FREECAM_MAX_TRANSLATION, "%.1f", ImGuiSliderFlags_None); + ImGui::NextColumn(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Y Offset:"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::DragFloat("##YOffset", &s_config.freecam_translation.y, 1.0f, FREECAM_MIN_TRANSLATION, + FREECAM_MAX_TRANSLATION, "%.1f", ImGuiSliderFlags_None); + ImGui::NextColumn(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.ItemInnerSpacing.y); + ImGui::TextUnformatted("Z Offset:"); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(item_width); + changed |= ImGui::DragFloat("##ZOffset", &s_config.freecam_translation.z, 1.0f, FREECAM_MIN_TRANSLATION, + FREECAM_MAX_TRANSLATION, "%.1f", ImGuiSliderFlags_None); + ImGui::NextColumn(); + + ImGui::Columns(1); + + if (ImGui::Button("Reset##ResetTranslation")) + { + s_config.freecam_translation = GSVector4::zero(); + changed = true; + } + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + padding_height); + } + + if (enabled_changed || (!freecam_enabled && changed)) + Host::RunOnCPUThread([enabled = freecam_enabled || changed]() { SetFreecamEnabled(enabled); }); + + if (changed) + s_config.freecam_transform_changed.store(true, std::memory_order_release); +} diff --git a/src/core/gte.h b/src/core/gte.h index eeb702251..6679005c8 100644 --- a/src/core/gte.h +++ b/src/core/gte.h @@ -27,4 +27,13 @@ void ExecuteInstruction(u32 inst_bits); using InstructionImpl = void (*)(Instruction); InstructionImpl GetInstructionImpl(u32 inst_bits, TickCount* ticks); +void DrawFreecamWindow(float scale); + +bool IsFreecamEnabled(); +void SetFreecamEnabled(bool enabled); +void SetFreecamMoveAxis(u32 axis, float x); +void SetFreecamRotateAxis(u32 axis, float x); +void UpdateFreecam(u64 current_time); +void ResetFreecam(); + } // namespace GTE diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index 3f4377e59..f0883d08f 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -9,6 +9,7 @@ #include "gpu.h" #include "gpu_hw_texture_cache.h" #include "gpu_thread.h" +#include "gte.h" #include "host.h" #include "imgui_overlays.h" #include "settings.h" @@ -520,6 +521,101 @@ DEFINE_HOTKEY("RotateCounterclockwise", TRANSLATE_NOOP("Hotkeys", "Graphics"), } }) +DEFINE_HOTKEY("FreecamToggle", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Toggle"), + [](s32 pressed) { + if (!pressed && !Achievements::IsHardcoreModeActive()) + GTE::SetFreecamEnabled(!GTE::IsFreecamEnabled()); + }) +DEFINE_HOTKEY("FreecamReset", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Reset"), + [](s32 pressed) { + if (!pressed && !Achievements::IsHardcoreModeActive()) + GTE::ResetFreecam(); + }) +DEFINE_HOTKEY("FreecamMoveLeft", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Move Left"), + [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(0, std::max(static_cast(pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamMoveRight", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Move Right"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(0, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamMoveUp", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Move Up"), + [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(1, std::max(static_cast(pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamMoveDown", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Move Down"), + [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(1, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamMoveForward", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Move Forward"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(2, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamMoveBackward", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Move Backward"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamMoveAxis(2, std::max(static_cast(pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRotateLeft", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Rotate Left"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(1, std::max(static_cast(pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRotateRight", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Rotate Right"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(1, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRotateForward", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Rotate Forward"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(0, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRotateBackward", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Rotate Backward"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(0, std::max(static_cast(pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRollLeft", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Freecam Roll Left"), + [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(2, std::min(static_cast(-pressed), 0.0f)); + }) +DEFINE_HOTKEY("FreecamRollRight", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Freecam Roll Right"), [](s32 pressed) { + if (Achievements::IsHardcoreModeActive()) + return; + + GTE::SetFreecamRotateAxis(2, std::max(static_cast(pressed), 0.0f)); + }) + DEFINE_HOTKEY("AudioMute", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"), [](s32 pressed) { if (!pressed && System::IsValid()) diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 160a534ca..7b3677be4 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -11,6 +11,7 @@ #include "gpu.h" #include "gpu_backend.h" #include "gpu_thread.h" +#include "gte.h" #include "host.h" #include "mdec.h" #include "performance_counters.h" @@ -72,6 +73,7 @@ struct DebugWindowInfo } // namespace static void FormatProcessorStat(SmallStringBase& text, double usage, double time); +static void SetStatusIndicatorIcons(SmallStringBase& text, bool paused); static void DrawPerformanceOverlay(const GPUBackend* gpu, float& position_y, float scale, float margin, float spacing); static void DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing); static void DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing); @@ -80,9 +82,10 @@ static void DrawInputsOverlay(); #ifndef __ANDROID__ -static constexpr size_t NUM_DEBUG_WINDOWS = 6; +static constexpr size_t NUM_DEBUG_WINDOWS = 7; static constexpr const char* DEBUG_WINDOW_CONFIG_SECTION = "DebugWindows"; static constexpr const std::array s_debug_window_info = {{ + {"Freecam", "Free Camera", ":icons/applications-system.png", >E::DrawFreecamWindow, 500, 400}, {"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 800, 915}, {"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 800, 540}, {"GPU", "GPU State", ":icons/applications-system.png", [](float sc) { g_gpu.DrawDebugStateWindow(sc); }, 450, 550}, @@ -250,6 +253,27 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub text.append_format("{:.1f}% ({:.2f}ms)", usage, time); } +void ImGuiManager::SetStatusIndicatorIcons(SmallStringBase& text, bool paused) +{ + text.clear(); + if (GTE::IsFreecamEnabled()) + text.append(ICON_EMOJI_MAGNIFIYING_GLASS_TILTED_LEFT " "); + + if (paused) + { + text.append(ICON_EMOJI_PAUSE); + } + else + { + const bool rewinding = System::IsRewinding(); + if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled()) + text.append(rewinding ? ICON_EMOJI_FAST_REVERSE : ICON_EMOJI_FAST_FORWARD); + } + + if (!text.empty() && text.back() == ' ') + text.pop_back(); +} + void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position_y, float scale, float margin, float spacing) { @@ -282,8 +306,7 @@ void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position position_y += text_size.y + spacing; \ } while (0) - const System::State state = System::GetState(); - if (state == System::State::Running) + if (!GPUThread::IsSystemPaused()) { const float speed = PerformanceCounters::GetEmulationSpeed(); if (g_gpu_settings.display_show_fps) @@ -415,18 +438,14 @@ void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position if (g_gpu_settings.display_show_status_indicators) { - const bool rewinding = System::IsRewinding(); - if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled()) - { - text.assign(rewinding ? ICON_EMOJI_FAST_REVERSE : ICON_EMOJI_FAST_FORWARD); + SetStatusIndicatorIcons(text, false); + if (!text.empty()) DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255)); - } } } - else if (g_gpu_settings.display_show_status_indicators && state == System::State::Paused && - !FullscreenUI::HasActiveWindow()) + else if (g_gpu_settings.display_show_status_indicators && !FullscreenUI::HasActiveWindow()) { - text.assign(ICON_EMOJI_PAUSE); + SetStatusIndicatorIcons(text, true); DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255)); } @@ -457,7 +476,8 @@ void ImGuiManager::DrawEnhancementsOverlay(const GPUBackend* gpu) text.append_format(" IR={}x", g_gpu_settings.gpu_resolution_scale); if (g_gpu_settings.gpu_multisamples != 1) { - text.append_format(" {}x{}", g_gpu_settings.gpu_multisamples, g_gpu_settings.gpu_per_sample_shading ? "SSAA" : "MSAA"); + text.append_format(" {}x{}", g_gpu_settings.gpu_multisamples, + g_gpu_settings.gpu_per_sample_shading ? "SSAA" : "MSAA"); } if (g_gpu_settings.gpu_true_color) text.append(" TrueCol"); diff --git a/src/core/system.cpp b/src/core/system.cpp index 270f6e89d..5a3fd0355 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2104,6 +2104,9 @@ void System::FrameDone() SaveMemoryState(AllocateMemoryState()); } + Timer::Value current_time = Timer::GetCurrentValue(); + GTE::UpdateFreecam(current_time); + // Frame step after runahead, otherwise the pause takes precedence and the replay never happens. if (s_state.frame_step_request) { @@ -2111,8 +2114,6 @@ void System::FrameDone() PauseSystem(true); } - Timer::Value current_time = Timer::GetCurrentValue(); - // pre-frame sleep accounting (input lag reduction) const Timer::Value pre_frame_sleep_until = s_state.next_frame_time + s_state.pre_frame_sleep_time; s_state.last_active_frame_time = current_time - s_state.frame_start_time; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index d42cfc9e5..bb46b26c9 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2134,6 +2134,7 @@ void MainWindow::connectSignals() g_emu_thread->dumpSPURAM(filename); }); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowVRAM, "Debug", "ShowVRAM", false); + SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionFreeCamera, "DebugWindows", "Freecam", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowGPUState, "DebugWindows", "GPU", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowCDROMState, "DebugWindows", "CDROM", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowSPUState, "DebugWindows", "SPU", false); diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 6455e859c..42b5997ed 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -231,6 +231,7 @@ + @@ -970,6 +971,14 @@ ISO Browser + + + true + + + Free Camera + +