GTE: Add 'Free Camera' feature

This commit is contained in:
Stenzek 2025-01-01 21:28:34 +10:00
parent 22202f1607
commit dcd439e7d8
No known key found for this signature in database
7 changed files with 461 additions and 17 deletions

View File

@ -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 <algorithm>
#include <array>
#include <atomic>
#include <numbers>
#include <numeric>
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(&REGS, 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<float>(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<float>(std::numbers::pi / 180.0) * dt);
// wrap around -360 degrees/360 degrees
constexpr GSVector4 min_rot = GSVector4::cxpr(static_cast<float>(std::numbers::pi * -2.0));
constexpr GSVector4 max_rot = GSVector4::cxpr(static_cast<float>(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<float>(static_cast<double>(x) / scale), static_cast<float>(static_cast<double>(y) / scale),
static_cast<float>(static_cast<double>(z) / scale), 1.0f);
xyz = s_config.freecam_matrix * xyz;
x = static_cast<s64>(static_cast<double>(xyz.x) * scale);
y = static_cast<s64>(static_cast<double>(xyz.y) * scale);
z = static_cast<s64>(static_cast<double>(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);
}

View File

@ -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

View File

@ -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<float>(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<float>(-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<float>(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<float>(-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<float>(-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<float>(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<float>(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<float>(-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<float>(-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<float>(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<float>(-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<float>(pressed), 0.0f));
})
DEFINE_HOTKEY("AudioMute", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"),
[](s32 pressed) {
if (!pressed && System::IsValid())

View File

@ -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<DebugWindowInfo, NUM_DEBUG_WINDOWS> s_debug_window_info = {{
{"Freecam", "Free Camera", ":icons/applications-system.png", &GTE::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");

View File

@ -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;

View File

@ -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);

View File

@ -231,6 +231,7 @@
<addaction name="actionMemoryScanner"/>
<addaction name="actionISOBrowser"/>
<addaction name="separator"/>
<addaction name="actionFreeCamera"/>
<addaction name="actionMediaCapture"/>
<addaction name="actionCaptureGPUFrame"/>
<addaction name="separator"/>
@ -970,6 +971,14 @@
<string>ISO Browser</string>
</property>
</action>
<action name="actionFreeCamera">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Free Camera</string>
</property>
</action>
</widget>
<resources>
<include location="resources/duckstation-qt.qrc"/>