GPU: Move backend work off CPU thread

This commit is contained in:
Stenzek 2023-12-09 01:28:17 +10:00
parent 801ddf7b36
commit 1503a0a23f
No known key found for this signature in database
48 changed files with 5488 additions and 4246 deletions

View File

@ -14,9 +14,9 @@
#define CPU_ARCH_SIMD 1
#define CPU_ARCH_SSE 1
#include <emmintrin.h>
#include <tmmintrin.h>
#include <smmintrin.h>
#include <immintrin.h>
#include <smmintrin.h>
#include <tmmintrin.h>
#if defined(__AVX2__)
#define CPU_ARCH_AVX 1
@ -96,3 +96,40 @@ ALWAYS_INLINE_RELEASE static void MemsetPtrs(T* ptr, T value, u32 count)
for (u32 i = 0; i < remaining_count; i++)
*(dest++) = value;
}
ALWAYS_INLINE static void MultiPause()
{
#if defined(CPU_ARCH_X86) || defined(CPU_ARCH_X64)
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
#elif defined(CPU_ARCH_ARM64) && defined(_MSC_VER)
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
#elif defined(CPU_ARCH_ARM64) || defined(CPU_ARCH_ARM32)
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
#elif defined(CPU_ARCH_RISCV64)
// Probably wrong... pause is optional :/
asm volatile("fence" ::: "memory");
#else
#pragma warning("Missing implementation")
#endif
}

View File

@ -33,6 +33,7 @@
X(GPUShaderCache) \
X(GPUTexture) \
X(GPUTextureCache) \
X(GPUThread) \
X(GPU_HW) \
X(GPU_SW) \
X(GPU_SW_Rasterizer) \

View File

@ -61,10 +61,10 @@ add_library(core
gpu_shadergen.h
gpu_sw.cpp
gpu_sw.h
gpu_sw_backend.cpp
gpu_sw_backend.h
gpu_sw_rasterizer.cpp
gpu_sw_rasterizer.h
gpu_thread.cpp
gpu_thread.h
gpu_types.h
guncon.cpp
guncon.h

View File

@ -9,6 +9,7 @@
#include "bus.h"
#include "cpu_core.h"
#include "fullscreen_ui.h"
#include "gpu_thread.h"
#include "host.h"
#include "system.h"
@ -1144,7 +1145,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// ensure fullscreen UI is ready for notifications
if (display_summary)
FullscreenUI::Initialize();
GPUThread::RunOnThread(&FullscreenUI::Initialize);
char url_buf[URL_BUFFER_SIZE];
if (int err = rc_client_game_get_image_url(info, url_buf, std::size(url_buf)); err == RC_OK)
@ -1199,7 +1200,7 @@ void Achievements::ClearGameHash()
void Achievements::DisplayAchievementSummary()
{
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title;
if (IsHardcoreModeActive())
@ -1224,8 +1225,13 @@ void Achievements::DisplayAchievementSummary()
summary = TRANSLATE_STR("Achievements", "This game has no achievements.");
}
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
std::move(summary), s_game_icon);
GPUThread::RunOnThread([title = std::move(title), summary = std::move(summary)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
std::move(summary), s_game_icon);
});
}
// Technically not going through the resource API, but since we're passing this to something else, we can't.
@ -1235,11 +1241,16 @@ void Achievements::DisplayAchievementSummary()
void Achievements::DisplayHardcoreDeferredMessage()
{
if (g_settings.achievements_hardcore_mode && !s_hardcore_mode && System::IsValid() && FullscreenUI::Initialize())
if (g_settings.achievements_hardcore_mode && !s_hardcore_mode && System::IsValid())
{
ImGuiFullscreen::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
});
}
}
@ -1261,7 +1272,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_game_id);
UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title;
if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL)
@ -1271,9 +1282,15 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state);
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id),
static_cast<float>(g_settings.achievements_notification_duration),
std::move(title), cheevo->description, std::move(badge_path));
GPUThread::RunOnThread([id = cheevo->id, duration = g_settings.achievements_notification_duration,
title = std::move(title), description = std::string(cheevo->description),
badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", id), static_cast<float>(duration),
std::move(title), std::move(description), std::move(badge_path));
});
}
if (g_settings.achievements_sound_effects)
@ -1285,7 +1302,7 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
INFO_LOG("Game {} complete", s_game_id);
UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_game_title);
std::string message = fmt::format(
@ -1294,8 +1311,13 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
s_game_summary.num_unlocked_achievements),
TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_game_summary.points_unlocked));
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), s_game_icon);
GPUThread::RunOnThread([title = std::move(title), message = std::move(message), icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), std::move(icon));
});
}
}
@ -1303,14 +1325,19 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
{
DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
LEADERBOARD_STARTED_NOTIFICATION_TIME, std::move(title), std::move(message),
s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1318,14 +1345,19 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
{
DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
LEADERBOARD_FAILED_NOTIFICATION_TIME, std::move(title), std::move(message),
s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_FAILED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1333,7 +1365,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
{
DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {}{}"),
@ -1349,9 +1381,14 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
}
if (g_settings.achievements_sound_effects)
@ -1363,7 +1400,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"),
@ -1380,9 +1417,15 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1512,26 +1555,30 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server disconnected.");
if (FullscreenUI::Initialize())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
TRANSLATE_STR("Achievements",
"An unlock request could not be completed. We will keep retrying to submit this request."),
Host::OSD_ERROR_DURATION);
}
});
}
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server reconnected.");
if (FullscreenUI::Initialize())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
Host::OSD_INFO_DURATION);
}
});
}
void Achievements::ResetClient()
@ -1609,12 +1656,17 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
// new mode
s_hardcore_mode = enabled;
if (System::IsValid() && (HasActiveGame() || force_display_message) && FullscreenUI::Initialize())
if (System::IsValid() && (HasActiveGame() || force_display_message))
{
ImGuiFullscreen::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
GPUThread::RunOnThread([enabled]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
});
}
rc_client_set_hardcore_enabled(s_client, enabled);
@ -1925,7 +1977,7 @@ void Achievements::ShowLoginNotification()
if (!user)
return;
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string badge_path = GetLoggedInUserBadgePath();
std::string title = user->display_name;
@ -1934,8 +1986,14 @@ void Achievements::ShowLoginNotification()
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
user->score, user->score_softcore, user->num_unread_messages);
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(badge_path));
GPUThread::RunOnThread(
[title = std::move(title), summary = std::move(summary), badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(badge_path));
});
}
}
@ -2035,14 +2093,6 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
}
#endif
if (!FullscreenUI::Initialize())
{
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);
callback(false);
return;
}
auto real_callback = [callback = std::move(callback)](bool res) mutable {
// don't run the callback in the middle of rendering the UI
Host::RunOnCPUThread([callback = std::move(callback), res]() {
@ -2052,13 +2102,25 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
});
};
ImGuiFullscreen::OpenConfirmMessageDialog(
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you "
"want to disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
GPUThread::RunOnThread([trigger = std::string(trigger), real_callback = std::move(real_callback)]() mutable {
if (!FullscreenUI::Initialize())
{
Host::AddOSDMessage(
fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);
real_callback(false);
return;
}
ImGuiFullscreen::OpenConfirmMessageDialog(
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
fmt::format(TRANSLATE_FS("Achievements",
"{0} cannot be performed while hardcore mode is active. Do you "
"want to disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
});
#else
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);

View File

@ -52,8 +52,8 @@
<ClCompile Include="gpu_hw_texture_cache.cpp" />
<ClCompile Include="gpu_shadergen.cpp" />
<ClCompile Include="gpu_sw.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_thread.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" />
@ -133,8 +133,8 @@
<ClInclude Include="gpu_hw_texture_cache.h" />
<ClInclude Include="gpu_shadergen.h" />
<ClInclude Include="gpu_sw.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="gpu_sw_rasterizer.h" />
<ClInclude Include="gpu_thread.h" />
<ClInclude Include="gpu_types.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="cpu_types.h" />

View File

@ -45,7 +45,6 @@
<ClCompile Include="analog_joystick.cpp" />
<ClCompile Include="cpu_recompiler_code_generator_aarch32.cpp" />
<ClCompile Include="gpu_backend.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="multitap.cpp" />
<ClCompile Include="host.cpp" />
<ClCompile Include="game_database.cpp" />
@ -71,6 +70,7 @@
<ClCompile Include="gpu_dump.cpp" />
<ClCompile Include="cdrom_subq_replacement.cpp" />
<ClCompile Include="performance_counters.cpp" />
<ClCompile Include="gpu_thread.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -119,7 +119,6 @@
<ClInclude Include="analog_joystick.h" />
<ClInclude Include="gpu_types.h" />
<ClInclude Include="gpu_backend.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="multitap.h" />
<ClInclude Include="host.h" />
<ClInclude Include="achievements.h" />
@ -149,6 +148,7 @@
<ClInclude Include="cdrom_subq_replacement.h" />
<ClInclude Include="performance_counters.h" />
<ClInclude Include="system_private.h" />
<ClInclude Include="gpu_thread.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

View File

@ -8,6 +8,7 @@
#include "controller.h"
#include "game_list.h"
#include "gpu.h"
#include "gpu_thread.h"
#include "host.h"
#include "settings.h"
#include "system.h"
@ -219,6 +220,7 @@ struct PostProcessingStageInfo
//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////
static void UpdateRunIdleState();
static void PauseForMenuOpen(bool set_pause_menu_open);
static bool AreAnyDialogsOpen();
static void ClosePauseMenu();
@ -600,12 +602,13 @@ bool FullscreenUI::Initialize()
s_about_window_open = false;
s_hotkey_list_cache = InputManager::GetHotkeyList();
if (s_initialized)
Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(true); });
if (!System::IsValid())
SwitchToLanding();
if (!System::IsRunning())
Host::OnIdleStateChanged();
UpdateRunIdleState();
ForceKeyNavEnabled();
return true;
}
@ -629,6 +632,7 @@ bool FullscreenUI::AreAnyDialogsOpen()
void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
@ -636,54 +640,102 @@ void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
// That means we're going to be reading achievement state.
if (old_settings.achievements_enabled && !g_settings.achievements_enabled)
{
if (s_current_main_window == MainWindowType::Achievements || s_current_main_window == MainWindowType::Leaderboards)
ReturnToPreviousWindow();
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (s_current_main_window == MainWindowType::Achievements ||
s_current_main_window == MainWindowType::Leaderboards)
{
ReturnToPreviousWindow();
}
});
}
}
void FullscreenUI::UpdateRunIdleState()
{
const bool new_run_idle = HasActiveWindow();
if (GPUThread::GetRunIdleOnThread() == new_run_idle)
return;
GPUThread::SetRunIdleOnThread(new_run_idle);
Host::RunOnCPUThread([new_run_idle]() { Host::OnFullscreenUIActiveChanged(new_run_idle); });
}
void FullscreenUI::OnSystemStarted()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
s_current_main_window = MainWindowType::None;
QueueResetFocus(FocusResetType::ViewChanged);
}
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
void FullscreenUI::OnSystemPaused()
{
// noop
s_current_main_window = MainWindowType::None;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
});
}
void FullscreenUI::OnSystemResumed()
{
// get rid of pause menu if we unpaused another way
if (s_current_main_window == MainWindowType::PauseMenu)
ClosePauseMenu();
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
// get rid of pause menu if we unpaused another way
if (s_current_main_window == MainWindowType::PauseMenu)
ClosePauseMenu();
UpdateRunIdleState();
});
}
void FullscreenUI::OnSystemDestroyed()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
s_pause_menu_was_open = false;
s_was_paused_on_quick_menu_open = false;
s_current_pause_submenu = PauseSubMenu::None;
SwitchToLanding();
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
s_pause_menu_was_open = false;
s_was_paused_on_quick_menu_open = false;
s_current_pause_submenu = PauseSubMenu::None;
SwitchToLanding();
UpdateRunIdleState();
});
}
void FullscreenUI::OnRunningGameChanged()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
const std::string& path = System::GetDiscPath();
const std::string& serial = System::GetGameSerial();
std::string subtitle;
if (!serial.empty())
s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
else
s_current_game_subtitle = {};
subtitle = {};
GPUThread::RunOnThread([subtitle = std::move(subtitle)]() mutable {
if (!IsInitialized())
return;
s_current_game_subtitle = std::move(subtitle);
});
}
void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open)
@ -700,15 +752,18 @@ void FullscreenUI::OpenPauseMenu()
if (!System::IsValid())
return;
if (!Initialize() || s_current_main_window != MainWindowType::None)
return;
GPUThread::RunOnThread([]() {
if (!Initialize() || s_current_main_window != MainWindowType::None)
return;
PauseForMenuOpen(true);
s_current_main_window = MainWindowType::PauseMenu;
s_current_pause_submenu = PauseSubMenu::None;
QueueResetFocus(FocusResetType::ViewChanged);
ForceKeyNavEnabled();
FixStateIfPaused();
PauseForMenuOpen(true);
s_current_main_window = MainWindowType::PauseMenu;
s_current_pause_submenu = PauseSubMenu::None;
QueueResetFocus(FocusResetType::ViewChanged);
ForceKeyNavEnabled();
UpdateRunIdleState();
FixStateIfPaused();
});
}
void FullscreenUI::OpenCheatsMenu()
@ -722,6 +777,7 @@ void FullscreenUI::OpenCheatsMenu()
s_settings_page = SettingsPage::Cheats;
PauseForMenuOpen(true);
ForceKeyNavEnabled();
UpdateRunIdleState();
FixStateIfPaused();
}
@ -732,31 +788,27 @@ void FullscreenUI::FixStateIfPaused()
// When we're paused, we won't have trickled the key up event for escape yet. Do it now.
ImGui::UpdateInputEvents(false);
Host::OnIdleStateChanged();
Host::RunOnCPUThread([]() {
if (System::IsValid())
{
// Why twice? To clear the "wants keyboard input" flag.
System::InvalidateDisplay();
System::InvalidateDisplay();
}
});
}
void FullscreenUI::ClosePauseMenu()
{
if (!IsInitialized() || !System::IsValid())
if (!System::IsValid())
return;
if (System::GetState() == System::State::Paused && !s_was_paused_on_quick_menu_open)
Host::RunOnCPUThread([]() { System::PauseSystem(false); });
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
s_current_main_window = MainWindowType::None;
s_current_pause_submenu = PauseSubMenu::None;
s_pause_menu_was_open = false;
QueueResetFocus(FocusResetType::ViewChanged);
FixStateIfPaused();
if (System::GetState() == System::State::Paused && !s_was_paused_on_quick_menu_open)
Host::RunOnCPUThread([]() { System::PauseSystem(false); });
s_current_main_window = MainWindowType::None;
s_current_pause_submenu = PauseSubMenu::None;
s_pause_menu_was_open = false;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
FixStateIfPaused();
});
}
void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
@ -787,8 +839,12 @@ void FullscreenUI::Shutdown()
s_current_game_subtitle = {};
DestroyResources();
ImGuiFullscreen::Shutdown();
if (s_initialized)
Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(false); });
s_initialized = false;
s_tried_to_initialize = false;
UpdateRunIdleState();
}
void FullscreenUI::Render()
@ -920,6 +976,7 @@ void FullscreenUI::ReturnToMainWindow()
{
ClosePauseMenu();
s_current_main_window = System::IsValid() ? MainWindowType::None : MainWindowType::Landing;
UpdateRunIdleState();
FixStateIfPaused();
}
@ -958,6 +1015,11 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona
if (System::IsValid())
return;
// Switch to nothing, we'll get called back via OnSystemDestroyed() if startup fails.
s_current_main_window = MainWindowType::None;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
SystemBootParameters params;
params.filename = std::move(path);
params.save_state = std::move(state);
@ -1152,108 +1214,116 @@ void FullscreenUI::DoChangeDiscFromFile()
void FullscreenUI::DoChangeDisc()
{
ImGuiFullscreen::ChoiceDialogOptions options;
Host::RunOnCPUThread([]() {
ImGuiFullscreen::ChoiceDialogOptions options;
if (System::HasMediaSubImages())
{
const u32 current_index = System::GetMediaSubImageIndex();
const u32 count = System::GetMediaSubImageCount();
options.reserve(count + 1);
options.emplace_back(FSUI_STR("From File..."), false);
for (u32 i = 0; i < count; i++)
options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
auto callback = [](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
}
CloseChoiceDialog();
ReturnToPreviousWindow();
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
return;
}
if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty())
{
const auto lock = GameList::GetLock();
auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials);
if (matches.size() > 1)
if (System::HasMediaSubImages())
{
options.reserve(matches.size() + 1);
const u32 current_index = System::GetMediaSubImageIndex();
const u32 count = System::GetMediaSubImageCount();
options.reserve(count + 1);
options.emplace_back(FSUI_STR("From File..."), false);
std::vector<std::string> paths;
paths.reserve(matches.size());
for (u32 i = 0; i < count; i++)
options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
const std::string& current_path = System::GetDiscPath();
for (auto& [title, glentry] : matches)
{
options.emplace_back(std::move(title), current_path == glentry->path);
paths.push_back(glentry->path);
}
GPUThread::RunOnThread([options = std::move(options)]() mutable {
auto callback = [](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
}
auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::InsertMedia(paths[index - 1].c_str());
}
ReturnToPreviousWindow();
};
CloseChoiceDialog();
ReturnToMainWindow();
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
});
return;
}
}
DoChangeDiscFromFile();
if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty())
{
const auto lock = GameList::GetLock();
auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials);
if (matches.size() > 1)
{
options.reserve(matches.size() + 1);
options.emplace_back(FSUI_STR("From File..."), false);
std::vector<std::string> paths;
paths.reserve(matches.size());
const std::string& current_path = System::GetDiscPath();
for (auto& [title, glentry] : matches)
{
options.emplace_back(std::move(title), current_path == glentry->path);
paths.push_back(glentry->path);
}
GPUThread::RunOnThread([options = std::move(options), paths = std::move(paths)]() mutable {
auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::InsertMedia(paths[index - 1].c_str());
}
CloseChoiceDialog();
ReturnToMainWindow();
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
});
return;
}
}
GPUThread::RunOnThread([]() { DoChangeDiscFromFile(); });
});
}
void FullscreenUI::DoToggleAnalogMode()
{
// hacky way to toggle analog mode
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
{
Controller* ctrl = System::GetController(i);
if (!ctrl)
continue;
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctrl->GetType());
if (!cinfo)
continue;
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
Host::RunOnCPUThread([]() {
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
{
if (std::strcmp(bi.name, "Analog") == 0)
Controller* ctrl = System::GetController(i);
if (!ctrl)
continue;
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctrl->GetType());
if (!cinfo)
continue;
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{
ctrl->SetBindState(bi.bind_index, 1.0f);
ctrl->SetBindState(bi.bind_index, 0.0f);
break;
if (std::strcmp(bi.name, "Analog") == 0)
{
ctrl->SetBindState(bi.bind_index, 1.0f);
ctrl->SetBindState(bi.bind_index, 0.0f);
break;
}
}
}
}
});
}
void FullscreenUI::DoRequestExit()
@ -3760,12 +3830,9 @@ void FullscreenUI::DrawControllerSettingsPage()
&Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count);
// load mtap settings
MultitapMode mtap_mode = g_settings.multitap_mode;
if (IsEditingGameSettings(bsi))
{
mtap_mode = Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
.value_or(g_settings.multitap_mode);
}
const MultitapMode mtap_mode =
Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
.value_or(Settings::DEFAULT_MULTITAP_MODE);
const std::array<bool, 2> mtap_enabled = {
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
@ -7388,31 +7455,36 @@ void FullscreenUI::DrawAboutWindow()
void FullscreenUI::OpenAchievementsWindow()
{
if (!System::IsValid())
return;
if (!Achievements::IsActive())
{
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."),
Host::OSD_INFO_DURATION);
return;
}
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasAchievements() || !Achievements::PrepareAchievementsWindow())
else if (!Achievements::HasAchievements())
{
ShowToast(std::string(), FSUI_STR("This game has no achievements."));
return;
}
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
ForceKeyNavEnabled();
}
GPUThread::RunOnThread([]() {
if (!Initialize() || !Achievements::PrepareAchievementsWindow())
return;
s_current_main_window = MainWindowType::Achievements;
QueueResetFocus(FocusResetType::ViewChanged);
FixStateIfPaused();
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
ForceKeyNavEnabled();
}
s_current_main_window = MainWindowType::Achievements;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
FixStateIfPaused();
});
}
bool FullscreenUI::IsAchievementsWindowOpen()
@ -7422,31 +7494,36 @@ bool FullscreenUI::IsAchievementsWindowOpen()
void FullscreenUI::OpenLeaderboardsWindow()
{
if (!System::IsValid())
return;
if (!Achievements::IsActive())
{
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."),
Host::OSD_INFO_DURATION);
return;
}
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasLeaderboards() || !Achievements::PrepareLeaderboardsWindow())
else if (!Achievements::HasLeaderboards())
{
ShowToast(std::string(), FSUI_STR("This game has no leaderboards."));
return;
}
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
ForceKeyNavEnabled();
}
GPUThread::RunOnThread([]() {
if (!Initialize() || !Achievements::PrepareLeaderboardsWindow())
return;
s_current_main_window = MainWindowType::Leaderboards;
QueueResetFocus(FocusResetType::ViewChanged);
FixStateIfPaused();
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
ForceKeyNavEnabled();
}
s_current_main_window = MainWindowType::Leaderboards;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
FixStateIfPaused();
});
}
bool FullscreenUI::IsLeaderboardsWindowOpen()

View File

@ -21,7 +21,6 @@ bool IsInitialized();
bool HasActiveWindow();
void CheckForConfigChanges(const Settings& old_settings);
void OnSystemStarted();
void OnSystemPaused();
void OnSystemResumed();
void OnSystemDestroyed();
void OnRunningGameChanged();
@ -50,6 +49,12 @@ namespace Host {
#ifndef __ANDROID__
/// Called whenever fullscreen UI starts/stops.
void OnFullscreenUIStartedOrStopped(bool started);
/// Called when the pause state changes, or fullscreen UI opens.
void OnFullscreenUIActiveChanged(bool is_active);
/// Requests shut down and exit of the hosting application. This may not actually exit,
/// if the user cancels the shutdown confirmation.
void RequestExitApplication(bool allow_confirm);

File diff suppressed because it is too large Load Diff

View File

@ -38,13 +38,11 @@ enum class PacketType : u8;
class Recorder;
class Player;
} // namespace GPUDump
class GPUBackend;
struct Settings;
namespace Threading {
class Thread;
}
class GPU
class GPU final
{
public:
enum class BlitterState : u8
@ -61,7 +59,6 @@ public:
DOT_TIMER_INDEX = 0,
HBLANK_TIMER_INDEX = 1,
MAX_RESOLUTION_SCALE = 32,
DEINTERLACE_BUFFER_COUNT = 4,
DRAWING_AREA_COORD_MASK = 1023,
};
@ -87,25 +84,14 @@ public:
// Base class constructor.
GPU();
virtual ~GPU();
~GPU();
virtual const Threading::Thread* GetSWThread() const = 0;
virtual bool IsHardwareRenderer() const = 0;
virtual bool Initialize(Error* error);
virtual void Reset(bool clear_vram);
virtual bool DoState(StateWrapper& sw, GPUTexture** save_to_texture, bool update_display);
// Graphics API state reset/restore - call when drawing the UI etc.
// TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext();
void Initialize();
void Reset(bool clear_vram);
bool DoState(StateWrapper& sw, bool update_display);
// Render statistics debug window.
void DrawDebugStateWindow(float scale);
void GetStatsString(SmallStringBase& str);
void GetMemoryStatsString(SmallStringBase& str);
void ResetStatistics();
void UpdateStatistics(u32 frame_count);
void CPUClockChanged();
@ -169,24 +155,15 @@ public:
void SynchronizeCRTC();
/// Recompile shaders/recreate framebuffers when needed.
virtual void UpdateSettings(const Settings& old_settings);
void UpdateSettings(const Settings& old_settings);
/// Returns the current resolution scale.
virtual u32 GetResolutionScale() const;
/// Updates the resolution scale when it's set to automatic.
virtual void UpdateResolutionScale();
/// Returns the full display resolution of the GPU, including padding.
std::tuple<u32, u32> GetFullDisplayResolution() const;
/// Computes clamped drawing area.
static GSVector4i GetClampedDrawingArea(const GPUDrawingArea& drawing_area);
float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const;
float ComputeDisplayAspectRatio() const;
static std::unique_ptr<GPU> CreateHardwareRenderer(Error* error);
static std::unique_ptr<GPU> CreateSoftwareRenderer(Error* error);
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns.
void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
float* display_y) const;
@ -217,39 +194,14 @@ public:
// Dumps raw VRAM to a file.
bool DumpVRAMToFile(const char* filename);
// Ensures all buffered vertices are drawn.
virtual void FlushRender() = 0;
/// Helper function for computing the draw rectangle in a larger window.
void CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio,
GSVector4i* display_rect, GSVector4i* draw_rect) const;
static void CalculateDrawRect(u32 window_width, u32 window_height, u32 crtc_display_width, u32 crtc_display_height,
s32 display_origin_left, s32 display_origin_top, u32 display_vram_width,
u32 display_vram_height, DisplayRotation rotation, float aspect_ratio,
bool stretch_vertically, bool integer_scale, GSVector4i* display_rect,
GSVector4i* draw_rect);
/// Helper function for computing screenshot bounds.
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const;
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string path);
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, std::vector<u32>* out_pixels, u32* out_stride,
GPUTexture::Format* out_format);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message);
/// Draws the current display texture, with any post-processing.
GPUDevice::PresentResult PresentDisplay();
/// Sends the current frame to media capture.
bool SendDisplayToMediaCapture(MediaCapture* cap);
/// Reads the CLUT from the specified coordinates, accounting for wrap-around.
static void ReadCLUT(u16* dest, GPUTexturePaletteReg reg, bool clut_is_8bit);
protected:
private:
TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const;
TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
@ -260,16 +212,6 @@ protected:
}
ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; }
static constexpr std::tuple<u8, u8> UnpackTexcoord(u16 texcoord)
{
return std::make_tuple(static_cast<u8>(texcoord), static_cast<u8>(texcoord >> 8));
}
static constexpr std::tuple<u8, u8, u8> UnpackColorRGB24(u32 rgb24)
{
return std::make_tuple(static_cast<u8>(rgb24), static_cast<u8>(rgb24 >> 8), static_cast<u8>(rgb24 >> 16));
}
static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer,
bool remove_alpha);
@ -289,10 +231,10 @@ protected:
void UpdateGPUIdle();
/// Returns 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...).
ALWAYS_INLINE u32 GetInterlacedDisplayField() const { return ZeroExtend32(m_crtc_state.interlaced_field); }
ALWAYS_INLINE u8 GetInterlacedDisplayField() const { return m_crtc_state.interlaced_field; }
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
ALWAYS_INLINE u32 GetActiveLineLSB() const { return ZeroExtend32(m_crtc_state.active_line_lsb); }
ALWAYS_INLINE u8 GetActiveLineLSB() const { return m_crtc_state.active_line_lsb; }
/// Updates drawing area that's suitablef or clamping.
void SetClampedDrawingArea();
@ -327,16 +269,15 @@ protected:
void InvalidateCLUT();
bool IsCLUTValid() const;
// Rendering in the backend
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) = 0;
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) = 0;
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) = 0;
virtual void DispatchRenderCommand() = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
virtual void UpdateDisplay() = 0;
virtual void DrawRendererStats();
virtual void OnBufferSwapped();
void ReadVRAM(u16 x, u16 y, u16 width, u16 height);
void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask);
void UpdateDisplay(bool is_frame);
void PrepareForDraw();
void FinishPolyline();
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
bool textured, bool semitransparent)
@ -433,14 +374,10 @@ protected:
u32 texture_window_value;
// decoded values
// TODO: Make this a command
GPUTextureWindow texture_window;
bool texture_x_flip;
bool texture_y_flip;
bool texture_page_changed;
ALWAYS_INLINE bool IsTexturePageChanged() const { return texture_page_changed; }
ALWAYS_INLINE void SetTexturePageChanged() { texture_page_changed = true; }
ALWAYS_INLINE void ClearTexturePageChangedFlag() { texture_page_changed = false; }
} m_draw_mode = {};
GPUDrawingArea m_drawing_area = {};
@ -574,65 +511,7 @@ protected:
TickCount m_max_run_ahead = 128;
u32 m_fifo_size = 128;
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
GPUDevice::PresentResult RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx);
bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing();
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
GPUTexture* m_display_depth_buffer = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
struct Counters
{
u32 num_reads;
u32 num_writes;
u32 num_copies;
u32 num_vertices;
u32 num_primitives;
// u32 num_read_texture_updates;
// u32 num_ubo_updates;
};
struct Stats : Counters
{
size_t host_buffer_streamed;
u32 host_num_draws;
u32 host_num_barriers;
u32 host_num_render_passes;
u32 host_num_copies;
u32 host_num_downloads;
u32 host_num_uploads;
};
Counters m_counters = {};
Stats m_stats = {};
private:
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
using GP0CommandHandler = bool (GPU::*)();
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
static GP0CommandHandlerTable GenerateGP0CommandHandlerTable();

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,8 @@
#include "gpu_types.h"
#include "util/gpu_device.h"
#include "common/heap_array.h"
#include "common/threading.h"
@ -12,84 +14,196 @@
#include <condition_variable>
#include <memory>
#include <mutex>
#include <tuple>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4324) // warning C4324: 'GPUBackend': structure was padded due to alignment specifier
#endif
class Error;
class SmallStringBase;
class GPUFramebuffer;
class GPUPipeline;
struct Settings;
class StateWrapper;
// DESIGN NOTE: Only static methods should be called on the CPU thread.
// You specifically don't have a global pointer available for this reason.
class GPUBackend
{
public:
static GPUThreadCommand* NewClearVRAMCommand();
static GPUThreadCommand* NewClearDisplayCommand();
static GPUBackendUpdateDisplayCommand* NewUpdateDisplayCommand();
static GPUThreadCommand* NewClearCacheCommand();
static GPUThreadCommand* NewBufferSwappedCommand();
static GPUThreadCommand* NewUpdateResolutionScaleCommand();
static GPUBackendReadVRAMCommand* NewReadVRAMCommand();
static GPUBackendFillVRAMCommand* NewFillVRAMCommand();
static GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
static GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
static GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
static GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
static GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
static GPUBackendDrawPrecisePolygonCommand* NewDrawPrecisePolygonCommand(u32 num_vertices);
static GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
static GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
static void PushCommand(GPUThreadCommand* cmd);
static void PushCommandAndWakeThread(GPUThreadCommand* cmd);
static void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
static bool IsUsingHardwareBackend();
static std::unique_ptr<GPUBackend> CreateHardwareBackend();
static std::unique_ptr<GPUBackend> CreateSoftwareBackend();
static bool RenderScreenshotToBuffer(u32 width, u32 height, bool postfx, u32* out_width, u32* out_height,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
static void RenderScreenshotToFile(const std::string_view path, DisplayScreenshotMode mode, u8 quality,
bool compress_on_thread, bool show_osd_message);
public:
GPUBackend();
virtual ~GPUBackend();
ALWAYS_INLINE const Threading::Thread* GetThread() const { return m_use_gpu_thread ? &m_gpu_thread : nullptr; }
virtual bool IsHardwareRenderer() const = 0;
virtual bool Initialize(bool use_thread);
virtual void Reset();
virtual void Shutdown();
virtual bool Initialize(bool upload_vram, Error* error);
void SetThreadEnabled(bool use_thread);
virtual void UpdateSettings(const Settings& old_settings);
GPUBackendFillVRAMCommand* NewFillVRAMCommand();
GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
/// Returns the current resolution scale.
virtual u32 GetResolutionScale() const = 0;
void PushCommand(GPUBackendCommand* cmd);
void Sync(bool allow_sleep);
/// Updates the resolution scale when it's set to automatic.
virtual void UpdateResolutionScale() = 0;
/// Processes all pending GPU commands.
void RunGPULoop();
/// Returns the full display resolution of the GPU, including padding.
std::tuple<u32, u32> GetFullDisplayResolution() const;
// Graphics API state reset/restore - call when drawing the UI etc.
// TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext() = 0;
/// Main command handler for GPU thread.
void HandleCommand(const GPUThreadCommand* cmd);
/// Draws the current display texture, with any post-processing.
GPUDevice::PresentResult PresentDisplay();
/// Helper function to save current display texture to PNG. Used for regtest.
bool WriteDisplayTextureToFile(std::string filename);
bool BeginQueueFrame();
void WaitForOneQueuedFrame();
void GetStatsString(SmallStringBase& str) const;
void GetMemoryStatsString(SmallStringBase& str) const;
void ResetStatistics();
void UpdateStatistics(u32 frame_count);
protected:
void* AllocateCommand(GPUBackendCommandType command, u32 size);
u32 GetPendingCommandSize() const;
void WakeGPUThread();
void StartGPUThread();
void StopGPUThread();
enum : u32
{
DEINTERLACE_BUFFER_COUNT = 4,
};
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) = 0;
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data,
GPUBackendCommandParameters params) = 0;
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) = 0;
virtual void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) = 0;
virtual void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) = 0;
virtual void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawLine(const GPUBackendDrawLineCommand* cmd) = 0;
virtual void FlushRender() = 0;
virtual void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) = 0;
virtual void DrawingAreaChanged() = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
virtual void ClearCache() = 0;
virtual void OnBufferSwapped() = 0;
virtual void ClearVRAM() = 0;
void HandleCommand(const GPUBackendCommand* cmd);
virtual void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) = 0;
Threading::KernelSemaphore m_sync_semaphore;
std::atomic_bool m_gpu_thread_sleeping{false};
std::atomic_bool m_gpu_loop_done{false};
Threading::Thread m_gpu_thread;
bool m_use_gpu_thread = false;
virtual void LoadState(const GPUBackendLoadStateCommand* cmd) = 0;
std::mutex m_sync_mutex;
std::condition_variable m_sync_cpu_thread_cv;
std::condition_variable m_wake_gpu_thread_cv;
bool m_sync_done = false;
/// Ensures all pending draws are flushed to the host GPU.
virtual void FlushRender() = 0;
enum : u32
{
COMMAND_QUEUE_SIZE = 4 * 1024 * 1024,
THRESHOLD_TO_WAKE_GPU = 256
};
/// Helper function for computing the draw rectangle in a larger window.
void CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio,
GSVector4i* display_rect, GSVector4i* draw_rect) const;
FixedHeapArray<u8, COMMAND_QUEUE_SIZE> m_command_fifo_data;
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_read_ptr{0};
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_write_ptr{0};
/// Helper function for computing screenshot bounds.
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const;
/// Renders the display, optionally with postprocessing to the specified image.
void HandleRenderScreenshotToBuffer(const GPUThreadRenderScreenshotToBufferCommand* cmd);
void HandleRenderScreenshotToFile(const GPUThreadRenderScreenshotToFileCommand* cmd);
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, std::vector<u32>* out_pixels, u32* out_stride,
GPUTexture::Format* out_format);
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
void HandleUpdateDisplayCommand(const GPUBackendUpdateDisplayCommand* cmd);
void ClearDisplay();
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_buffer, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
GPUDevice::PresentResult RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx);
/// Sends the current frame to media capture.
void SendDisplayToMediaCapture(MediaCapture* cap);
bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing();
s32 m_display_width = 0;
s32 m_display_height = 0;
s32 m_display_origin_left = 0;
s32 m_display_origin_top = 0;
s32 m_display_vram_width = 0;
s32 m_display_vram_height = 0;
float m_display_aspect_ratio = 0.0f;
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
GPUTexture* m_display_depth_buffer = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
std::atomic<u32> m_queued_frames;
std::atomic_bool m_waiting_for_gpu_thread;
Threading::KernelSemaphore m_gpu_thread_wait;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
namespace Host {
/// Called at the end of the frame, before presentation.
void FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number);
} // namespace Host

View File

@ -1,13 +1,16 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cpu_pgxp.h"
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_dump.h"
#include "gpu_hw_texture_cache.h"
#include "interrupt_controller.h"
#include "system.h"
#include "common/assert.h"
#include "common/gsvector_formatter.h"
#include "common/log.h"
#include "common/string_util.h"
@ -93,7 +96,7 @@ void GPU::TryExecuteCommands()
// drop terminator
m_fifo.RemoveOne();
DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
DispatchRenderCommand();
FinishPolyline();
m_blit_buffer.clear();
EndCommand();
continue;
@ -200,8 +203,8 @@ bool GPU::HandleNOPCommand()
bool GPU::HandleClearCacheCommand()
{
DEBUG_LOG("GP0 clear cache");
m_draw_mode.SetTexturePageChanged();
InvalidateCLUT();
GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand());
m_fifo.RemoveOne();
AddCommandTicks(1);
EndCommand();
@ -248,8 +251,6 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand()
DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
if (m_drawing_area.left != left || m_drawing_area.top != top)
{
FlushRender();
m_drawing_area.left = left;
m_drawing_area.top = top;
m_drawing_area_changed = true;
@ -270,8 +271,6 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand()
DEBUG_LOG("Set drawing area bottom-right: ({}, {})", m_drawing_area.right, m_drawing_area.bottom);
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
{
FlushRender();
m_drawing_area.right = right;
m_drawing_area.bottom = bottom;
m_drawing_area_changed = true;
@ -291,8 +290,6 @@ bool GPU::HandleSetDrawingOffsetCommand()
DEBUG_LOG("Set drawing offset ({}, {})", m_drawing_offset.x, m_drawing_offset.y);
if (m_drawing_offset.x != x || m_drawing_offset.y != y)
{
FlushRender();
m_drawing_offset.x = x;
m_drawing_offset.y = y;
}
@ -308,11 +305,7 @@ bool GPU::HandleSetMaskBitCommand()
constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
const u32 gpustat_bits = (param & 0x03) << 11;
if ((m_GPUSTAT.bits & gpustat_mask) != gpustat_bits)
{
FlushRender();
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
}
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
@ -321,6 +314,36 @@ bool GPU::HandleSetMaskBitCommand()
return true;
}
void GPU::PrepareForDraw()
{
if (m_drawing_area_changed)
{
m_drawing_area_changed = false;
GPUBackendSetDrawingAreaCommand* cmd = GPUBackend::NewSetDrawingAreaCommand();
cmd->new_area = m_drawing_area;
GPUBackend::PushCommand(cmd);
}
}
void GPU::FillBackendCommandParameters(GPUBackendCommand* cmd) const
{
cmd->params.bits = 0;
cmd->params.check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
cmd->params.set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
cmd->params.active_line_lsb = m_crtc_state.active_line_lsb;
cmd->params.interlaced_rendering = IsInterlacedRenderingEnabled();
}
void GPU::FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const
{
FillBackendCommandParameters(cmd);
cmd->rc.bits = rc.bits;
cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
cmd->draw_mode.dither_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
cmd->palette.bits = m_draw_mode.palette_reg.bits;
cmd->window = m_draw_mode.texture_window;
}
bool GPU::HandleRenderPolygonCommand()
{
const GPURenderCommand rc{FifoPeek(0)};
@ -346,6 +369,7 @@ bool GPU::HandleRenderPolygonCommand()
words_per_vertex, setup_ticks);
// set draw state up
// TODO: Get rid of SetTexturePalette() and just fill it as needed
if (rc.texture_enable)
{
const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
@ -355,12 +379,218 @@ bool GPU::HandleRenderPolygonCommand()
UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
}
m_counters.num_vertices += num_vertices;
m_counters.num_primitives++;
m_render_command.bits = rc.bits;
m_fifo.RemoveOne();
DispatchRenderCommand();
PrepareForDraw();
if (g_settings.gpu_pgxp_enable)
{
GPUBackendDrawPrecisePolygonCommand* cmd = GPUBackend::NewDrawPrecisePolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
bool valid_w = g_settings.gpu_pgxp_texture_correction;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPrecisePolygonCommand::Vertex* vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->native_x = m_drawing_offset.x + vp.x;
vert->native_y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
}
cmd->valid_w = valid_w;
if (!valid_w)
{
if (g_settings.gpu_pgxp_disable_2d)
{
// NOTE: This reads uninitialized data, but it's okay, it doesn't get used.
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPrecisePolygonCommand::Vertex& v = cmd->vertices[i];
GSVector2::store(&v.x, GSVector2(GSVector2i::load(&v.native_x)));
v.w = 1.0f;
}
}
else
{
for (u32 i = 0; i < num_vertices; i++)
cmd->vertices[i].w = 1.0f;
}
}
// Cull polygons which are too large.
const GSVector2 v0f = GSVector2::load(&cmd->vertices[0].x);
const GSVector2 v1f = GSVector2::load(&cmd->vertices[1].x);
const GSVector2 v2f = GSVector2::load(&cmd->vertices[2].x);
const GSVector2 min_pos_12 = v1f.min(v2f);
const GSVector2 max_pos_12 = v1f.max(v2f);
const GSVector4i draw_rect_012 = GSVector4i(GSVector4(min_pos_12.min(v0f)).upld(GSVector4(max_pos_12.max(v0f))))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_012.rintersects(m_clamped_drawing_area));
if (first_tri_culled)
{
// TODO: GPU events... somehow.
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].native_x,
cmd->vertices[0].native_y, cmd->vertices[1].native_x, cmd->vertices[1].native_y,
cmd->vertices[2].native_x, cmd->vertices[2].native_y);
if (!rc.quad_polygon)
{
EndCommand();
return true;
}
}
else
{
AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[0].native_x), GSVector2i::load(&cmd->vertices[1].native_x),
GSVector2i::load(&cmd->vertices[2].native_x), rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector2 v3f = GSVector2::load(&cmd->vertices[3].x);
const GSVector4i draw_rect_123 = GSVector4i(GSVector4(min_pos_12.min(v3f)).upld(GSVector4(max_pos_12.max(v3f))))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_123.rintersects(m_clamped_drawing_area));
if (second_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon (quad second half): {},{} {},{} {},{}",
cmd->vertices[2].native_x, cmd->vertices[2].native_y, cmd->vertices[1].native_x,
cmd->vertices[1].native_y, cmd->vertices[0].native_x, cmd->vertices[0].native_y);
if (first_tri_culled)
{
EndCommand();
return true;
}
// Remove second part of quad.
cmd->num_vertices = 3;
}
else
{
AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[2].native_x), GSVector2i::load(&cmd->vertices[1].native_x),
GSVector2i::load(&cmd->vertices[3].native_x), rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
// If first part was culled, move the second part to the first.
if (first_tri_culled)
{
std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
cmd->num_vertices = 3;
}
}
}
GPUBackend::PushCommand(cmd);
}
else
{
GPUBackendDrawPolygonCommand* cmd = GPUBackend::NewDrawPolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPolygonCommand::Vertex* vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->x = m_drawing_offset.x + vp.x;
vert->y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
}
// Cull polygons which are too large.
const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
const GSVector2i v2 = GSVector2i::load(&cmd->vertices[2].x);
const GSVector2i min_pos_12 = v1.min_s32(v2);
const GSVector2i max_pos_12 = v1.max_s32(v2);
const GSVector4i draw_rect_012 =
GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_012.rintersects(m_clamped_drawing_area));
if (first_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
if (!rc.quad_polygon)
{
EndCommand();
return true;
}
}
else
{
AddDrawTriangleTicks(v0, v1, v2, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector2i v3 = GSVector2i::load(&cmd->vertices[3].x);
const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
.upl64(GSVector4i(max_pos_12.max_s32(v3)))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_123.rintersects(m_clamped_drawing_area));
if (second_tri_culled)
{
DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
if (first_tri_culled)
{
EndCommand();
return true;
}
// Remove second part of quad.
cmd->num_vertices = 3;
}
else
{
AddDrawTriangleTicks(v2, v1, v3, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
// If first part was culled, move the second part to the first.
if (first_tri_culled)
{
std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPolygonCommand::Vertex));
std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPolygonCommand::Vertex));
cmd->num_vertices = 3;
}
}
}
GPUBackend::PushCommand(cmd);
}
EndCommand();
return true;
}
@ -389,12 +619,65 @@ bool GPU::HandleRenderRectangleCommand()
rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
m_counters.num_vertices++;
m_counters.num_primitives++;
m_render_command.bits = rc.bits;
m_fifo.RemoveOne();
DispatchRenderCommand();
PrepareForDraw();
GPUBackendDrawRectangleCommand* cmd = GPUBackend::NewDrawRectangleCommand();
FillDrawCommand(cmd, rc);
cmd->color = rc.color_for_first_vertex;
const GPUVertexPosition vp{FifoPop()};
cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
if (rc.texture_enable)
{
const u32 texcoord_and_palette = FifoPop();
cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
cmd->texcoord = Truncate16(texcoord_and_palette);
}
else
{
cmd->palette.bits = 0;
cmd->texcoord = 0;
}
switch (rc.rectangle_size)
{
case GPUDrawRectangleSize::R1x1:
cmd->width = 1;
cmd->height = 1;
break;
case GPUDrawRectangleSize::R8x8:
cmd->width = 8;
cmd->height = 8;
break;
case GPUDrawRectangleSize::R16x16:
cmd->width = 16;
cmd->height = 16;
break;
default:
{
const u32 width_and_height = FifoPop();
cmd->width = static_cast<u16>(width_and_height & VRAM_WIDTH_MASK);
cmd->height = static_cast<u16>((width_and_height >> 16) & VRAM_HEIGHT_MASK);
}
break;
}
const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty()) [[unlikely]]
{
DEBUG_LOG("Culling off-screen rectangle {}", rect);
EndCommand();
return true;
}
AddDrawRectangleTicks(clamped_rect, rc.texture_enable, rc.transparency_enable);
GPUBackend::PushCommand(cmd);
EndCommand();
return true;
}
@ -411,12 +694,55 @@ bool GPU::HandleRenderLineCommand()
TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
rc.shading_enable ? "shaded" : "monochrome", total_words);
m_counters.num_vertices += 2;
m_counters.num_primitives++;
m_render_command.bits = rc.bits;
m_fifo.RemoveOne();
DispatchRenderCommand();
PrepareForDraw();
GPUBackendDrawLineCommand* cmd = GPUBackend::NewDrawLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
if (rc.shading_enable)
{
cmd->vertices[0].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
else
{
cmd->vertices[0].color = rc.color_for_first_vertex;
cmd->vertices[1].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y);
EndCommand();
return true;
}
AddDrawLineTicks(clamped_rect, rc.shading_enable);
GPUBackend::PushCommand(cmd);
EndCommand();
return true;
}
@ -453,6 +779,64 @@ bool GPU::HandleRenderPolyLineCommand()
return true;
}
void GPU::FinishPolyline()
{
PrepareForDraw();
const u32 num_vertices = GetPolyLineVertexCount();
DebugAssert(num_vertices >= 2);
GPUBackendDrawLineCommand* cmd = GPUBackend::NewDrawLineCommand((num_vertices - 1) * 2);
FillDrawCommand(cmd, m_render_command);
u32 buffer_pos = 0;
const GPUVertexPosition start_vp{m_blit_buffer[buffer_pos++]};
const GSVector2i draw_offset = GSVector2i::load(&m_drawing_offset.x);
GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
u32 start_color = m_render_command.color_for_first_vertex;
const bool shaded = m_render_command.shading_enable;
u32 out_vertex_count = 0;
for (u32 i = 1; i < num_vertices; i++)
{
const u32 end_color =
shaded ? (m_blit_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF)) : m_render_command.color_for_first_vertex;
const GPUVertexPosition vp{m_blit_buffer[buffer_pos++]};
const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
const GSVector4i rect =
GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", start_pos.x, start_pos.y, end_pos.x, end_pos.y);
}
else
{
AddDrawLineTicks(clamped_rect, m_render_command.shading_enable);
GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
out_vertex_count += 2;
GSVector2i::store(&out_vertex[0].x, start_pos);
out_vertex[0].color = start_color;
GSVector2i::store(&out_vertex[1].x, end_pos);
out_vertex[1].color = end_color;
}
start_pos = end_pos;
start_color = end_color;
}
if (out_vertex_count > 0)
{
DebugAssert(out_vertex_count <= cmd->num_vertices);
cmd->num_vertices = Truncate16(out_vertex_count);
GPUBackend::PushCommand(cmd);
}
}
bool GPU::HandleFillRectangleCommand()
{
CHECK_COMMAND_SIZE(3);
@ -460,8 +844,6 @@ bool GPU::HandleFillRectangleCommand()
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
SynchronizeCRTC();
FlushRender();
const u32 color = FifoPop() & 0x00FFFFFF;
const u32 dst_x = FifoPeek() & 0x3F0;
const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
@ -471,9 +853,17 @@ bool GPU::HandleFillRectangleCommand()
DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
if (width > 0 && height > 0)
FillVRAM(dst_x, dst_y, width, height, color);
{
GPUBackendFillVRAMCommand* cmd = GPUBackend::NewFillVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->x = static_cast<u16>(dst_x);
cmd->y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
cmd->color = color;
GPUBackend::PushCommand(cmd);
}
m_counters.num_writes++;
AddCommandTicks(46 + ((width / 8) + 9) * height);
EndCommand();
return true;
@ -523,8 +913,6 @@ void GPU::FinishVRAMWrite()
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
SynchronizeCRTC();
FlushRender();
if (m_blit_remaining_words == 0)
{
if (g_settings.debugging.dump_cpu_to_vram_copies)
@ -557,18 +945,18 @@ void GPU::FinishVRAMWrite()
const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data());
if (transferred_full_rows > 0)
{
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, transferred_full_rows, blit_ptr,
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, static_cast<u16>(transferred_full_rows),
blit_ptr, m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16);
}
if (transferred_width_last_row > 0)
{
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y + transferred_full_rows, transferred_width_last_row, 1, blit_ptr,
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
UpdateVRAM(m_vram_transfer.x, static_cast<u16>(m_vram_transfer.y + transferred_full_rows),
static_cast<u16>(transferred_width_last_row), 1, blit_ptr, m_GPUSTAT.set_mask_while_drawing,
m_GPUSTAT.check_mask_before_draw);
}
}
m_counters.num_writes++;
m_blit_buffer.clear();
m_vram_transfer = {};
m_blitter_state = BlitterState::Idle;
@ -588,9 +976,6 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
m_vram_transfer.width, m_vram_transfer.height);
DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
// all rendering should be done first...
FlushRender();
// ensure VRAM shadow is up to date
ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
@ -602,7 +987,6 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
}
// switch to pixel-by-pixel read state
m_counters.num_reads++;
m_blitter_state = BlitterState::ReadingVRAM;
m_command_total_words = 0;
@ -633,10 +1017,15 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
if (!skip_copy)
{
m_counters.num_copies++;
FlushRender();
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->src_x = static_cast<u16>(src_x);
cmd->src_y = static_cast<u16>(src_y);
cmd->dst_x = static_cast<u16>(dst_x);
cmd->dst_y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
GPUBackend::PushCommand(cmd);
}
AddCommandTicks(width * height * 2);

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
#pragma once
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_hw_texture_cache.h"
#include "util/gpu_device.h"
@ -21,7 +21,9 @@ class GPU_SW_Backend;
struct GPUBackendCommand;
struct GPUBackendDrawCommand;
class GPU_HW final : public GPU
// TODO: Move to cpp
// TODO: Rename to GPUHWBackend, preserved to avoid conflicts.
class GPU_HW final : public GPUBackend
{
public:
enum class BatchRenderMode : u8
@ -63,21 +65,40 @@ public:
GPU_HW();
~GPU_HW() override;
const Threading::Thread* GetSWThread() const override;
bool IsHardwareRenderer() const override;
bool Initialize(Error* error) override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
bool Initialize(bool upload_vram, Error* error) override;
u32 GetResolutionScale() const override;
void RestoreDeviceContext() override;
protected:
void UpdateSettings(const Settings& old_settings) override;
u32 GetResolutionScale() const override;
void UpdateResolutionScale() override;
void UpdateDisplay() override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void FlushRender() override;
void DrawingAreaChanged() override;
void ClearVRAM() override;
void LoadState(const GPUBackendLoadStateCommand* cmd) override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
private:
enum : u32
@ -86,6 +107,7 @@ private:
MAX_VERTICES_FOR_RECTANGLE = 6 * (((MAX_PRIMITIVE_WIDTH + (TEXTURE_PAGE_WIDTH - 1)) / TEXTURE_PAGE_WIDTH) + 1u) *
(((MAX_PRIMITIVE_HEIGHT + (TEXTURE_PAGE_HEIGHT - 1)) / TEXTURE_PAGE_HEIGHT) + 1u),
NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount),
INVALID_DRAW_MODE_BITS = 0xFFFFFFFFu,
};
enum : u8
{
@ -164,8 +186,6 @@ private:
bool CompileResolutionDependentPipelines(Error* error);
bool CompileDownsamplePipelines(Error* error);
void LoadVertices();
void PrintSettingsToLog();
void CheckSettings();
@ -184,8 +204,10 @@ private:
u32 CalculateResolutionScale() const;
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
bool ShouldDrawWithSoftwareRenderer() const;
bool IsUsingMultisampling() const;
bool IsUsingDownsampling() const;
bool IsUsingDownsampling(const GPUBackendUpdateDisplayCommand* cmd) const;
void SetFullVRAMDirtyRectangle();
void ClearVRAMDirtyRectangle();
@ -195,12 +217,15 @@ private:
void AddUnclampedDrawnRectangle(const GSVector4i rect);
void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
void CheckForTexPageOverlap(GSVector4i uv_rect);
void CheckForTexPageOverlap(const GPUBackendDrawCommand* cmd, GSVector4i uv_rect);
bool ShouldCheckForTexPageOverlap() const;
bool IsFlushed() const;
void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices);
void EnsureVertexBufferSpaceForCurrentCommand();
void EnsureVertexBufferSpaceForCommand(const GPUBackendDrawCommand* cmd);
void PrepareDraw(const GPUBackendDrawCommand* cmd);
void FinishPolygonDraw(const GPUBackendDrawCommand* cmd, std::array<BatchVertex, 4>& vertices, u32 num_vertices,
bool is_3d);
void ResetBatchVertexDepth();
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
@ -212,20 +237,6 @@ private:
/// Returns true if the draw is going to use shader blending/framebuffer fetch.
bool NeedsShaderBlending(GPUTransparencyMode transparency, BatchTextureMode texture, bool check_mask) const;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
void UpdateSoftwareRenderer(bool copy_vram_from_hw);
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void DispatchRenderCommand() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FlushRender() override;
void DrawRendererStats() override;
void OnBufferSwapped() override;
void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask,
bool check_mask, const GSVector4i bounds);
bool BlitVRAMReplacementTexture(const GPUTextureCache::TextureReplacementImage* tex, u32 dst_x, u32 dst_y, u32 width,
@ -235,17 +246,17 @@ private:
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
/// Handles quads with flipped texture coordinate directions.
void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices);
void HandleFlippedQuadTextureCoordinates(const GPUBackendDrawCommand* cmd, BatchVertex* vertices);
bool IsPossibleSpritePolygon(const BatchVertex* vertices) const;
bool ExpandLineTriangles(BatchVertex* vertices);
/// Computes polygon U/V boundaries, and for overlap with the current texture page.
void ComputePolygonUVLimits(BatchVertex* vertices, u32 num_vertices);
void ComputePolygonUVLimits(const GPUBackendDrawCommand* cmd, BatchVertex* vertices, u32 num_vertices);
/// Sets the depth test flag for PGXP depth buffering.
void SetBatchDepthBuffer(bool enabled);
void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices);
void SetBatchSpriteMode(bool enabled);
void SetBatchDepthBuffer(const GPUBackendDrawCommand* cmd, bool enabled);
void CheckForDepthClear(const GPUBackendDrawCommand* cmd, const BatchVertex* vertices, u32 num_vertices);
void SetBatchSpriteMode(const GPUBackendDrawCommand* cmd, bool enabled);
void UpdateDownsamplingLevels();
@ -264,8 +275,6 @@ private:
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
std::unique_ptr<GPUTexture> m_vram_write_texture;
std::unique_ptr<GPU_SW_Backend> m_sw_renderer;
BatchVertex* m_batch_vertex_ptr = nullptr;
u16* m_batch_index_ptr = nullptr;
u32 m_batch_base_vertex = 0;
@ -307,18 +316,32 @@ private:
u8 m_texpage_dirty = 0;
bool m_batch_ubo_dirty = true;
bool m_drawing_area_changed = true;
BatchConfig m_batch;
// Changed state
BatchUBOData m_batch_ubo_data = {};
// Bounding box of VRAM area that the GPU has drawn into.
GSVector4i m_clamped_drawing_area = {};
GSVector4i m_vram_dirty_draw_rect = INVALID_RECT;
GSVector4i m_vram_dirty_write_rect = INVALID_RECT; // TODO: Don't use in TC mode, should be kept at zero.
GSVector4i m_current_uv_rect = INVALID_RECT;
GSVector4i m_current_draw_rect = INVALID_RECT;
s32 m_current_texture_page_offset[2] = {};
union
{
struct
{
// NOTE: Only the texture-related bits should be used here, the others are not validated.
GPUDrawModeReg mode_reg;
GPUTexturePaletteReg palette_reg;
};
u32 bits = INVALID_DRAW_MODE_BITS;
} m_draw_mode = {};
std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
// [wrapped][interlaced]

View File

@ -49,6 +49,9 @@ static constexpr const GSVector4i& INVALID_RECT = GPU_HW::INVALID_RECT;
static constexpr const GPUTexture::Format REPLACEMENT_TEXTURE_FORMAT = GPUTexture::Format::RGBA8;
static constexpr const char LOCAL_CONFIG_FILENAME[] = "config.yaml";
static constexpr u32 STATE_PALETTE_RECORD_SIZE =
sizeof(GSVector4i) + sizeof(SourceKey) + sizeof(PaletteRecordFlags) + sizeof(HashType) + sizeof(u16) * MAX_CLUT_SIZE;
// Has to be public because it's referenced in Source.
struct HashCacheEntry
{
@ -517,6 +520,7 @@ static std::unique_ptr<GPUTexture> s_replacement_texture_render_target;
static std::unique_ptr<GPUPipeline> s_replacement_draw_pipeline; // copies alpha as-is
static std::unique_ptr<GPUPipeline> s_replacement_semitransparent_draw_pipeline; // inverts alpha (i.e. semitransparent)
static GPU_HW* s_hw_backend = nullptr; // TODO:FIXME: remove me
static bool s_track_vram_writes = false;
static std::string s_game_id;
@ -551,8 +555,10 @@ bool GPUTextureCache::IsDumpingVRAMWriteTextures()
return (g_settings.texture_replacements.dump_textures && !s_config.dump_texture_pages);
}
bool GPUTextureCache::Initialize()
bool GPUTextureCache::Initialize(GPU_HW* backend)
{
s_hw_backend = backend;
LoadLocalConfiguration(false, false);
UpdateVRAMTrackingState();
if (!CompilePipelines())
@ -599,134 +605,164 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
}
}
bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
bool GPUTextureCache::GetStateSize(StateWrapper& sw, u32* size)
{
if (sw.GetVersion() < 73)
{
if (!skip)
WARNING_LOG("Texture cache not in save state due to old version.");
Invalidate();
*size = 0;
return true;
}
if (!sw.DoMarker("GPUTextureCache"))
const size_t start = sw.GetPosition();
if (!sw.DoMarker("GPUTextureCache")) [[unlikely]]
return false;
if (sw.IsReading())
u32 num_vram_writes = 0;
sw.Do(&num_vram_writes);
for (u32 i = 0; i < num_vram_writes; i++)
{
if (!skip)
Invalidate();
sw.SkipBytes(sizeof(GSVector4i) * 2 + sizeof(HashType));
u32 num_vram_writes = 0;
sw.Do(&num_vram_writes);
const bool skip_writes = (skip || !s_track_vram_writes);
for (u32 i = 0; i < num_vram_writes; i++)
{
static constexpr u32 PALETTE_RECORD_SIZE = sizeof(GSVector4i) + sizeof(SourceKey) + sizeof(PaletteRecordFlags) +
sizeof(HashType) + sizeof(u16) * MAX_CLUT_SIZE;
if (skip_writes)
{
sw.SkipBytes(sizeof(GSVector4i) * 2 + sizeof(HashType));
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
sw.SkipBytes(num_palette_records * PALETTE_RECORD_SIZE);
}
else
{
VRAMWrite* vrw = new VRAMWrite();
DoStateVector(sw, &vrw->active_rect);
DoStateVector(sw, &vrw->write_rect);
sw.Do(&vrw->hash);
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
// Skip palette records if we're not dumping now.
if (g_settings.texture_replacements.dump_textures)
{
vrw->palette_records.reserve(num_palette_records);
for (u32 j = 0; j < num_palette_records; j++)
{
VRAMWrite::PaletteRecord& rec = vrw->palette_records.emplace_back();
DoStateVector(sw, &rec.rect);
sw.DoBytes(&rec.key, sizeof(rec.key));
sw.Do(&rec.flags);
sw.Do(&rec.palette_hash);
sw.DoBytes(rec.palette, sizeof(rec.palette));
}
}
else
{
sw.SkipBytes(num_palette_records * PALETTE_RECORD_SIZE);
}
if (sw.HasError())
{
delete vrw;
Invalidate();
return false;
}
vrw->num_page_refs = 0;
LoopRectPages(vrw->active_rect, [vrw](u32 pn) {
DebugAssert(vrw->num_page_refs < MAX_PAGE_REFS_PER_WRITE);
ListAppend(&s_pages[pn].writes, vrw, &vrw->page_refs[vrw->num_page_refs++]);
return true;
});
}
}
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
sw.SkipBytes(num_palette_records * STATE_PALETTE_RECORD_SIZE);
}
else
if (sw.HasError()) [[unlikely]]
return false;
*size = static_cast<u32>(sw.GetPosition() - start);
return true;
}
void GPUTextureCache::LoadState(std::span<const u8> data, u32 data_version)
{
Invalidate();
if (data.empty())
{
s_temp_vram_write_list.clear();
WARNING_LOG("Texture cache not in save state due to old version.");
return;
}
if (!skip && s_track_vram_writes)
// Don't need anything if we're not tracking VRAM writes.
if (!s_track_vram_writes)
return;
StateWrapper sw(data, StateWrapper::Mode::Read, data_version);
if (!sw.DoMarker("GPUTextureCache")) [[unlikely]]
{
WARNING_LOG("Invalid save state data.");
return;
}
u32 num_vram_writes = 0;
sw.Do(&num_vram_writes);
for (u32 i = 0; i < num_vram_writes; i++)
{
if (!s_track_vram_writes)
{
for (PageEntry& page : s_pages)
{
ListIterate(page.writes, [](VRAMWrite* vrw) {
if (std::find(s_temp_vram_write_list.begin(), s_temp_vram_write_list.end(), vrw) !=
s_temp_vram_write_list.end())
{
return;
}
sw.SkipBytes(sizeof(GSVector4i) * 2 + sizeof(HashType));
// try not to lose data... pull it from the sources
if (g_settings.texture_replacements.dump_textures)
SyncVRAMWritePaletteRecords(vrw);
s_temp_vram_write_list.push_back(vrw);
});
}
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
sw.SkipBytes(num_palette_records * STATE_PALETTE_RECORD_SIZE);
}
u32 num_vram_writes = static_cast<u32>(s_temp_vram_write_list.size());
sw.Do(&num_vram_writes);
for (VRAMWrite* vrw : s_temp_vram_write_list)
else
{
VRAMWrite* vrw = new VRAMWrite();
DoStateVector(sw, &vrw->active_rect);
DoStateVector(sw, &vrw->write_rect);
sw.Do(&vrw->hash);
u32 num_palette_records = static_cast<u32>(vrw->palette_records.size());
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
for (VRAMWrite::PaletteRecord& rec : vrw->palette_records)
// Skip palette records if we're not dumping now.
if (g_settings.texture_replacements.dump_textures)
{
DoStateVector(sw, &rec.rect);
sw.DoBytes(&rec.key, sizeof(rec.key));
sw.Do(&rec.flags);
sw.Do(&rec.palette_hash);
sw.DoBytes(rec.palette, sizeof(rec.palette));
vrw->palette_records.reserve(num_palette_records);
for (u32 j = 0; j < num_palette_records; j++)
{
VRAMWrite::PaletteRecord& rec = vrw->palette_records.emplace_back();
DoStateVector(sw, &rec.rect);
sw.DoBytes(&rec.key, sizeof(rec.key));
sw.Do(&rec.flags);
sw.Do(&rec.palette_hash);
sw.DoBytes(rec.palette, sizeof(rec.palette));
}
}
else
{
sw.SkipBytes(num_palette_records * STATE_PALETTE_RECORD_SIZE);
}
if (sw.HasError())
{
WARNING_LOG("Invalid save state data.");
delete vrw;
Invalidate();
return;
}
vrw->num_page_refs = 0;
LoopRectPages(vrw->active_rect, [vrw](u32 pn) {
DebugAssert(vrw->num_page_refs < MAX_PAGE_REFS_PER_WRITE);
ListAppend(&s_pages[pn].writes, vrw, &vrw->page_refs[vrw->num_page_refs++]);
return true;
});
}
}
}
void GPUTextureCache::SaveState(StateWrapper& sw)
{
sw.DoMarker("GPUTextureCache");
s_temp_vram_write_list.clear();
if (s_track_vram_writes)
{
for (PageEntry& page : s_pages)
{
ListIterate(page.writes, [](VRAMWrite* vrw) {
if (std::find(s_temp_vram_write_list.begin(), s_temp_vram_write_list.end(), vrw) !=
s_temp_vram_write_list.end())
{
return;
}
// try not to lose data... pull it from the sources
if (g_settings.texture_replacements.dump_textures)
SyncVRAMWritePaletteRecords(vrw);
s_temp_vram_write_list.push_back(vrw);
});
}
}
return !sw.HasError();
u32 num_vram_writes = static_cast<u32>(s_temp_vram_write_list.size());
sw.Do(&num_vram_writes);
for (VRAMWrite* vrw : s_temp_vram_write_list)
{
DoStateVector(sw, &vrw->active_rect);
DoStateVector(sw, &vrw->write_rect);
sw.Do(&vrw->hash);
u32 num_palette_records = static_cast<u32>(vrw->palette_records.size());
sw.Do(&num_palette_records);
for (VRAMWrite::PaletteRecord& rec : vrw->palette_records)
{
DoStateVector(sw, &rec.rect);
sw.DoBytes(&rec.key, sizeof(rec.key));
sw.Do(&rec.flags);
sw.Do(&rec.palette_hash);
sw.DoBytes(rec.palette, sizeof(rec.palette));
}
}
}
void GPUTextureCache::Shutdown()
@ -737,6 +773,7 @@ void GPUTextureCache::Shutdown()
s_replacement_texture_render_target.reset();
s_hash_cache_purge_list = {};
s_temp_vram_write_list = {};
s_hw_backend = nullptr;
s_track_vram_writes = false;
s_replacement_image_cache.clear();
@ -3305,5 +3342,5 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
g_gpu_device->RecycleTexture(std::move(entry->texture));
entry->texture = std::move(replacement_tex);
g_gpu->RestoreDeviceContext();
s_hw_backend->RestoreDeviceContext();
}

View File

@ -10,6 +10,7 @@ class RGBA8Image;
class StateWrapper;
struct Settings;
class GPU_HW;
//////////////////////////////////////////////////////////////////////////
// Texture Cache
@ -102,9 +103,13 @@ struct Source
TListNode<Source> hash_cache_ref;
};
bool Initialize();
bool Initialize(GPU_HW* backend);
void UpdateSettings(bool use_texture_cache, const Settings& old_settings);
bool DoState(StateWrapper& sw, bool skip);
bool GetStateSize(StateWrapper& sw, u32* size);
void LoadState(std::span<const u8> data, u32 data_version);
void SaveState(StateWrapper& sw);
void Shutdown();
void Invalidate();

View File

@ -2,7 +2,8 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_sw.h"
#include "gpu_hw_texture_cache.h"
#include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "settings.h"
#include "system.h"
@ -10,8 +11,7 @@
#include "common/align.h"
#include "common/assert.h"
#include "common/gsvector.h"
#include "common/gsvector_formatter.h"
#include "common/intrin.h"
#include "common/log.h"
#include <algorithm>
@ -20,27 +20,149 @@ LOG_CHANNEL(GPU_SW);
GPU_SW::GPU_SW() = default;
GPU_SW::~GPU_SW()
{
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
m_backend.Shutdown();
}
const Threading::Thread* GPU_SW::GetSWThread() const
{
return m_backend.GetThread();
}
GPU_SW::~GPU_SW() = default;
bool GPU_SW::IsHardwareRenderer() const
{
return false;
}
bool GPU_SW::Initialize(Error* error)
u32 GPU_SW::GetResolutionScale() const
{
if (!GPU::Initialize(error) || !m_backend.Initialize(g_settings.gpu_use_thread))
return 1u;
}
bool GPU_SW::Initialize(bool upload_vram, Error* error)
{
if (!GPUBackend::Initialize(upload_vram, error))
return false;
// if we're using "new" vram, clear it out here
if (!upload_vram)
std::memset(g_vram, 0, sizeof(g_vram));
SetDisplayTextureFormat();
return true;
}
void GPU_SW::ClearVRAM()
{
std::memset(g_vram, 0, sizeof(g_vram));
std::memset(g_gpu_clut, 0, sizeof(g_gpu_clut));
}
void GPU_SW::UpdateResolutionScale()
{
}
void GPU_SW::LoadState(const GPUBackendLoadStateCommand* cmd)
{
std::memcpy(g_vram, cmd->vram_data, sizeof(g_vram));
std::memcpy(g_gpu_clut, cmd->clut_data, sizeof(g_gpu_clut));
}
void GPU_SW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
}
void GPU_SW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, params.interlaced_rendering, params.active_line_lsb);
}
void GPU_SW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::WriteVRAM(x, y, width, height, data, params.set_mask_while_drawing, params.check_mask_before_draw);
}
void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, params.set_mask_while_drawing,
params.check_mask_before_draw);
}
void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (cmd->num_vertices > 3)
DrawFunction(cmd, &cmd->vertices[2], &cmd->vertices[1], &cmd->vertices[3]);
}
void GPU_SW::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
// Need to cut out the irrelevant bits.
// TODO: In _theory_ we could use the fixed-point parts here.
GPUBackendDrawPolygonCommand::Vertex vertices[4];
for (u32 i = 0; i < cmd->num_vertices; i++)
{
const GPUBackendDrawPrecisePolygonCommand::Vertex& src = cmd->vertices[i];
vertices[i] = GPUBackendDrawPolygonCommand::Vertex{
.x = src.native_x, .y = src.native_y, .color = src.color, .texcoord = src.texcoord};
}
DrawFunction(cmd, &vertices[0], &vertices[1], &vertices[2]);
if (cmd->num_vertices > 3)
DrawFunction(cmd, &vertices[2], &vertices[1], &vertices[3]);
}
void GPU_SW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd);
}
void GPU_SW::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->rc.shading_enable, cmd->rc.transparency_enable);
for (u16 i = 0; i < cmd->num_vertices; i += 2)
DrawFunction(cmd, &cmd->vertices[i], &cmd->vertices[i + 1]);
}
void GPU_SW::DrawingAreaChanged()
{
// GPU_SW_Rasterizer::g_drawing_area set by base class.
}
void GPU_SW::ClearCache()
{
}
void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU_SW_Rasterizer::UpdateCLUT(reg, clut_is_8bit);
}
void GPU_SW::OnBufferSwapped()
{
}
void GPU_SW::FlushRender()
{
}
void GPU_SW::RestoreDeviceContext()
{
}
void GPU_SW::SetDisplayTextureFormat()
{
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB565, GPUTexture::Format::RGBA5551,
GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8};
static constexpr const std::array formats_for_24bit = {GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8,
@ -61,35 +183,6 @@ bool GPU_SW::Initialize(Error* error)
break;
}
}
return true;
}
bool GPU_SW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display)
{
// need to ensure the worker thread is done
m_backend.Sync(true);
// ignore the host texture for software mode, since we want to save vram here
if (!GPU::DoState(sw, nullptr, update_display))
return false;
// need to still call the TC, to toss any data in the state
return GPUTextureCache::DoState(sw, true);
}
void GPU_SW::Reset(bool clear_vram)
{
GPU::Reset(clear_vram);
m_backend.Reset();
}
void GPU_SW::UpdateSettings(const Settings& old_settings)
{
GPU::UpdateSettings(old_settings);
if (g_settings.gpu_use_thread != old_settings.gpu_use_thread)
m_backend.SetThreadEnabled(g_settings.gpu_use_thread);
}
GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format)
@ -427,32 +520,28 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3
}
}
void GPU_SW::UpdateDisplay()
void GPU_SW::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
{
// fill display texture
m_backend.Sync(true);
if (!g_settings.debugging.show_vram)
{
if (IsDisplayDisabled())
if (cmd->display_disabled)
{
ClearDisplayTexture();
return;
}
const bool is_24bit = m_GPUSTAT.display_area_color_depth_24;
const bool interlaced = IsInterlacedDisplayEnabled();
const u32 field = GetInterlacedDisplayField();
const u32 vram_offset_x = is_24bit ? m_crtc_state.regs.X : m_crtc_state.display_vram_left;
const u32 vram_offset_y =
m_crtc_state.display_vram_top + ((interlaced && m_GPUSTAT.vertical_resolution) ? field : 0);
const u32 skip_x = is_24bit ? (m_crtc_state.display_vram_left - m_crtc_state.regs.X) : 0;
const u32 read_width = m_crtc_state.display_vram_width;
const u32 read_height = interlaced ? (m_crtc_state.display_vram_height / 2) : m_crtc_state.display_vram_height;
const bool is_24bit = cmd->display_24bit;
const bool interlaced = cmd->interlaced_display_enabled;
const u32 field = cmd->interlaced_display_field;
const u32 vram_offset_x = is_24bit ? cmd->X : cmd->display_vram_left;
const u32 vram_offset_y = cmd->display_vram_top + ((interlaced && cmd->interlaced_display_interleaved) ? field : 0);
const u32 skip_x = is_24bit ? (cmd->display_vram_left - cmd->X) : 0;
const u32 read_width = cmd->display_vram_width;
const u32 read_height = interlaced ? (cmd->display_vram_height / 2) : cmd->display_vram_height;
if (IsInterlacedDisplayEnabled())
if (cmd->interlaced_display_enabled)
{
const u32 line_skip = m_GPUSTAT.vertical_resolution;
const u32 line_skip = cmd->interlaced_display_interleaved;
if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, line_skip, is_24bit))
{
SetDisplayTexture(m_upload_texture.get(), nullptr, 0, 0, read_width, read_height);
@ -484,351 +573,7 @@ void GPU_SW::UpdateDisplay()
}
}
void GPU_SW::FillBackendCommandParameters(GPUBackendCommand* cmd) const
std::unique_ptr<GPUBackend> GPUBackend::CreateSoftwareBackend()
{
cmd->params.bits = 0;
cmd->params.check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
cmd->params.set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
cmd->params.active_line_lsb = m_crtc_state.active_line_lsb;
cmd->params.interlaced_rendering = IsInterlacedRenderingEnabled();
}
void GPU_SW::FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const
{
FillBackendCommandParameters(cmd);
cmd->rc.bits = rc.bits;
cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
cmd->draw_mode.dither_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
cmd->palette.bits = m_draw_mode.palette_reg.bits;
cmd->window = m_draw_mode.texture_window;
}
void GPU_SW::DispatchRenderCommand()
{
if (m_drawing_area_changed)
{
GPUBackendSetDrawingAreaCommand* cmd = m_backend.NewSetDrawingAreaCommand();
cmd->new_area = m_drawing_area;
GSVector4i::store<false>(cmd->new_clamped_area, m_clamped_drawing_area);
m_backend.PushCommand(cmd);
m_drawing_area_changed = false;
}
const GPURenderCommand rc{m_render_command.bits};
switch (rc.primitive)
{
case GPUPrimitive::Polygon:
{
const u32 num_vertices = rc.quad_polygon ? 4 : 3;
GPUBackendDrawPolygonCommand* cmd = m_backend.NewDrawPolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
std::array<GSVector2i, 4> positions;
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPolygonCommand::Vertex* vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->x = m_drawing_offset.x + vp.x;
vert->y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
positions[i] = GSVector2i::load(&vert->x);
}
// Cull polygons which are too large.
const GSVector2i min_pos_12 = positions[1].min_s32(positions[2]);
const GSVector2i max_pos_12 = positions[1].max_s32(positions[2]);
const GSVector4i draw_rect_012 = GSVector4i(min_pos_12.min_s32(positions[0]))
.upl64(GSVector4i(max_pos_12.max_s32(positions[0])))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!m_clamped_drawing_area.rintersects(draw_rect_012));
if (first_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
if (!rc.quad_polygon)
return;
}
else
{
AddDrawTriangleTicks(positions[0], positions[1], positions[2], rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(positions[3]))
.upl64(GSVector4i(max_pos_12.max_s32(positions[3])))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!m_clamped_drawing_area.rintersects(draw_rect_123));
if (second_tri_culled)
{
DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
if (first_tri_culled)
return;
}
else
{
AddDrawTriangleTicks(positions[2], positions[1], positions[3], rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
}
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Rectangle:
{
GPUBackendDrawRectangleCommand* cmd = m_backend.NewDrawRectangleCommand();
FillDrawCommand(cmd, rc);
cmd->color = rc.color_for_first_vertex;
const GPUVertexPosition vp{FifoPop()};
cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
if (rc.texture_enable)
{
const u32 texcoord_and_palette = FifoPop();
cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
cmd->texcoord = Truncate16(texcoord_and_palette);
}
else
{
cmd->palette.bits = 0;
cmd->texcoord = 0;
}
switch (rc.rectangle_size)
{
case GPUDrawRectangleSize::R1x1:
cmd->width = 1;
cmd->height = 1;
break;
case GPUDrawRectangleSize::R8x8:
cmd->width = 8;
cmd->height = 8;
break;
case GPUDrawRectangleSize::R16x16:
cmd->width = 16;
cmd->height = 16;
break;
default:
{
const u32 width_and_height = FifoPop();
cmd->width = static_cast<u16>(width_and_height & VRAM_WIDTH_MASK);
cmd->height = static_cast<u16>((width_and_height >> 16) & VRAM_HEIGHT_MASK);
}
break;
}
const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty()) [[unlikely]]
{
DEBUG_LOG("Culling off-screen rectangle {}", rect);
return;
}
AddDrawRectangleTicks(clamped_rect, rc.texture_enable, rc.transparency_enable);
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Line:
{
if (!rc.polyline)
{
GPUBackendDrawLineCommand* cmd = m_backend.NewDrawLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
if (rc.shading_enable)
{
cmd->vertices[0].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
else
{
cmd->vertices[0].color = rc.color_for_first_vertex;
cmd->vertices[1].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
const GSVector4i v0 = GSVector4i::loadl(&cmd->vertices[0].x);
const GSVector4i v1 = GSVector4i::loadl(&cmd->vertices[1].x);
const GSVector4i rect = v0.min_s32(v1).xyxy(v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y);
return;
}
AddDrawLineTicks(clamped_rect, rc.shading_enable);
m_backend.PushCommand(cmd);
}
else
{
const u32 num_vertices = GetPolyLineVertexCount();
GPUBackendDrawLineCommand* cmd = m_backend.NewDrawLineCommand((num_vertices - 1) * 2);
FillDrawCommand(cmd, m_render_command);
u32 buffer_pos = 0;
const GPUVertexPosition start_vp{m_blit_buffer[buffer_pos++]};
const GSVector2i draw_offset = GSVector2i::load(&m_drawing_offset.x);
GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
u32 start_color = m_render_command.color_for_first_vertex;
const bool shaded = m_render_command.shading_enable;
u32 out_vertex_count = 0;
for (u32 i = 1; i < num_vertices; i++)
{
const u32 end_color =
shaded ? (m_blit_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF)) : m_render_command.color_for_first_vertex;
const GPUVertexPosition vp{m_blit_buffer[buffer_pos++]};
const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
const GSVector4i rect = GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[i - 1].x,
cmd->vertices[i - 1].y, cmd->vertices[i].x, cmd->vertices[i].y);
}
else
{
AddDrawLineTicks(clamped_rect, rc.shading_enable);
GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
out_vertex_count += 2;
GSVector2i::store(&out_vertex[0].x, start_pos);
out_vertex[0].color = start_color;
GSVector2i::store(&out_vertex[1].x, end_pos);
out_vertex[1].color = end_color;
}
start_pos = end_pos;
start_color = end_color;
}
if (out_vertex_count > 0)
{
DebugAssert(out_vertex_count <= cmd->num_vertices);
cmd->num_vertices = Truncate16(out_vertex_count);
m_backend.PushCommand(cmd);
}
}
}
break;
default:
UnreachableCode();
break;
}
}
void GPU_SW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
m_backend.Sync(false);
}
void GPU_SW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color)
{
GPUBackendFillVRAMCommand* cmd = m_backend.NewFillVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->x = static_cast<u16>(x);
cmd->y = static_cast<u16>(y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
cmd->color = color;
m_backend.PushCommand(cmd);
}
void GPU_SW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask)
{
const u32 num_words = width * height;
GPUBackendUpdateVRAMCommand* cmd = m_backend.NewUpdateVRAMCommand(num_words);
FillBackendCommandParameters(cmd);
cmd->params.set_mask_while_drawing = set_mask;
cmd->params.check_mask_before_draw = check_mask;
cmd->x = static_cast<u16>(x);
cmd->y = static_cast<u16>(y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
std::memcpy(cmd->data, data, sizeof(u16) * num_words);
m_backend.PushCommand(cmd);
}
void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height)
{
GPUBackendCopyVRAMCommand* cmd = m_backend.NewCopyVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->src_x = static_cast<u16>(src_x);
cmd->src_y = static_cast<u16>(src_y);
cmd->dst_x = static_cast<u16>(dst_x);
cmd->dst_y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
m_backend.PushCommand(cmd);
}
void GPU_SW::FlushRender()
{
}
void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPUBackendUpdateCLUTCommand* cmd = m_backend.NewUpdateCLUTCommand();
FillBackendCommandParameters(cmd);
cmd->reg.bits = reg.bits;
cmd->clut_is_8bit = clut_is_8bit;
m_backend.PushCommand(cmd);
}
std::unique_ptr<GPU> GPU::CreateSoftwareRenderer(Error* error)
{
std::unique_ptr<GPU_SW> gpu(std::make_unique<GPU_SW>());
if (!gpu->Initialize(error))
gpu.reset();
return gpu;
return std::make_unique<GPU_SW>();
}

View File

@ -4,7 +4,7 @@
#pragma once
#include "gpu.h"
#include "gpu_sw_backend.h"
#include "gpu_backend.h"
#include "util/gpu_device.h"
@ -12,36 +12,49 @@
#include <memory>
namespace Threading {
class Thread;
}
class GPUTexture;
class GPU_SW final : public GPU
// TODO: Move to cpp
// TODO: Rename to GPUSWBackend, preserved to avoid conflicts.
class GPU_SW final : public GPUBackend
{
public:
GPU_SW();
~GPU_SW() override;
ALWAYS_INLINE const GPU_SW_Backend& GetBackend() const { return m_backend; }
const Threading::Thread* GetSWThread() const override;
bool IsHardwareRenderer() const override;
bool Initialize(Error* error) override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
void Reset(bool clear_vram) override;
void UpdateSettings(const Settings& old_settings) override;
bool Initialize(bool upload_vram, Error* error) override;
void RestoreDeviceContext() override;
u32 GetResolutionScale() const override;
protected:
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void FlushRender() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawingAreaChanged() override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
void ClearVRAM() override;
void FlushRender() override;
void UpdateResolutionScale() override;
void LoadState(const GPUBackendLoadStateCommand* cmd) override;
private:
template<GPUTexture::Format display_format>
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
@ -50,19 +63,11 @@ protected:
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
void UpdateDisplay() override;
void DispatchRenderCommand() override;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
void SetDisplayTextureFormat();
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::RGB565;
GPUTexture::Format m_24bit_display_format = GPUTexture::Format::RGBA8;
std::unique_ptr<GPUTexture> m_upload_texture;
GPU_SW_Backend m_backend;
};

View File

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_sw_backend.h"
#include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "system.h"
#include "util/gpu_device.h"
#include <algorithm>
GPU_SW_Backend::GPU_SW_Backend() = default;
GPU_SW_Backend::~GPU_SW_Backend() = default;
bool GPU_SW_Backend::Initialize(bool use_thread)
{
return GPUBackend::Initialize(use_thread);
}
void GPU_SW_Backend::Reset()
{
GPUBackend::Reset();
}
void GPU_SW_Backend::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (rc.quad_polygon)
DrawFunction(cmd, &cmd->vertices[2], &cmd->vertices[1], &cmd->vertices[3]);
}
void GPU_SW_Backend::DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd);
}
void GPU_SW_Backend::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->rc.shading_enable, cmd->rc.transparency_enable);
for (u16 i = 1; i < cmd->num_vertices; i++)
DrawFunction(cmd, &cmd->vertices[i - 1], &cmd->vertices[i]);
}
void GPU_SW_Backend::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, params.interlaced_rendering, params.active_line_lsb);
}
void GPU_SW_Backend::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data,
GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::WriteVRAM(x, y, width, height, data, params.set_mask_while_drawing, params.check_mask_before_draw);
}
void GPU_SW_Backend::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, params.set_mask_while_drawing,
params.check_mask_before_draw);
}
void GPU_SW_Backend::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU::ReadCLUT(g_gpu_clut, reg, clut_is_8bit);
}
void GPU_SW_Backend::DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area)
{
GPU_SW_Rasterizer::g_drawing_area = new_drawing_area;
}
void GPU_SW_Backend::FlushRender()
{
}

View File

@ -1,32 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "gpu.h"
#include "gpu_backend.h"
#include <array>
class GPU_SW_Backend final : public GPUBackend
{
public:
GPU_SW_Backend();
~GPU_SW_Backend() override;
bool Initialize(bool use_thread) override;
void Reset() override;
protected:
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FlushRender() override;
};

View File

@ -45,6 +45,31 @@ CopyVRAMFunction CopyVRAM = nullptr;
GPUDrawingArea g_drawing_area = {};
} // namespace GPU_SW_Rasterizer
void GPU_SW_Rasterizer::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
const u16* const src_row = &g_vram[reg.GetYBase() * VRAM_WIDTH];
const u32 start_x = reg.GetXBase();
if (!clut_is_8bit)
{
// Wraparound can't happen in 4-bit mode.
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * 16);
}
else
{
if ((start_x + 256) > VRAM_WIDTH) [[unlikely]]
{
const u32 end = VRAM_WIDTH - start_x;
const u32 start = 256 - end;
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * end);
std::memcpy(g_gpu_clut + end, src_row, sizeof(u16) * start);
}
else
{
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * 256);
}
}
}
// Default scalar implementation definitions.
namespace GPU_SW_Rasterizer::Scalar {
namespace {

View File

@ -18,12 +18,15 @@ static constexpr u32 DITHER_LUT_SIZE = 512;
using DitherLUT = std::array<std::array<std::array<u8, DITHER_LUT_SIZE>, DITHER_MATRIX_SIZE>, DITHER_MATRIX_SIZE>;
extern const DitherLUT g_dither_lut;
// TODO: Pack in struct
extern GPUDrawingArea g_drawing_area;
extern void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit);
using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd);
typedef const DrawRectangleFunction DrawRectangleFunctionTable[2][2][2];
using DrawTriangleFunction = void (*)(const GPUBackendDrawPolygonCommand* cmd,
using DrawTriangleFunction = void (*)(const GPUBackendDrawCommand* cmd,
const GPUBackendDrawPolygonCommand::Vertex* v0,
const GPUBackendDrawPolygonCommand::Vertex* v1,
const GPUBackendDrawPolygonCommand::Vertex* v2);

View File

@ -966,7 +966,7 @@ struct TrianglePart
#ifndef USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
static void DrawSpan(const GPUBackendDrawCommand* cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep)
{
s32 width = x_bound - x_start;
@ -1006,7 +1006,7 @@ static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start
}
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCommand* cmd, const TrianglePart& tp,
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* cmd, const TrianglePart& tp,
const UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb,
const RGBSteps& rgbstep)
{
@ -1143,7 +1143,7 @@ struct TriangleVectors : PixelVectors<texture_enable>
} // namespace
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
static void DrawSpan(const GPUBackendDrawCommand* cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep,
const TriangleVectors<shading_enable, texture_enable>& tv)
{
@ -1248,7 +1248,7 @@ static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start
}
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCommand* cmd, const TrianglePart& tp,
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* cmd, const TrianglePart& tp,
const UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb,
const RGBSteps& rgbstep)
{
@ -1347,7 +1347,7 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
#endif // USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawTriangle(const GPUBackendDrawPolygonCommand* cmd, const GPUBackendDrawPolygonCommand::Vertex* v0,
static void DrawTriangle(const GPUBackendDrawCommand* cmd, const GPUBackendDrawPolygonCommand::Vertex* v0,
const GPUBackendDrawPolygonCommand::Vertex* v1, const GPUBackendDrawPolygonCommand::Vertex* v2)
{
#ifdef CHECK_VECTOR

1173
src/core/gpu_thread.cpp Normal file

File diff suppressed because it is too large Load Diff

76
src/core/gpu_thread.h Normal file
View File

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/types.h"
#include <functional>
#include <optional>
class Error;
struct WindowInfo;
namespace Threading {
class ThreadHandle;
}
enum class RenderAPI : u8;
enum class GPUVSyncMode : u8;
enum class GPURenderer : u8;
enum class GPUBackendCommandType : u8;
struct GPUThreadCommand;
struct GPUBackendUpdateDisplayCommand;
namespace GPUThread {
using AsyncCallType = std::function<void()>;
/// Starts Big Picture UI.
bool StartFullscreenUI(bool fullscreen, Error* error);
void StopFullscreenUI();
/// Backend control.
std::optional<GPURenderer> GetRequestedRenderer();
bool CreateGPUBackend(GPURenderer renderer, bool upload_vram, bool fullscreen, bool force_recreate_device,
Error* error);
void DestroyGPUBackend();
/// Re-presents the current frame. Call when things like window resizes happen to re-display
/// the current frame with the correct proportions. Should only be called from the CPU thread.
void PresentCurrentFrame();
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
/// Access to main window size from CPU thread.
const WindowInfo& GetRenderWindowInfo();
void UpdateSettings(bool gpu_settings_changed);
void RunOnThread(AsyncCallType func);
void SetVSync(GPUVSyncMode mode, bool allow_present_throttle);
bool GetRunIdleOnThread();
void SetRunIdleOnThread(bool enabled);
GPUThreadCommand* AllocateCommand(GPUBackendCommandType command, u32 size);
void PushCommand(GPUThreadCommand* cmd);
void PushCommandAndWakeThread(GPUThreadCommand* cmd);
void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
void PushCommandAndFrame(GPUBackendUpdateDisplayCommand* cmd);
// NOTE: Only called by GPUBackend
namespace Internal {
const Threading::ThreadHandle& GetThreadHandle();
void ProcessStartup();
void SetThreadEnabled(bool enabled);
void RequestShutdown();
void GPUThreadEntryPoint();
void PresentFrame(bool allow_skip_present, u64 present_time);
} // namespace Internal
} // namespace GPUThread

View File

@ -5,12 +5,24 @@
#include "types.h"
#include "util/gpu_texture.h"
#include "common/bitfield.h"
#include "common/bitutils.h"
#include "common/gsvector.h"
#include <array>
#include <string>
#include <functional>
#include <vector>
class Error;
class StateWrapper;
class MediaCapture;
enum class GPUVSyncMode : u8;
enum : u32
{
@ -308,12 +320,17 @@ union GPUTexturePaletteReg
ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); }
};
struct GPUTextureWindow
union GPUTextureWindow
{
u8 and_x;
u8 and_y;
u8 or_x;
u8 or_y;
struct
{
u8 and_x;
u8 and_y;
u8 or_x;
u8 or_y;
};
u32 bits;
ALWAYS_INLINE bool operator==(const GPUTextureWindow& rhs) const
{
@ -453,17 +470,149 @@ static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-
enum class GPUBackendCommandType : u8
{
Wraparound,
Sync,
AsyncCall,
Reconfigure,
Shutdown,
ClearVRAM,
ClearDisplay,
UpdateDisplay,
BufferSwapped,
UpdateResolutionScale,
RenderScreenshotToBuffer,
RenderScreenshotToFile,
LoadState,
SaveState,
LoadMemoryState,
SaveMemoryState,
ReadVRAM,
FillVRAM,
UpdateVRAM,
CopyVRAM,
SetDrawingArea,
UpdateCLUT,
ClearCache,
DrawPolygon,
DrawPrecisePolygon,
DrawRectangle,
DrawLine,
};
struct GPUThreadCommand
{
u32 size;
GPUBackendCommandType type;
};
struct GPUThreadReconfigureCommand : public GPUThreadCommand
{
Error* error_ptr;
std::optional<GPURenderer> renderer;
std::optional<bool> fullscreen;
std::optional<bool> start_fullscreen_ui;
GPUVSyncMode vsync_mode;
bool allow_present_throttle;
bool force_recreate_device;
bool upload_vram;
bool result;
};
struct GPUThreadAsyncCallCommand : public GPUThreadCommand
{
std::function<void()> func;
};
struct GPUThreadRenderScreenshotToBufferCommand : public GPUThreadCommand
{
u32 width;
u32 height;
u32* out_width;
u32* out_height;
std::vector<u32>* out_pixels;
u32* out_stride;
GPUTexture::Format* out_format;
bool* out_result;
bool postfx;
};
struct GPUThreadRenderScreenshotToFileCommand : public GPUThreadCommand
{
DisplayScreenshotMode mode;
u8 quality;
bool compress_on_thread;
bool show_osd_message;
u32 path_length;
char path[0];
};
struct GPUBackendLoadStateCommand : public GPUThreadCommand
{
GPUDrawingArea drawing_area;
u16 vram_data[VRAM_WIDTH * VRAM_HEIGHT];
u16 clut_data[GPU_CLUT_SIZE];
u32 texture_cache_state_version;
u32 texture_cache_state_size;
u8 texture_cache_state[0]; // texture_cache_state_size
};
struct GPUBackendSaveStateCommand : public GPUThreadCommand
{
StateWrapper* sw;
};
struct GPUBackendLoadMemoryStateCommand : public GPUThreadCommand
{
};
struct GPUBackendSaveMemoryStateCommand : public GPUThreadCommand
{
};
struct GPUBackendUpdateDisplayCommand : public GPUThreadCommand
{
u32 frame_number;
u32 internal_frame_number;
u16 display_width;
u16 display_height;
u16 display_origin_left;
u16 display_origin_top;
u16 display_vram_left;
u16 display_vram_top;
u16 display_vram_width;
u16 display_vram_height;
u16 X; // TODO: Can we get rid of this?
union
{
u16 bits;
BitField<u16, bool, 0, 1> interlaced_display_enabled;
BitField<u16, u8, 1, 1> interlaced_display_field;
BitField<u16, bool, 2, 1> interlaced_display_interleaved;
BitField<u16, bool, 3, 1> display_24bit;
BitField<u16, bool, 4, 1> display_disabled;
BitField<u16, bool, 6, 1> allow_present_skip;
BitField<u16, bool, 7, 1> present_frame;
BitField<u16, bool, 8, 1> is_frame;
};
float display_aspect_ratio;
u64 present_time;
MediaCapture* media_capture;
};
struct GPUBackendReadVRAMCommand : public GPUThreadCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
};
union GPUBackendCommandParameters
{
u8 bits;
@ -489,18 +638,12 @@ union GPUBackendCommandParameters
}
};
struct GPUBackendCommand
// TODO: Merge this into the other structs, saves padding bytes
struct GPUBackendCommand : public GPUThreadCommand
{
u32 size;
GPUBackendCommandType type;
GPUBackendCommandParameters params;
};
struct GPUBackendSyncCommand : public GPUBackendCommand
{
bool allow_sleep;
};
struct GPUBackendFillVRAMCommand : public GPUBackendCommand
{
u16 x;
@ -532,7 +675,6 @@ struct GPUBackendCopyVRAMCommand : public GPUBackendCommand
struct GPUBackendSetDrawingAreaCommand : public GPUBackendCommand
{
GPUDrawingArea new_area;
s32 new_clamped_area[4];
};
struct GPUBackendUpdateCLUTCommand : public GPUBackendCommand
@ -541,8 +683,10 @@ struct GPUBackendUpdateCLUTCommand : public GPUBackendCommand
bool clut_is_8bit;
};
// TODO: Pack texpage
struct GPUBackendDrawCommand : public GPUBackendCommand
{
// TODO: Cut this down
GPUDrawModeReg draw_mode;
GPURenderCommand rc;
GPUTexturePaletteReg palette;
@ -551,7 +695,7 @@ struct GPUBackendDrawCommand : public GPUBackendCommand
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
{
u16 num_vertices;
u8 num_vertices;
struct Vertex
{
@ -572,14 +716,22 @@ struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
};
u16 texcoord;
};
};
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_, u16 texcoord_)
{
x = x_;
y = y_;
color = color_;
texcoord = texcoord_;
}
Vertex vertices[0];
};
struct GPUBackendDrawPrecisePolygonCommand : public GPUBackendDrawCommand
{
u8 num_vertices;
bool valid_w;
struct Vertex
{
float x, y, w;
s32 native_x, native_y;
u32 color;
u16 texcoord;
};
Vertex vertices[0];
@ -587,9 +739,9 @@ struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
struct GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
{
s32 x, y;
u16 width, height;
u16 texcoord;
s32 x, y;
u32 color;
};

View File

@ -2,19 +2,13 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "host.h"
#include "fullscreen_ui.h"
#include "gpu.h"
#include "imgui_overlays.h"
#include "shader_cache_version.h"
#include "system.h"
#include "system_private.h"
#include "scmversion/scmversion.h"
#include "util/compress_helpers.h"
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h"
#include "common/assert.h"
#include "common/error.h"
@ -342,175 +336,3 @@ std::string Host::GetHTTPUserAgent()
{
return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str);
}
bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error)
{
DebugAssert(!g_gpu_device);
INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
g_gpu_device = GPUDevice::CreateDeviceForAPI(api);
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device && g_gpu_device->SupportsExclusiveFullscreen())
{
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
exclusive_fullscreen_control =
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
}
u32 disabled_features = 0;
if (g_settings.gpu_disable_dual_source_blend)
disabled_features |= GPUDevice::FEATURE_MASK_DUAL_SOURCE_BLEND;
if (g_settings.gpu_disable_framebuffer_fetch)
disabled_features |= GPUDevice::FEATURE_MASK_FRAMEBUFFER_FETCH;
if (g_settings.gpu_disable_texture_buffers)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_BUFFERS;
if (g_settings.gpu_disable_memory_import)
disabled_features |= GPUDevice::FEATURE_MASK_MEMORY_IMPORT;
if (g_settings.gpu_disable_raster_order_views)
disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS;
// Don't dump shaders on debug builds for Android, users will complain about storage...
#if !defined(__ANDROID__) || defined(_DEBUG)
const std::string_view shader_dump_directory(EmuFolders::DataRoot);
#else
const std::string_view shader_dump_directory;
#endif
Error create_error;
std::optional<WindowInfo> wi;
if (!g_gpu_device ||
!(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() ||
!g_gpu_device->Create(
g_settings.gpu_adapter, static_cast<GPUDevice::FeatureMask>(disabled_features), shader_dump_directory,
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, wi.value(), System::GetEffectiveVSyncMode(),
System::ShouldAllowPresentThrottle(), fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
if (wi.has_value())
Host::ReleaseRenderWindow();
Error::SetStringFmt(
error,
TRANSLATE_FS("System", "Failed to create render device:\n\n{0}\n\nThis may be due to your GPU not supporting the "
"chosen renderer ({1}), or because your graphics drivers need to be updated."),
create_error.GetDescription(), GPUDevice::RenderAPIToString(api));
return false;
}
if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_osd_margin, &create_error))
{
ERROR_LOG("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
return false;
}
InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight());
return true;
}
void Host::UpdateDisplayWindow(bool fullscreen)
{
if (!g_gpu_device)
return;
const GPUVSyncMode vsync_mode = System::GetEffectiveVSyncMode();
const bool allow_present_throttle = System::ShouldAllowPresentThrottle();
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device->SupportsExclusiveFullscreen())
{
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
exclusive_fullscreen_control =
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
}
g_gpu_device->DestroyMainSwapChain();
Error error;
std::optional<WindowInfo> wi =
Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error);
if (!wi.has_value())
{
Host::ReportFatalError("Failed to get render window after update", error.GetDescription());
return;
}
// if surfaceless, just leave it
if (wi->IsSurfaceless())
return;
if (!g_gpu_device->RecreateMainSwapChain(wi.value(), vsync_mode, allow_present_throttle,
fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &error))
{
Host::ReportFatalError("Failed to change window after update", error.GetDescription());
return;
}
const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
const float f_width = static_cast<float>(new_width);
const float f_height = static_cast<float>(new_height);
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized(new_width, new_height);
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return;
DEV_LOG("Display window resized to {}x{}", width, height);
Error error;
if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error))
{
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
UpdateDisplayWindow(Host::IsFullscreen());
return;
}
const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
const float f_width = static_cast<float>(new_width);
const float f_height = static_cast<float>(new_height);
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized(new_width, new_height);
}
void Host::ReleaseGPUDevice()
{
if (!g_gpu_device)
return;
ImGuiManager::DestroyAllDebugWindows();
ImGuiManager::DestroyOverlayTextures();
FullscreenUI::Shutdown();
ImGuiManager::Shutdown();
INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
}

View File

@ -96,21 +96,6 @@ bool IsFullscreen();
/// Alters fullscreen state of hosting application.
void SetFullscreen(bool enabled);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
/// Destroys any active rendering device.
void ReleaseGPUDevice();
/// Called at the end of the frame, before presentation.
void FrameDone();
namespace Internal {
/// Returns true if the host should use portable mode.

View File

@ -8,6 +8,7 @@
#include "fullscreen_ui.h"
#include "gpu.h"
#include "gpu_hw_texture_cache.h"
#include "gpu_thread.h"
#include "host.h"
#include "imgui_overlays.h"
#include "settings.h"
@ -58,8 +59,7 @@ static void HotkeyModifyResolutionScale(s32 increment)
if (System::IsValid())
{
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
GPUThread::UpdateSettings(true);
System::ClearMemorySaveStates();
}
}
@ -386,11 +386,10 @@ DEFINE_HOTKEY("TogglePGXP", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOO
[](s32 pressed) {
if (!pressed && System::IsValid())
{
Settings old_settings = g_settings;
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
System::ClearMemorySaveStates();
GPUThread::UpdateSettings(true);
Host::AddKeyedOSDMessage("TogglePGXP",
g_settings.gpu_pgxp_enable ?
TRANSLATE_STR("OSDMessage", "PGXP is now enabled.") :
@ -459,12 +458,11 @@ DEFINE_HOTKEY("TogglePGXPDepth", TRANSLATE_NOOP("Hotkeys", "Graphics"),
if (!g_settings.gpu_pgxp_enable)
return;
const Settings old_settings = g_settings;
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
System::ClearMemorySaveStates();
GPUThread::UpdateSettings(true);
Host::AddKeyedOSDMessage("TogglePGXPDepth",
g_settings.gpu_pgxp_depth_buffer ?
TRANSLATE_STR("OSDMessage", "PGXP Depth Buffer is now enabled.") :
@ -480,12 +478,11 @@ DEFINE_HOTKEY("TogglePGXPCPU", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_
if (!g_settings.gpu_pgxp_enable)
return;
const Settings old_settings = g_settings;
g_settings.gpu_pgxp_cpu = !g_settings.gpu_pgxp_cpu;
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
// GPU thread is unchanged
System::ClearMemorySaveStates();
Host::AddKeyedOSDMessage("TogglePGXPCPU",
g_settings.gpu_pgxp_cpu ?
TRANSLATE_STR("OSDMessage", "PGXP CPU mode is now enabled.") :
@ -595,29 +592,29 @@ DEFINE_HOTKEY("AudioVolumeDown", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_N
DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) {
if (!pressed)
Host::RunOnCPUThread(SaveStateSelectorUI::LoadCurrentSlot);
GPUThread::RunOnThread(SaveStateSelectorUI::LoadCurrentSlot);
})
DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), [](s32 pressed) {
if (!pressed)
Host::RunOnCPUThread(SaveStateSelectorUI::SaveCurrentSlot);
GPUThread::RunOnThread(SaveStateSelectorUI::SaveCurrentSlot);
})
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
if (!pressed)
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
})
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
if (!pressed)
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
})
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
if (!pressed && System::IsValid())
{
SaveStateSelectorUI::SaveCurrentSlot();
SaveStateSelectorUI::SelectNextSlot(false);
GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectNextSlot(false); });
}
})

View File

@ -9,6 +9,8 @@
#include "dma.h"
#include "fullscreen_ui.h"
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "host.h"
#include "mdec.h"
#include "performance_counters.h"
@ -70,10 +72,10 @@ struct DebugWindowInfo
} // namespace
static void FormatProcessorStat(SmallStringBase& text, double usage, double time);
static void DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing);
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);
static void DrawEnhancementsOverlay();
static void DrawEnhancementsOverlay(const GPUBackend* gpu);
static void DrawInputsOverlay();
#ifndef __ANDROID__
@ -284,26 +286,25 @@ void ImGuiManager::DestroyAllDebugWindows()
#endif
}
void ImGuiManager::RenderTextOverlays()
void ImGuiManager::RenderTextOverlays(const GPUBackend* gpu)
{
// NOTE: Racey read.
const System::State state = System::GetState();
if (state != System::State::Shutdown)
{
const float scale = ImGuiManager::GetGlobalScale();
const float f_margin = ImGuiManager::GetScreenMargin() * scale;
const float margin = ImCeil(ImGuiManager::GetScreenMargin() * scale);
const float spacing = ImCeil(5.0f * scale);
float position_y = ImFloor(f_margin);
DrawPerformanceOverlay(position_y, scale, margin, spacing);
DrawFrameTimeOverlay(position_y, scale, margin, spacing);
DrawMediaCaptureOverlay(position_y, scale, margin, spacing);
if (g_settings.display_show_enhancements && state != System::State::Paused)
DrawEnhancementsOverlay();
const float scale = ImGuiManager::GetGlobalScale();
const float f_margin = ImGuiManager::GetScreenMargin() * scale;
const float margin = ImCeil(ImGuiManager::GetScreenMargin() * scale);
const float spacing = ImCeil(5.0f * scale);
float position_y = ImFloor(f_margin);
DrawPerformanceOverlay(gpu, position_y, scale, margin, spacing);
DrawFrameTimeOverlay(position_y, scale, margin, spacing);
DrawMediaCaptureOverlay(position_y, scale, margin, spacing);
if (g_settings.display_show_inputs && state != System::State::Paused)
DrawInputsOverlay();
}
if (g_gpu_settings.display_show_enhancements && state != System::State::Paused)
DrawEnhancementsOverlay(gpu);
if (g_gpu_settings.display_show_inputs && state != System::State::Paused)
DrawInputsOverlay();
}
void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, double time)
@ -317,11 +318,12 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub
text.append_format("{:.1f}% ({:.2f}ms)", usage, time);
}
void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing)
void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position_y, float scale, float margin,
float spacing)
{
if (!(g_settings.display_show_fps || g_settings.display_show_speed || g_settings.display_show_gpu_stats ||
g_settings.display_show_resolution || g_settings.display_show_cpu_usage ||
(g_settings.display_show_status_indicators &&
if (!(g_gpu_settings.display_show_fps || g_gpu_settings.display_show_speed || g_gpu_settings.display_show_gpu_stats ||
g_gpu_settings.display_show_resolution || g_gpu_settings.display_show_cpu_usage ||
(g_gpu_settings.display_show_status_indicators &&
(System::IsPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
{
return;
@ -352,9 +354,9 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
if (state == System::State::Running)
{
const float speed = PerformanceCounters::GetEmulationSpeed();
if (g_settings.display_show_fps)
if (g_gpu_settings.display_show_fps)
text.append_format("G: {:.2f} | V: {:.2f}", PerformanceCounters::GetFPS(), PerformanceCounters::GetVPS());
if (g_settings.display_show_speed)
if (g_gpu_settings.display_show_speed)
{
text.append_format("{}{}%", text.empty() ? "" : " | ", static_cast<u32>(std::round(speed)));
@ -377,19 +379,19 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
DRAW_LINE(fixed_font, text, color);
}
if (g_settings.display_show_gpu_stats)
if (g_gpu_settings.display_show_gpu_stats)
{
g_gpu->GetStatsString(text);
gpu->GetStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
g_gpu->GetMemoryStatsString(text);
gpu->GetMemoryStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_resolution)
if (g_gpu_settings.display_show_resolution)
{
const u32 resolution_scale = g_gpu->GetResolutionScale();
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution();
const u32 resolution_scale = gpu->GetResolutionScale();
const auto [display_width, display_height] = gpu->GetFullDisplayResolution();
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
const bool pal = g_gpu->IsInPALMode();
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
@ -397,13 +399,13 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_latency_stats)
if (g_gpu_settings.display_show_latency_stats)
{
System::FormatLatencyStats(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_cpu_usage)
if (g_gpu_settings.display_show_cpu_usage)
{
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", PerformanceCounters::GetMinimumFrameTime(),
PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime());
@ -459,11 +461,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
PerformanceCounters::GetCPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
if (g_gpu->GetSWThread())
if (g_gpu_settings.gpu_use_thread)
{
text.assign("SW: ");
FormatProcessorStat(text, PerformanceCounters::GetSWThreadUsage(),
PerformanceCounters::GetSWThreadAverageTime());
text.assign("RNDR: ");
FormatProcessorStat(text, PerformanceCounters::GetGPUThreadUsage(),
PerformanceCounters::GetGPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
@ -477,14 +479,14 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
#endif
}
if (g_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled())
if (g_gpu_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled())
{
text.assign("GPU: ");
FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_status_indicators)
if (g_gpu_settings.display_show_status_indicators)
{
const bool rewinding = System::IsRewinding();
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
@ -494,7 +496,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
}
}
}
else if (g_settings.display_show_status_indicators && state == System::State::Paused &&
else if (g_gpu_settings.display_show_status_indicators && state == System::State::Paused &&
!FullscreenUI::HasActiveWindow())
{
text.assign(ICON_EMOJI_PAUSE);
@ -504,12 +506,12 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
#undef DRAW_LINE
}
void ImGuiManager::DrawEnhancementsOverlay()
void ImGuiManager::DrawEnhancementsOverlay(const GPUBackend* gpu)
{
LargeString text;
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
g_gpu->IsHardwareRenderer() ? "HW" : "SW");
gpu->IsHardwareRenderer() ? "HW" : "SW");
if (g_settings.rewind_enable)
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
@ -953,7 +955,10 @@ void SaveStateSelectorUI::ClearList()
for (ListEntry& li : s_slots)
{
if (li.preview_texture)
g_gpu_device->RecycleTexture(std::move(li.preview_texture));
{
GPUThread::RunOnThread(
[tex = li.preview_texture.release()]() { g_gpu_device->RecycleTexture(std::unique_ptr<GPUTexture>(tex)); });
}
}
s_slots.clear();
}
@ -1273,7 +1278,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
}
}
Close();
GPUThread::RunOnThread(&Close);
}
void SaveStateSelectorUI::SaveCurrentSlot()
@ -1290,7 +1295,7 @@ void SaveStateSelectorUI::SaveCurrentSlot()
}
}
Close();
GPUThread::RunOnThread(&Close);
}
void SaveStateSelectorUI::ShowSlotOSDMessage()
@ -1314,7 +1319,7 @@ void SaveStateSelectorUI::ShowSlotOSDMessage()
void ImGuiManager::RenderOverlayWindows()
{
const System::State state = System::GetState();
if (state != System::State::Shutdown)
if (state == System::State::Paused || state == System::State::Running)
{
if (SaveStateSelectorUI::s_open)
SaveStateSelectorUI::Draw();

View File

@ -7,8 +7,10 @@
#include <string>
class GPUBackend;
namespace ImGuiManager {
void RenderTextOverlays();
void RenderTextOverlays(const GPUBackend* gpu);
void RenderDebugWindows();
bool UpdateDebugWindowConfig();
void DestroyAllDebugWindows();

View File

@ -3,6 +3,8 @@
#include "performance_counters.h"
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "system.h"
#include "system_private.h"
@ -45,9 +47,9 @@ struct State
float cpu_thread_usage;
float cpu_thread_time;
u64 last_sw_time;
float sw_thread_usage;
float sw_thread_time;
u64 last_gpu_thread_time;
float gpu_thread_usage;
float gpu_thread_time;
float average_gpu_time;
float accumulated_gpu_time;
@ -105,14 +107,14 @@ float PerformanceCounters::GetCPUThreadAverageTime()
return s_state.cpu_thread_time;
}
float PerformanceCounters::GetSWThreadUsage()
float PerformanceCounters::GetGPUThreadUsage()
{
return s_state.sw_thread_usage;
return s_state.gpu_thread_usage;
}
float PerformanceCounters::GetSWThreadAverageTime()
float PerformanceCounters::GetGPUThreadAverageTime()
{
return s_state.sw_thread_time;
return s_state.gpu_thread_time;
}
float PerformanceCounters::GetGPUUsage()
@ -150,17 +152,16 @@ void PerformanceCounters::Reset()
s_state.last_frame_number = System::GetFrameNumber();
s_state.last_internal_frame_number = System::GetInternalFrameNumber();
s_state.last_cpu_time = System::GetCPUThreadHandle().GetCPUTime();
if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread)
s_state.last_sw_time = sw_thread->GetCPUTime();
else
s_state.last_sw_time = 0;
s_state.last_gpu_thread_time = GPUThread::Internal::GetThreadHandle().GetCPUTime();
s_state.average_frame_time_accumulator = 0.0f;
s_state.minimum_frame_time_accumulator = 0.0f;
s_state.maximum_frame_time_accumulator = 0.0f;
std::atomic_thread_fence(std::memory_order_release);
}
void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
void PerformanceCounters::Update(GPUBackend* gpu, u32 frame_number, u32 internal_frame_number)
{
const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue();
@ -177,7 +178,7 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
// update fps counter
const Common::Timer::Value ticks_diff = now_ticks - s_state.last_update_time;
const float time = static_cast<float>(Common::Timer::ConvertValueToSeconds(ticks_diff));
if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL)
if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL || s_state.last_frame_number == frame_number)
return;
s_state.last_update_time = now_ticks;
@ -202,18 +203,17 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
s_state.fps = static_cast<float>(internal_frames_run) / time;
s_state.speed = (s_state.vps / System::GetVideoFrameRate()) * 100.0f;
const Threading::Thread* sw_thread = g_gpu->GetSWThread();
const u64 cpu_time = System::GetCPUThreadHandle().GetCPUTime();
const u64 sw_time = sw_thread ? sw_thread->GetCPUTime() : 0;
const u64 gpu_thread_time = GPUThread::Internal::GetThreadHandle().GetCPUTime();
const u64 cpu_delta = cpu_time - s_state.last_cpu_time;
const u64 sw_delta = sw_time - s_state.last_sw_time;
const u64 gpu_thread_delta = gpu_thread_time - s_state.last_gpu_thread_time;
s_state.last_cpu_time = cpu_time;
s_state.last_sw_time = sw_time;
s_state.last_gpu_thread_time = gpu_thread_time;
s_state.cpu_thread_usage = static_cast<float>(static_cast<double>(cpu_delta) * pct_divider);
s_state.cpu_thread_time = static_cast<float>(static_cast<double>(cpu_delta) * time_divider);
s_state.sw_thread_usage = static_cast<float>(static_cast<double>(sw_delta) * pct_divider);
s_state.sw_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider);
s_state.gpu_thread_usage = static_cast<float>(static_cast<double>(gpu_thread_delta) * pct_divider);
s_state.gpu_thread_time = static_cast<float>(static_cast<double>(gpu_thread_delta) * time_divider);
if (MediaCapture* cap = System::GetMediaCapture())
cap->UpdateCaptureThreadUsage(pct_divider, time_divider);
@ -228,13 +228,13 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
s_state.presents_since_last_update = 0;
if (g_settings.display_show_gpu_stats)
g_gpu->UpdateStatistics(frames_run);
gpu->UpdateStatistics(frames_run);
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms",
s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time,
s_state.minimum_frame_time, s_state.maximum_frame_time);
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} RNDR: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms",
s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_thread_usage, s_state.gpu_usage,
s_state.average_frame_time, s_state.minimum_frame_time, s_state.maximum_frame_time);
Host::OnPerformanceCountersUpdated();
Host::OnPerformanceCountersUpdated(gpu);
}
void PerformanceCounters::AccumulateGPUTime()

View File

@ -5,6 +5,8 @@
#include "common/types.h"
class GPUBackend;
namespace PerformanceCounters
{
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
@ -18,8 +20,8 @@ float GetMinimumFrameTime();
float GetMaximumFrameTime();
float GetCPUThreadUsage();
float GetCPUThreadAverageTime();
float GetSWThreadUsage();
float GetSWThreadAverageTime();
float GetGPUThreadUsage();
float GetGPUThreadAverageTime();
float GetGPUUsage();
float GetGPUAverageTime();
const FrameTimeHistory& GetFrameTimeHistory();
@ -27,7 +29,7 @@ u32 GetFrameTimeHistoryPos();
void Clear();
void Reset();
void Update(u32 frame_number, u32 internal_frame_number);
void Update(GPUBackend* gpu, u32 frame_number, u32 internal_frame_number);
void AccumulateGPUTime();
} // namespace Host

View File

@ -28,7 +28,8 @@
LOG_CHANNEL(Settings);
Settings g_settings;
ALIGN_TO_CACHE_LINE Settings g_settings;
ALIGN_TO_CACHE_LINE Settings g_gpu_settings;
const char* SettingInfo::StringDefaultValue() const
{
@ -204,6 +205,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_disable_raster_order_views = si.GetBoolValue("GPU", "DisableRasterOrderViews", false);
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
gpu_max_queued_frames = static_cast<u8>(si.GetUIntValue("GPU", "MaxQueuedFrames", DEFAULT_GPU_MAX_QUEUED_FRAMES));
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
@ -533,6 +535,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
}
si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading);
si.SetUIntValue("GPU", "MaxQueuedFrames", gpu_max_queued_frames);
si.SetBoolValue("GPU", "UseThread", gpu_use_thread);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "TrueColor", gpu_true_color);

View File

@ -98,6 +98,7 @@ struct Settings
std::string gpu_adapter;
u8 gpu_resolution_scale = 1;
u8 gpu_multisamples = 1;
u8 gpu_max_queued_frames = 2;
bool gpu_use_thread : 1 = true;
bool gpu_use_software_renderer_for_readbacks : 1 = false;
bool gpu_use_debug_device : 1 = false;
@ -486,6 +487,8 @@ struct Settings
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
static constexpr u8 DEFAULT_GPU_MAX_QUEUED_FRAMES = 2; // TODO: Maybe lower? But that means fast CPU threads would
// always stall, could be a problem for power management.
// Prefer oldrec over newrec for now. Except on RISC-V, where there is no oldrec.
#if defined(CPU_ARCH_RISCV64)
@ -552,7 +555,9 @@ struct Settings
#endif
};
extern Settings g_settings;
// TODO: Use smaller copy for GPU thread copy.
ALIGN_TO_CACHE_LINE extern Settings g_settings; // CPU thread copy.
ALIGN_TO_CACHE_LINE extern Settings g_gpu_settings; // GPU thread copy.
namespace EmuFolders {
extern std::string AppRoot;

File diff suppressed because it is too large Load Diff

View File

@ -159,7 +159,6 @@ std::string GetGameSettingsPath(std::string_view game_serial);
std::string GetInputProfilePath(std::string_view name);
State GetState();
void SetState(State new_state);
bool IsRunning();
bool IsPaused();
bool IsShutdown();
@ -272,6 +271,8 @@ bool IsRunningAtNonStandardSpeed();
float GetVideoFrameRate();
void SetVideoFrameRate(float frequency);
void GetFramePresentationDetails(bool* is_frame, bool* present_frame, bool* allow_present_skip, u64* present_time);
// Access controllers for simulating input.
Controller* GetController(u32 slot);
void UpdateMemoryCardTypes();
@ -375,7 +376,7 @@ s32 GetAudioOutputVolume();
void UpdateVolume();
/// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically.
bool SaveScreenshot(const char* path = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode,
void SaveScreenshot(const char* path = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode,
DisplayScreenshotFormat format = g_settings.display_screenshot_format,
u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
@ -404,10 +405,6 @@ void ToggleSoftwareRendering();
/// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x.
void RequestDisplaySize(float scale = 0.0f);
/// Renders the display.
bool PresentDisplay(bool explicit_present, u64 present_time);
void InvalidateDisplay();
//////////////////////////////////////////////////////////////////////////
// Memory Save States (Rewind and Runahead)
//////////////////////////////////////////////////////////////////////////

View File

@ -86,11 +86,8 @@ void OnSystemPaused();
/// Called when the VM is resumed after being paused.
void OnSystemResumed();
/// Called when the pause state changes, or fullscreen UI opens.
void OnIdleStateChanged();
/// Called when performance metrics are updated, approximately once a second.
void OnPerformanceCountersUpdated();
void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend);
/// Provided by the host; called when the running executable changes.
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);

View File

@ -604,8 +604,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
"replacement or an overlay."));
dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
tr("Uses a second thread for drawing graphics. Currently only available for the software "
"renderer, but can provide a significant speed improvement, and is safe to use."));
tr("Uses a second thread for drawing graphics. Provides a significant speed improvement "
"particularly with the software renderer, and is safe to use."));
dialog->registerWidgetHelp(
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
@ -807,8 +807,6 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11);
#endif
m_ui.gpuThread->setEnabled(!is_hardware);
m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 ||
render_api == RenderAPI::Vulkan);
m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan);

View File

@ -83,6 +83,7 @@ static bool s_use_central_widget = false;
// UI thread VM validity.
static bool s_system_valid = false;
static bool s_system_paused = false;
static bool s_fullscreen_ui_started = false;
static std::atomic_uint32_t s_system_locked{false};
static QString s_current_game_title;
static QString s_current_game_serial;
@ -762,7 +763,7 @@ void MainWindow::recreate()
{
g_emu_thread->setSurfaceless(false);
g_main_window->updateEmulationActions(false, System::IsValid(), Achievements::IsHardcoreModeActive());
g_main_window->onFullscreenUIStateChange(g_emu_thread->isRunningFullscreenUI());
g_main_window->onFullscreenUIStartedOrStopped(s_fullscreen_ui_started);
}
if (controller_settings_window_pos.has_value())
@ -1252,8 +1253,9 @@ void MainWindow::onStartFullscreenUITriggered()
g_emu_thread->startFullscreenUI();
}
void MainWindow::onFullscreenUIStateChange(bool running)
void MainWindow::onFullscreenUIStartedOrStopped(bool running)
{
s_fullscreen_ui_started = running;
m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture Mode"));
m_ui.actionStartFullscreenUI2->setText(running ? tr("Exit Big Picture") : tr("Big Picture"));
}
@ -1999,7 +2001,7 @@ void MainWindow::connectSignals()
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);
connect(g_emu_thread, &EmuThread::fullscreenUIStateChange, this, &MainWindow::onFullscreenUIStateChange);
connect(g_emu_thread, &EmuThread::fullscreenUIStartedOrStopped, this, &MainWindow::onFullscreenUIStartedOrStopped);
connect(g_emu_thread, &EmuThread::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
&MainWindow::onAchievementsChallengeModeChanged);
@ -2452,7 +2454,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
// reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches
// would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in
// batch mode, when we're going to exit anyway.
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !g_emu_thread->isRunningFullscreenUI())
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !s_fullscreen_ui_started)
updateWindowState(true);
// Now we can actually shut down the VM.

View File

@ -167,7 +167,7 @@ private Q_SLOTS:
void onCheatsActionTriggered();
void onCheatsMenuAboutToShow();
void onStartFullscreenUITriggered();
void onFullscreenUIStateChange(bool running);
void onFullscreenUIStartedOrStopped(bool running);
void onRemoveDiscActionTriggered();
void onViewToolbarActionToggled(bool checked);
void onViewLockToolbarActionToggled(bool checked);

View File

@ -19,7 +19,9 @@
#include "core/game_list.h"
#include "core/gdb_server.h"
#include "core/gpu.h"
#include "core/gpu_backend.h"
#include "core/gpu_hw_texture_cache.h"
#include "core/gpu_thread.h"
#include "core/host.h"
#include "core/imgui_overlays.h"
#include "core/memory_card.h"
@ -87,6 +89,20 @@ static constexpr u32 GDB_SERVER_POLLING_INTERVAL = 1;
// Local function declarations
//////////////////////////////////////////////////////////////////////////
namespace QtHost {
namespace {
class GPUThread : public QThread
{
public:
GPUThread(QObject* parent = nullptr);
~GPUThread() override;
void run() override;
};
} // namespace
static bool PerformEarlyHardwareChecks();
static bool EarlyProcessStartup();
static void RegisterTypes();
@ -565,13 +581,8 @@ void Host::LoadSettings(const SettingsInterface& si, std::unique_lock<std::mutex
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
if (g_main_window)
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
if (System::IsValid())
updatePerformanceCounters();
}
// don't mess with fullscreen while locked
if (!QtHost::IsSystemLocked())
{
@ -580,7 +591,7 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
m_is_rendering_to_main = render_to_main;
if (g_gpu_device)
Host::UpdateDisplayWindow(m_is_fullscreen);
GPUThread::UpdateDisplayWindow(m_is_fullscreen);
}
}
}
@ -708,33 +719,24 @@ void EmuThread::startFullscreenUI()
return;
}
if (System::IsValid())
if (System::IsValid() || m_is_fullscreen_ui_started)
return;
// we want settings loaded so we choose the correct renderer
// this also sorts out input sources.
System::LoadSettings(false);
m_is_rendering_to_main = shouldRenderToMain();
m_run_fullscreen_ui = true;
// borrow the game start fullscreen flag
const bool start_fullscreen =
(s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
Error error;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), start_fullscreen, &error) ||
!FullscreenUI::Initialize())
if (!GPUThread::StartFullscreenUI(start_fullscreen, &error))
{
Host::ReportErrorAsync("Error", error.GetDescription());
m_run_fullscreen_ui = false;
return;
}
emit fullscreenUIStateChange(true);
// poll more frequently so we don't lose events
stopBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
}
void EmuThread::stopFullscreenUI()
@ -749,18 +751,8 @@ void EmuThread::stopFullscreenUI()
return;
}
setFullscreen(false, true);
if (m_run_fullscreen_ui)
{
m_run_fullscreen_ui = false;
emit fullscreenUIStateChange(false);
}
if (!g_gpu_device)
return;
Host::ReleaseGPUDevice();
if (m_is_fullscreen_ui_started)
GPUThread::StopFullscreenUI();
}
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -867,7 +859,7 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
{
Host::ResizeDisplayWindow(width, height, scale);
GPUThread::ResizeDisplayWindow(width, height, scale);
}
void EmuThread::redrawDisplayWindow()
@ -878,10 +870,10 @@ void EmuThread::redrawDisplayWindow()
return;
}
if (!g_gpu_device || System::IsShutdown())
if (System::IsShutdown())
return;
System::InvalidateDisplay();
GPUThread::PresentCurrentFrame();
}
void EmuThread::toggleFullscreen()
@ -909,7 +901,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
m_is_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow(fullscreen);
GPUThread::UpdateDisplayWindow(fullscreen);
}
bool Host::IsFullscreen()
@ -938,7 +930,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return;
m_is_surfaceless = surfaceless;
Host::UpdateDisplayWindow(false);
GPUThread::UpdateDisplayWindow(false);
}
void EmuThread::requestDisplaySize(float scale)
@ -995,6 +987,7 @@ void Host::OnSystemStarting()
void Host::OnSystemStarted()
{
g_emu_thread->stopBackgroundControllerPollTimer();
g_emu_thread->wakeThread();
emit g_emu_thread->systemStarted();
}
@ -1012,6 +1005,7 @@ void Host::OnSystemResumed()
g_emu_thread->setSurfaceless(false);
emit g_emu_thread->systemResumed();
g_emu_thread->wakeThread();
g_emu_thread->stopBackgroundControllerPollTimer();
}
@ -1023,9 +1017,14 @@ void Host::OnSystemDestroyed()
emit g_emu_thread->systemDestroyed();
}
void Host::OnIdleStateChanged()
void Host::OnFullscreenUIStartedOrStopped(bool started)
{
g_emu_thread->wakeThread();
g_emu_thread->setFullscreenUIStarted(started);
}
void Host::OnFullscreenUIActiveChanged(bool is_active)
{
g_emu_thread->setFullscreenUIActive(is_active);
}
void EmuThread::reloadInputSources()
@ -1679,7 +1678,8 @@ void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32*
*height = size.height();
// eat all pending events, to make sure we're not going to write input events back to a dead pointer
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
if (g_emu_thread->isCurrentThread())
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
}
void EmuThread::queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
@ -1699,10 +1699,12 @@ void EmuThread::processAuxiliaryRenderWindowInputEvent(void* userdata, quint32 e
quint32 param3)
{
DebugAssert(isCurrentThread());
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
GPUThread::RunOnThread([userdata, event, param1, param2, param3]() {
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
});
}
void EmuThread::doBackgroundControllerPoll()
@ -1731,7 +1733,7 @@ void EmuThread::startBackgroundControllerPollTimer()
return;
u32 poll_interval = BACKGROUND_CONTROLLER_POLLING_INTERVAL;
if (FullscreenUI::IsInitialized())
if (m_is_fullscreen_ui_active)
poll_interval = FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL;
if (GDBServer::HasAnyClients())
poll_interval = GDB_SERVER_POLLING_INTERVAL;
@ -1747,6 +1749,27 @@ void EmuThread::stopBackgroundControllerPollTimer()
m_background_controller_polling_timer->stop();
}
void EmuThread::setFullscreenUIActive(bool active)
{
m_is_fullscreen_ui_active = active;
// adjust the timer speed to pick up controller input faster
if (!m_background_controller_polling_timer->isActive())
return;
g_emu_thread->stopBackgroundControllerPollTimer();
g_emu_thread->startBackgroundControllerPollTimer();
}
void EmuThread::setFullscreenUIStarted(bool started)
{
if (m_is_fullscreen_ui_started == started)
return;
m_is_fullscreen_ui_started = started;
emit fullscreenUIStartedOrStopped(started);
}
void EmuThread::start()
{
AssertMsg(!g_emu_thread, "Emu thread does not exist");
@ -1790,49 +1813,52 @@ void EmuThread::run()
}
}
// bind buttons/axises
createBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
// main loop
while (!m_shutdown_flag)
{
if (System::IsRunning())
// kick off GPU thread
QtHost::GPUThread gpu_thread;
gpu_thread.start();
// bind buttons/axises
createBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
// main loop
while (!m_shutdown_flag)
{
System::Execute();
}
else
{
// we want to keep rendering the UI when paused and fullscreen UI is enabled
if (!FullscreenUI::HasActiveWindow() && !System::IsRunning())
{
// wait until we have a system before running
if (System::IsRunning())
System::Execute();
else
m_event_loop->exec();
continue;
}
m_event_loop->processEvents(QEventLoop::AllEvents);
System::IdlePollUpdate();
if (g_gpu_device && g_gpu_device->HasMainSwapChain())
{
System::PresentDisplay(false, 0);
if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking())
g_gpu_device->GetMainSwapChain()->ThrottlePresentation();
}
}
if (System::IsValid())
System::ShutdownSystem(false);
destroyBackgroundControllerPollTimer();
// tell GPU thread to exit
GPUThread::Internal::RequestShutdown();
// and tidy up everything left
System::CPUThreadShutdown();
}
if (System::IsValid())
System::ShutdownSystem(false);
destroyBackgroundControllerPollTimer();
System::CPUThreadShutdown();
// move back to UI thread
moveToThread(m_ui_thread);
}
void Host::FrameDone()
QtHost::GPUThread::GPUThread(QObject* parent) : QThread(parent)
{
}
QtHost::GPUThread::~GPUThread() = default;
void QtHost::GPUThread::run()
{
::GPUThread::Internal::GPUThreadEntryPoint();
}
void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
{
}
@ -1921,7 +1947,7 @@ void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view
{
emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name));
if (System::IsValid() || g_emu_thread->isRunningFullscreenUI())
if (System::IsValid() || g_emu_thread->isFullscreenUIActive())
{
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier),
@ -1947,7 +1973,7 @@ void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view ident
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, std::move(message),
Host::OSD_WARNING_DURATION);
}
else if (System::IsValid() || g_emu_thread->isRunningFullscreenUI())
else if (System::IsValid() || g_emu_thread->isFullscreenUIActive())
{
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier),
@ -2012,17 +2038,17 @@ void Host::ReleaseRenderWindow()
g_emu_thread->releaseRenderWindow();
}
void EmuThread::updatePerformanceCounters()
void EmuThread::updatePerformanceCounters(const GPUBackend* gpu_backend)
{
const RenderAPI render_api = g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::None;
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer();
const RenderAPI render_api = g_gpu_device->GetRenderAPI();
const bool hardware_renderer = gpu_backend->IsHardwareRenderer();
u32 render_width = 0;
u32 render_height = 0;
if (g_gpu)
if (gpu_backend)
{
const u32 render_scale = g_gpu->GetResolutionScale();
std::tie(render_width, render_height) = g_gpu->GetFullDisplayResolution();
const u32 render_scale = gpu_backend->GetResolutionScale();
std::tie(render_width, render_height) = gpu_backend->GetFullDisplayResolution();
render_width *= render_scale;
render_height *= render_scale;
}
@ -2085,9 +2111,9 @@ void EmuThread::resetPerformanceCounters()
Q_ARG(const QString&, blank));
}
void Host::OnPerformanceCountersUpdated()
void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
{
g_emu_thread->updatePerformanceCounters();
g_emu_thread->updatePerformanceCounters(gpu_backend);
}
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name)

View File

@ -44,6 +44,8 @@ class INISettingsInterface;
enum class RenderAPI : u8;
class GPUDevice;
class GPUBackend;
class MainWindow;
class DisplayWidget;
@ -91,9 +93,9 @@ public:
ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; }
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
ALWAYS_INLINE bool isFullscreenUIActive() const { return m_is_fullscreen_ui_active; }
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error);
@ -102,6 +104,8 @@ public:
void startBackgroundControllerPollTimer();
void stopBackgroundControllerPollTimer();
void setFullscreenUIActive(bool active);
void setFullscreenUIStarted(bool started);
void wakeThread();
bool shouldRenderToMain() const;
@ -109,7 +113,7 @@ public:
void bootOrLoadState(std::string path);
void updatePerformanceCounters();
void updatePerformanceCounters(const GPUBackend* gpu_backend);
void resetPerformanceCounters();
/// Locks the system by pausing it, while a popup dialog is displayed.
@ -147,7 +151,7 @@ Q_SIGNALS:
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void inputProfileLoaded();
void mouseModeRequested(bool relative, bool hide_cursor);
void fullscreenUIStateChange(bool running);
void fullscreenUIStartedOrStopped(bool running);
void achievementsLoginRequested(Achievements::LoginRequestReason reason);
void achievementsRefreshed(quint32 id, const QString& game_info_string);
void achievementsChallengeModeChanged(bool enabled);
@ -242,9 +246,10 @@ private:
QTimer* m_background_controller_polling_timer = nullptr;
bool m_shutdown_flag = false;
bool m_run_fullscreen_ui = false;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_fullscreen_ui_started = false;
bool m_is_fullscreen_ui_active = false;
bool m_is_surfaceless = false;
bool m_save_state_on_shutdown = false;

View File

@ -5,7 +5,7 @@
#include "core/controller.h"
#include "core/fullscreen_ui.h"
#include "core/game_list.h"
#include "core/gpu.h"
#include "core/gpu_backend.h"
#include "core/host.h"
#include "core/system.h"
#include "core/system_private.h"
@ -276,7 +276,7 @@ void Host::OnIdleStateChanged()
//
}
void Host::OnPerformanceCountersUpdated()
void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
{
//
}
@ -365,14 +365,10 @@ void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32*
{
}
void Host::FrameDone()
void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
{
const u32 frame = System::GetFrameNumber();
if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame % s_frame_dump_interval) == 0))
{
std::string dump_filename(RegTestHost::GetFrameDumpFilename(frame));
g_gpu->WriteDisplayTextureToFile(std::move(dump_filename));
}
if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame_number % s_frame_dump_interval) == 0))
gpu_backend->WriteDisplayTextureToFile(RegTestHost::GetFrameDumpFilename(frame_number));
}
void Host::OpenURL(std::string_view url)

View File

@ -343,6 +343,17 @@ const char* GPUDevice::ShaderLanguageToString(GPUShaderLanguage language)
}
}
const char* GPUDevice::VSyncModeToString(GPUVSyncMode mode)
{
static constexpr std::array<const char*, static_cast<size_t>(GPUVSyncMode::Count)> vsync_modes = {{
"Disabled",
"FIFO",
"Mailbox",
}};
return vsync_modes[static_cast<size_t>(mode)];
}
bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
{
return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) &&

View File

@ -601,6 +601,9 @@ public:
/// Returns a string representing the specified language.
static const char* ShaderLanguageToString(GPUShaderLanguage language);
/// Returns a string representing the specified vsync mode.
static const char* VSyncModeToString(GPUVSyncMode mode);
/// Returns a new device for the specified API.
static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api);

View File

@ -34,6 +34,8 @@ public:
ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); }
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
ALWAYS_INLINE u32 GetVersion() const { return m_version; }
ALWAYS_INLINE const u8* GetData() const { return m_data; }
ALWAYS_INLINE size_t GetDataSize() const { return m_size; }
ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
/// Overload for integral or floating-point types. Writes bytes as-is.