GPU: Move backend work off CPU thread
This commit is contained in:
parent
831c982f3b
commit
2d659fc3eb
|
@ -14,9 +14,9 @@
|
||||||
#define CPU_ARCH_SIMD 1
|
#define CPU_ARCH_SIMD 1
|
||||||
#define CPU_ARCH_SSE 1
|
#define CPU_ARCH_SSE 1
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
#include <tmmintrin.h>
|
|
||||||
#include <smmintrin.h>
|
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
|
#include <smmintrin.h>
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
|
||||||
#if defined(__AVX2__)
|
#if defined(__AVX2__)
|
||||||
#define CPU_ARCH_AVX 1
|
#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++)
|
for (u32 i = 0; i < remaining_count; i++)
|
||||||
*(dest++) = value;
|
*(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
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
X(GPU) \
|
X(GPU) \
|
||||||
X(GPUDevice) \
|
X(GPUDevice) \
|
||||||
X(GPUDump) \
|
X(GPUDump) \
|
||||||
|
X(GPUThread) \
|
||||||
X(GPU_SW) \
|
X(GPU_SW) \
|
||||||
X(GPU_HW) \
|
X(GPU_HW) \
|
||||||
X(GameDatabase) \
|
X(GameDatabase) \
|
||||||
|
|
|
@ -61,10 +61,11 @@ add_library(core
|
||||||
gpu_shadergen.h
|
gpu_shadergen.h
|
||||||
gpu_sw.cpp
|
gpu_sw.cpp
|
||||||
gpu_sw.h
|
gpu_sw.h
|
||||||
gpu_sw_backend.cpp
|
|
||||||
gpu_sw_backend.h
|
|
||||||
gpu_sw_rasterizer.cpp
|
gpu_sw_rasterizer.cpp
|
||||||
gpu_sw_rasterizer.h
|
gpu_sw_rasterizer.h
|
||||||
|
gpu_thread.cpp
|
||||||
|
gpu_thread.h
|
||||||
|
gpu_thread_commands.h
|
||||||
gpu_types.h
|
gpu_types.h
|
||||||
guncon.cpp
|
guncon.cpp
|
||||||
guncon.h
|
guncon.h
|
||||||
|
@ -73,8 +74,6 @@ add_library(core
|
||||||
gte_types.h
|
gte_types.h
|
||||||
host.cpp
|
host.cpp
|
||||||
host.h
|
host.h
|
||||||
host_interface_progress_callback.cpp
|
|
||||||
host_interface_progress_callback.h
|
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
input_types.h
|
input_types.h
|
||||||
imgui_overlays.cpp
|
imgui_overlays.cpp
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
#include "bus.h"
|
#include "bus.h"
|
||||||
#include "cpu_core.h"
|
#include "cpu_core.h"
|
||||||
#include "fullscreen_ui.h"
|
#include "fullscreen_ui.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
#include "imgui_overlays.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
|
||||||
#include "scmversion/scmversion.h"
|
#include "scmversion/scmversion.h"
|
||||||
|
@ -480,7 +482,9 @@ void Achievements::UpdateGlyphRanges()
|
||||||
std::sort(sorted_codepoints.begin(), sorted_codepoints.end());
|
std::sort(sorted_codepoints.begin(), sorted_codepoints.end());
|
||||||
|
|
||||||
// Compact codepoints to ranges.
|
// Compact codepoints to ranges.
|
||||||
ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints));
|
GPUThread::RunOnThread([sorted_codepoints = std::move(sorted_codepoints)]() {
|
||||||
|
ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Achievements::IsActive()
|
bool Achievements::IsActive()
|
||||||
|
@ -1173,7 +1177,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
|
||||||
|
|
||||||
// ensure fullscreen UI is ready for notifications
|
// ensure fullscreen UI is ready for notifications
|
||||||
if (display_summary)
|
if (display_summary)
|
||||||
FullscreenUI::Initialize();
|
GPUThread::RunOnThread(&FullscreenUI::Initialize);
|
||||||
|
|
||||||
char url_buf[URL_BUFFER_SIZE];
|
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)
|
if (int err = rc_client_game_get_image_url(info, url_buf, std::size(url_buf)); err == RC_OK)
|
||||||
|
@ -1229,7 +1233,7 @@ void Achievements::ClearGameHash()
|
||||||
|
|
||||||
void Achievements::DisplayAchievementSummary()
|
void Achievements::DisplayAchievementSummary()
|
||||||
{
|
{
|
||||||
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
|
if (g_settings.achievements_notifications)
|
||||||
{
|
{
|
||||||
std::string title;
|
std::string title;
|
||||||
if (IsHardcoreModeActive())
|
if (IsHardcoreModeActive())
|
||||||
|
@ -1254,8 +1258,14 @@ void Achievements::DisplayAchievementSummary()
|
||||||
summary = TRANSLATE_STR("Achievements", "This game has no achievements.");
|
summary = TRANSLATE_STR("Achievements", "This game has no achievements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
|
GPUThread::RunOnThread(
|
||||||
std::move(summary), s_state.game_icon);
|
[title = std::move(title), summary = std::move(summary), icon = s_state.game_icon]() mutable {
|
||||||
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
|
||||||
|
std::move(summary), std::move(icon));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Technically not going through the resource API, but since we're passing this to something else, we can't.
|
// Technically not going through the resource API, but since we're passing this to something else, we can't.
|
||||||
|
@ -1265,12 +1275,16 @@ void Achievements::DisplayAchievementSummary()
|
||||||
|
|
||||||
void Achievements::DisplayHardcoreDeferredMessage()
|
void Achievements::DisplayHardcoreDeferredMessage()
|
||||||
{
|
{
|
||||||
if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid() &&
|
if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid())
|
||||||
FullscreenUI::Initialize())
|
|
||||||
{
|
{
|
||||||
ImGuiFullscreen::ShowToast(std::string(),
|
GPUThread::RunOnThread([]() {
|
||||||
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
|
if (!FullscreenUI::Initialize())
|
||||||
Host::OSD_WARNING_DURATION);
|
return;
|
||||||
|
|
||||||
|
ImGuiFullscreen::ShowToast(std::string(),
|
||||||
|
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
|
||||||
|
Host::OSD_WARNING_DURATION);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1292,7 +1306,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
|
||||||
INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_state.game_id);
|
INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_state.game_id);
|
||||||
UpdateGameSummary();
|
UpdateGameSummary();
|
||||||
|
|
||||||
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
|
if (g_settings.achievements_notifications)
|
||||||
{
|
{
|
||||||
std::string title;
|
std::string title;
|
||||||
if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL)
|
if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL)
|
||||||
|
@ -1302,9 +1316,15 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
|
||||||
|
|
||||||
std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state);
|
std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state);
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id),
|
GPUThread::RunOnThread([id = cheevo->id, duration = g_settings.achievements_notification_duration,
|
||||||
static_cast<float>(g_settings.achievements_notification_duration),
|
title = std::move(title), description = std::string(cheevo->description),
|
||||||
std::move(title), cheevo->description, std::move(badge_path));
|
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)
|
if (g_settings.achievements_sound_effects)
|
||||||
|
@ -1316,7 +1336,7 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
|
||||||
INFO_LOG("Game {} complete", s_state.game_id);
|
INFO_LOG("Game {} complete", s_state.game_id);
|
||||||
UpdateGameSummary();
|
UpdateGameSummary();
|
||||||
|
|
||||||
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
|
if (g_settings.achievements_notifications)
|
||||||
{
|
{
|
||||||
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_state.game_title);
|
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_state.game_title);
|
||||||
std::string message = fmt::format(
|
std::string message = fmt::format(
|
||||||
|
@ -1325,8 +1345,14 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
|
||||||
s_state.game_summary.num_unlocked_achievements),
|
s_state.game_summary.num_unlocked_achievements),
|
||||||
TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked));
|
TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked));
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
|
GPUThread::RunOnThread(
|
||||||
std::move(message), s_state.game_icon);
|
[title = std::move(title), message = std::move(message), icon = s_state.game_icon]() mutable {
|
||||||
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
|
||||||
|
std::move(message), std::move(icon));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1334,14 +1360,19 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
|
||||||
{
|
{
|
||||||
DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
|
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 title = event->leaderboard->title;
|
||||||
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
|
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
|
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
|
||||||
LEADERBOARD_STARTED_NOTIFICATION_TIME, std::move(title), std::move(message),
|
icon = s_state.game_icon]() mutable {
|
||||||
s_state.game_icon);
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME,
|
||||||
|
std::move(title), std::move(message), std::move(icon));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1349,14 +1380,19 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
|
||||||
{
|
{
|
||||||
DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
|
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 title = event->leaderboard->title;
|
||||||
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
|
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
|
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
|
||||||
LEADERBOARD_FAILED_NOTIFICATION_TIME, std::move(title), std::move(message),
|
icon = s_state.game_icon]() mutable {
|
||||||
s_state.game_icon);
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_FAILED_NOTIFICATION_TIME,
|
||||||
|
std::move(title), std::move(message), std::move(icon));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1364,7 +1400,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
|
||||||
{
|
{
|
||||||
DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
|
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] = {
|
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
|
||||||
TRANSLATE_NOOP("Achievements", "Your Time: {}{}"),
|
TRANSLATE_NOOP("Achievements", "Your Time: {}{}"),
|
||||||
|
@ -1380,9 +1416,14 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
|
||||||
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
|
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
|
||||||
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
|
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
|
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
|
||||||
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
|
icon = s_state.game_icon]() mutable {
|
||||||
std::move(message), s_state.game_icon);
|
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)
|
if (g_settings.achievements_sound_effects)
|
||||||
|
@ -1394,7 +1435,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
|
||||||
DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
|
DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
|
||||||
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
|
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] = {
|
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
|
||||||
TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"),
|
TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"),
|
||||||
|
@ -1411,9 +1452,15 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
|
||||||
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
|
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
|
||||||
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
|
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
|
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
|
||||||
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
|
icon = s_state.game_icon]() mutable {
|
||||||
std::move(message), s_state.game_icon);
|
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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1543,26 +1590,30 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
|
||||||
{
|
{
|
||||||
WARNING_LOG("Server disconnected.");
|
WARNING_LOG("Server disconnected.");
|
||||||
|
|
||||||
if (FullscreenUI::Initialize())
|
GPUThread::RunOnThread([]() {
|
||||||
{
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
ImGuiFullscreen::ShowToast(
|
ImGuiFullscreen::ShowToast(
|
||||||
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
|
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
|
||||||
TRANSLATE_STR("Achievements",
|
TRANSLATE_STR("Achievements",
|
||||||
"An unlock request could not be completed. We will keep retrying to submit this request."),
|
"An unlock request could not be completed. We will keep retrying to submit this request."),
|
||||||
Host::OSD_ERROR_DURATION);
|
Host::OSD_ERROR_DURATION);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
|
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
|
||||||
{
|
{
|
||||||
WARNING_LOG("Server reconnected.");
|
WARNING_LOG("Server reconnected.");
|
||||||
|
|
||||||
if (FullscreenUI::Initialize())
|
GPUThread::RunOnThread([]() {
|
||||||
{
|
if (!FullscreenUI::Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
|
ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
|
||||||
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
|
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
|
||||||
Host::OSD_INFO_DURATION);
|
Host::OSD_INFO_DURATION);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Achievements::ResetClient()
|
void Achievements::ResetClient()
|
||||||
|
@ -1640,12 +1691,17 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
|
||||||
// new mode
|
// new mode
|
||||||
s_state.hardcore_mode = enabled;
|
s_state.hardcore_mode = enabled;
|
||||||
|
|
||||||
if (System::IsValid() && (HasActiveGame() || force_display_message) && FullscreenUI::Initialize())
|
if (System::IsValid() && (HasActiveGame() || force_display_message))
|
||||||
{
|
{
|
||||||
ImGuiFullscreen::ShowToast(std::string(),
|
GPUThread::RunOnThread([enabled]() {
|
||||||
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
|
if (!FullscreenUI::Initialize())
|
||||||
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
|
return;
|
||||||
Host::OSD_INFO_DURATION);
|
|
||||||
|
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_state.client, enabled);
|
rc_client_set_hardcore_enabled(s_state.client, enabled);
|
||||||
|
@ -1687,8 +1743,15 @@ bool Achievements::DoState(StateWrapper& sw)
|
||||||
// before deserializing, otherwise that state's going to get lost.
|
// before deserializing, otherwise that state's going to get lost.
|
||||||
if (!IsUsingRAIntegration() && s_state.load_game_request)
|
if (!IsUsingRAIntegration() && s_state.load_game_request)
|
||||||
{
|
{
|
||||||
Host::DisplayLoadingScreen("Downloading achievements data...");
|
// Messy because GPU-thread, but at least it looks pretty.
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
FullscreenUI::OpenLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME,
|
||||||
|
TRANSLATE_SV("Achievements", "Downloading achievements data..."));
|
||||||
|
});
|
||||||
|
|
||||||
s_state.http_downloader->WaitForAllRequests();
|
s_state.http_downloader->WaitForAllRequests();
|
||||||
|
|
||||||
|
GPUThread::RunOnThread([]() { FullscreenUI::CloseLoadingScreen(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 data_size = 0;
|
u32 data_size = 0;
|
||||||
|
@ -1957,7 +2020,7 @@ void Achievements::ShowLoginNotification()
|
||||||
if (!user)
|
if (!user)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
|
if (g_settings.achievements_notifications)
|
||||||
{
|
{
|
||||||
std::string badge_path = GetLoggedInUserBadgePath();
|
std::string badge_path = GetLoggedInUserBadgePath();
|
||||||
std::string title = user->display_name;
|
std::string title = user->display_name;
|
||||||
|
@ -1966,8 +2029,14 @@ void Achievements::ShowLoginNotification()
|
||||||
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
|
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
|
||||||
user->score, user->score_softcore, user->num_unread_messages);
|
user->score, user->score_softcore, user->num_unread_messages);
|
||||||
|
|
||||||
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
|
GPUThread::RunOnThread(
|
||||||
std::move(summary), std::move(badge_path));
|
[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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2066,6 +2135,15 @@ bool Achievements::ConfirmHardcoreModeDisable(const char* trigger)
|
||||||
|
|
||||||
void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function<void(bool)> callback)
|
void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function<void(bool)> callback)
|
||||||
{
|
{
|
||||||
|
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]() {
|
||||||
|
if (res)
|
||||||
|
DisableHardcoreMode();
|
||||||
|
callback(res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
#ifdef ENABLE_RAINTEGRATION
|
#ifdef ENABLE_RAINTEGRATION
|
||||||
if (IsUsingRAIntegration())
|
if (IsUsingRAIntegration())
|
||||||
|
@ -2076,34 +2154,32 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!FullscreenUI::Initialize())
|
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);
|
Host::AddOSDMessage(
|
||||||
callback(false);
|
fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
|
||||||
return;
|
Host::OSD_WARNING_DURATION);
|
||||||
}
|
real_callback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto real_callback = [callback = std::move(callback)](bool res) mutable {
|
ImGuiFullscreen::OpenConfirmMessageDialog(
|
||||||
// don't run the callback in the middle of rendering the UI
|
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
|
||||||
Host::RunOnCPUThread([callback = std::move(callback), res]() {
|
fmt::format(TRANSLATE_FS("Achievements",
|
||||||
if (res)
|
"{0} cannot be performed while hardcore mode is active. Do you "
|
||||||
DisableHardcoreMode();
|
"want to disable hardcore mode? {0} will be cancelled if you select No."),
|
||||||
callback(res);
|
trigger),
|
||||||
});
|
std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
|
||||||
};
|
fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
|
||||||
|
});
|
||||||
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
|
#else
|
||||||
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
|
Host::ConfirmMessageAsync(
|
||||||
Host::OSD_WARNING_DURATION);
|
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
|
||||||
callback(false);
|
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));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
#include "cdrom_async_reader.h"
|
#include "cdrom_async_reader.h"
|
||||||
#include "cdrom_subq_replacement.h"
|
#include "cdrom_subq_replacement.h"
|
||||||
#include "dma.h"
|
#include "dma.h"
|
||||||
|
#include "fullscreen_ui.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "host_interface_progress_callback.h"
|
|
||||||
#include "interrupt_controller.h"
|
#include "interrupt_controller.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "spu.h"
|
#include "spu.h"
|
||||||
|
@ -999,7 +999,7 @@ bool CDROM::PrecacheMedia()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HostInterfaceProgressCallback callback;
|
LoadingScreenProgressCallback callback;
|
||||||
if (!s_reader.Precache(&callback))
|
if (!s_reader.Precache(&callback))
|
||||||
{
|
{
|
||||||
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Precaching CD image failed, it may be unreliable."),
|
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Precaching CD image failed, it may be unreliable."),
|
||||||
|
|
|
@ -40,14 +40,13 @@
|
||||||
<ClCompile Include="gpu_hw_texture_cache.cpp" />
|
<ClCompile Include="gpu_hw_texture_cache.cpp" />
|
||||||
<ClCompile Include="gpu_shadergen.cpp" />
|
<ClCompile Include="gpu_shadergen.cpp" />
|
||||||
<ClCompile Include="gpu_sw.cpp" />
|
<ClCompile Include="gpu_sw.cpp" />
|
||||||
<ClCompile Include="gpu_sw_backend.cpp" />
|
|
||||||
<ClCompile Include="gpu_sw_rasterizer.cpp" />
|
<ClCompile Include="gpu_sw_rasterizer.cpp" />
|
||||||
|
<ClCompile Include="gpu_thread.cpp" />
|
||||||
<ClCompile Include="gte.cpp" />
|
<ClCompile Include="gte.cpp" />
|
||||||
<ClCompile Include="dma.cpp" />
|
<ClCompile Include="dma.cpp" />
|
||||||
<ClCompile Include="gpu.cpp" />
|
<ClCompile Include="gpu.cpp" />
|
||||||
<ClCompile Include="gpu_hw.cpp" />
|
<ClCompile Include="gpu_hw.cpp" />
|
||||||
<ClCompile Include="host.cpp" />
|
<ClCompile Include="host.cpp" />
|
||||||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
|
||||||
<ClCompile Include="hotkeys.cpp" />
|
<ClCompile Include="hotkeys.cpp" />
|
||||||
<ClCompile Include="imgui_overlays.cpp" />
|
<ClCompile Include="imgui_overlays.cpp" />
|
||||||
<ClCompile Include="interrupt_controller.cpp" />
|
<ClCompile Include="interrupt_controller.cpp" />
|
||||||
|
@ -119,8 +118,9 @@
|
||||||
<ClInclude Include="gpu_hw_texture_cache.h" />
|
<ClInclude Include="gpu_hw_texture_cache.h" />
|
||||||
<ClInclude Include="gpu_shadergen.h" />
|
<ClInclude Include="gpu_shadergen.h" />
|
||||||
<ClInclude Include="gpu_sw.h" />
|
<ClInclude Include="gpu_sw.h" />
|
||||||
<ClInclude Include="gpu_sw_backend.h" />
|
|
||||||
<ClInclude Include="gpu_sw_rasterizer.h" />
|
<ClInclude Include="gpu_sw_rasterizer.h" />
|
||||||
|
<ClInclude Include="gpu_thread.h" />
|
||||||
|
<ClInclude Include="gpu_thread_commands.h" />
|
||||||
<ClInclude Include="gpu_types.h" />
|
<ClInclude Include="gpu_types.h" />
|
||||||
<ClInclude Include="gte.h" />
|
<ClInclude Include="gte.h" />
|
||||||
<ClInclude Include="cpu_types.h" />
|
<ClInclude Include="cpu_types.h" />
|
||||||
|
@ -129,7 +129,6 @@
|
||||||
<ClInclude Include="gpu_hw.h" />
|
<ClInclude Include="gpu_hw.h" />
|
||||||
<ClInclude Include="gte_types.h" />
|
<ClInclude Include="gte_types.h" />
|
||||||
<ClInclude Include="host.h" />
|
<ClInclude Include="host.h" />
|
||||||
<ClInclude Include="host_interface_progress_callback.h" />
|
|
||||||
<ClInclude Include="imgui_overlays.h" />
|
<ClInclude Include="imgui_overlays.h" />
|
||||||
<ClInclude Include="input_types.h" />
|
<ClInclude Include="input_types.h" />
|
||||||
<ClInclude Include="interrupt_controller.h" />
|
<ClInclude Include="interrupt_controller.h" />
|
||||||
|
|
|
@ -33,13 +33,11 @@
|
||||||
<ClCompile Include="guncon.cpp" />
|
<ClCompile Include="guncon.cpp" />
|
||||||
<ClCompile Include="playstation_mouse.cpp" />
|
<ClCompile Include="playstation_mouse.cpp" />
|
||||||
<ClCompile Include="negcon.cpp" />
|
<ClCompile Include="negcon.cpp" />
|
||||||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
|
||||||
<ClCompile Include="cpu_pgxp.cpp" />
|
<ClCompile Include="cpu_pgxp.cpp" />
|
||||||
<ClCompile Include="cheats.cpp" />
|
<ClCompile Include="cheats.cpp" />
|
||||||
<ClCompile Include="memory_card_image.cpp" />
|
<ClCompile Include="memory_card_image.cpp" />
|
||||||
<ClCompile Include="analog_joystick.cpp" />
|
<ClCompile Include="analog_joystick.cpp" />
|
||||||
<ClCompile Include="gpu_backend.cpp" />
|
<ClCompile Include="gpu_backend.cpp" />
|
||||||
<ClCompile Include="gpu_sw_backend.cpp" />
|
|
||||||
<ClCompile Include="multitap.cpp" />
|
<ClCompile Include="multitap.cpp" />
|
||||||
<ClCompile Include="host.cpp" />
|
<ClCompile Include="host.cpp" />
|
||||||
<ClCompile Include="game_database.cpp" />
|
<ClCompile Include="game_database.cpp" />
|
||||||
|
@ -67,6 +65,7 @@
|
||||||
<ClCompile Include="performance_counters.cpp" />
|
<ClCompile Include="performance_counters.cpp" />
|
||||||
<ClCompile Include="jogcon.cpp" />
|
<ClCompile Include="jogcon.cpp" />
|
||||||
<ClCompile Include="pio.cpp" />
|
<ClCompile Include="pio.cpp" />
|
||||||
|
<ClCompile Include="gpu_thread.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
|
@ -102,7 +101,6 @@
|
||||||
<ClInclude Include="guncon.h" />
|
<ClInclude Include="guncon.h" />
|
||||||
<ClInclude Include="playstation_mouse.h" />
|
<ClInclude Include="playstation_mouse.h" />
|
||||||
<ClInclude Include="negcon.h" />
|
<ClInclude Include="negcon.h" />
|
||||||
<ClInclude Include="host_interface_progress_callback.h" />
|
|
||||||
<ClInclude Include="gte_types.h" />
|
<ClInclude Include="gte_types.h" />
|
||||||
<ClInclude Include="cpu_pgxp.h" />
|
<ClInclude Include="cpu_pgxp.h" />
|
||||||
<ClInclude Include="cpu_core_private.h" />
|
<ClInclude Include="cpu_core_private.h" />
|
||||||
|
@ -111,7 +109,6 @@
|
||||||
<ClInclude Include="analog_joystick.h" />
|
<ClInclude Include="analog_joystick.h" />
|
||||||
<ClInclude Include="gpu_types.h" />
|
<ClInclude Include="gpu_types.h" />
|
||||||
<ClInclude Include="gpu_backend.h" />
|
<ClInclude Include="gpu_backend.h" />
|
||||||
<ClInclude Include="gpu_sw_backend.h" />
|
|
||||||
<ClInclude Include="multitap.h" />
|
<ClInclude Include="multitap.h" />
|
||||||
<ClInclude Include="host.h" />
|
<ClInclude Include="host.h" />
|
||||||
<ClInclude Include="achievements.h" />
|
<ClInclude Include="achievements.h" />
|
||||||
|
@ -143,6 +140,8 @@
|
||||||
<ClInclude Include="system_private.h" />
|
<ClInclude Include="system_private.h" />
|
||||||
<ClInclude Include="jogcon.h" />
|
<ClInclude Include="jogcon.h" />
|
||||||
<ClInclude Include="pio.h" />
|
<ClInclude Include="pio.h" />
|
||||||
|
<ClInclude Include="gpu_thread.h" />
|
||||||
|
<ClInclude Include="gpu_thread_commands.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="gpu_sw_rasterizer.inl" />
|
<None Include="gpu_sw_rasterizer.inl" />
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "game_list.h"
|
#include "game_list.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
#include "imgui_overlays.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "system_private.h"
|
#include "system_private.h"
|
||||||
|
@ -203,6 +205,7 @@ struct PostProcessingStageInfo
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Main
|
// Main
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
static void UpdateRunIdleState();
|
||||||
static void PauseForMenuOpen(bool set_pause_menu_open);
|
static void PauseForMenuOpen(bool set_pause_menu_open);
|
||||||
static bool AreAnyDialogsOpen();
|
static bool AreAnyDialogsOpen();
|
||||||
static void ClosePauseMenu();
|
static void ClosePauseMenu();
|
||||||
|
@ -602,12 +605,12 @@ bool FullscreenUI::Initialize()
|
||||||
s_state.about_window_open = false;
|
s_state.about_window_open = false;
|
||||||
s_state.hotkey_list_cache = InputManager::GetHotkeyList();
|
s_state.hotkey_list_cache = InputManager::GetHotkeyList();
|
||||||
|
|
||||||
if (!System::IsValid())
|
Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(true); });
|
||||||
|
|
||||||
|
if (!GPUThread::HasGPUBackend() && !GPUThread::IsGPUBackendRequested())
|
||||||
SwitchToLanding();
|
SwitchToLanding();
|
||||||
|
|
||||||
if (!System::IsRunning())
|
UpdateRunIdleState();
|
||||||
Host::OnIdleStateChanged();
|
|
||||||
|
|
||||||
ForceKeyNavEnabled();
|
ForceKeyNavEnabled();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -631,6 +634,7 @@ bool FullscreenUI::AreAnyDialogsOpen()
|
||||||
|
|
||||||
void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
|
void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
|
||||||
{
|
{
|
||||||
|
// NOTE: Called on CPU thread.
|
||||||
if (!IsInitialized())
|
if (!IsInitialized())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -638,55 +642,98 @@ void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
|
||||||
// That means we're going to be reading achievement state.
|
// That means we're going to be reading achievement state.
|
||||||
if (old_settings.achievements_enabled && !g_settings.achievements_enabled)
|
if (old_settings.achievements_enabled && !g_settings.achievements_enabled)
|
||||||
{
|
{
|
||||||
if (s_state.current_main_window == MainWindowType::Achievements ||
|
if (!IsInitialized())
|
||||||
s_state.current_main_window == MainWindowType::Leaderboards)
|
return;
|
||||||
ReturnToPreviousWindow();
|
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
if (s_state.current_main_window == MainWindowType::Achievements ||
|
||||||
|
s_state.current_main_window == MainWindowType::Leaderboards)
|
||||||
|
{
|
||||||
|
ReturnToPreviousWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FullscreenUI::UpdateRunIdleState()
|
||||||
|
{
|
||||||
|
const bool new_run_idle = HasActiveWindow();
|
||||||
|
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, new_run_idle);
|
||||||
|
}
|
||||||
|
|
||||||
void FullscreenUI::OnSystemStarted()
|
void FullscreenUI::OnSystemStarted()
|
||||||
{
|
{
|
||||||
|
// NOTE: Called on CPU thread.
|
||||||
if (!IsInitialized())
|
if (!IsInitialized())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_state.current_main_window = MainWindowType::None;
|
GPUThread::RunOnThread([]() {
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
if (!IsInitialized())
|
||||||
}
|
return;
|
||||||
|
|
||||||
void FullscreenUI::OnSystemPaused()
|
s_state.current_main_window = MainWindowType::None;
|
||||||
{
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
// noop
|
UpdateRunIdleState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::OnSystemResumed()
|
void FullscreenUI::OnSystemResumed()
|
||||||
{
|
{
|
||||||
// get rid of pause menu if we unpaused another way
|
// NOTE: Called on CPU thread.
|
||||||
if (s_state.current_main_window == MainWindowType::PauseMenu)
|
if (!IsInitialized())
|
||||||
ClosePauseMenu();
|
return;
|
||||||
|
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
if (!IsInitialized())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// get rid of pause menu if we unpaused another way
|
||||||
|
if (s_state.current_main_window == MainWindowType::PauseMenu)
|
||||||
|
ClosePauseMenu();
|
||||||
|
|
||||||
|
UpdateRunIdleState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::OnSystemDestroyed()
|
void FullscreenUI::OnSystemDestroyed()
|
||||||
{
|
{
|
||||||
|
// NOTE: Called on CPU thread.
|
||||||
if (!IsInitialized())
|
if (!IsInitialized())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_state.pause_menu_was_open = false;
|
GPUThread::RunOnThread([]() {
|
||||||
s_state.was_paused_on_quick_menu_open = false;
|
if (!IsInitialized())
|
||||||
s_state.current_pause_submenu = PauseSubMenu::None;
|
return;
|
||||||
SwitchToLanding();
|
|
||||||
|
s_state.pause_menu_was_open = false;
|
||||||
|
s_state.was_paused_on_quick_menu_open = false;
|
||||||
|
s_state.current_pause_submenu = PauseSubMenu::None;
|
||||||
|
SwitchToLanding();
|
||||||
|
UpdateRunIdleState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::OnRunningGameChanged()
|
void FullscreenUI::OnRunningGameChanged()
|
||||||
{
|
{
|
||||||
|
// NOTE: Called on CPU thread.
|
||||||
if (!IsInitialized())
|
if (!IsInitialized())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::string& path = System::GetDiscPath();
|
const std::string& path = System::GetDiscPath();
|
||||||
const std::string& serial = System::GetGameSerial();
|
const std::string& serial = System::GetGameSerial();
|
||||||
|
|
||||||
|
std::string subtitle;
|
||||||
if (!serial.empty())
|
if (!serial.empty())
|
||||||
s_state.current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
|
subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
|
||||||
else
|
else
|
||||||
s_state.current_game_subtitle = {};
|
subtitle = {};
|
||||||
|
|
||||||
|
GPUThread::RunOnThread([subtitle = std::move(subtitle)]() mutable {
|
||||||
|
if (!IsInitialized())
|
||||||
|
return;
|
||||||
|
|
||||||
|
s_state.current_game_subtitle = std::move(subtitle);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open)
|
void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open)
|
||||||
|
@ -703,15 +750,18 @@ void FullscreenUI::OpenPauseMenu()
|
||||||
if (!System::IsValid())
|
if (!System::IsValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!Initialize() || s_state.current_main_window != MainWindowType::None)
|
GPUThread::RunOnThread([]() {
|
||||||
return;
|
if (!Initialize() || s_state.current_main_window != MainWindowType::None)
|
||||||
|
return;
|
||||||
|
|
||||||
PauseForMenuOpen(true);
|
PauseForMenuOpen(true);
|
||||||
s_state.current_main_window = MainWindowType::PauseMenu;
|
s_state.current_main_window = MainWindowType::PauseMenu;
|
||||||
s_state.current_pause_submenu = PauseSubMenu::None;
|
s_state.current_pause_submenu = PauseSubMenu::None;
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
ForceKeyNavEnabled();
|
ForceKeyNavEnabled();
|
||||||
FixStateIfPaused();
|
UpdateRunIdleState();
|
||||||
|
FixStateIfPaused();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::OpenCheatsMenu()
|
void FullscreenUI::OpenCheatsMenu()
|
||||||
|
@ -725,41 +775,39 @@ void FullscreenUI::OpenCheatsMenu()
|
||||||
s_state.settings_page = SettingsPage::Cheats;
|
s_state.settings_page = SettingsPage::Cheats;
|
||||||
PauseForMenuOpen(true);
|
PauseForMenuOpen(true);
|
||||||
ForceKeyNavEnabled();
|
ForceKeyNavEnabled();
|
||||||
|
UpdateRunIdleState();
|
||||||
FixStateIfPaused();
|
FixStateIfPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::FixStateIfPaused()
|
void FullscreenUI::FixStateIfPaused()
|
||||||
{
|
{
|
||||||
if (!System::IsValid() || System::IsRunning())
|
if (!GPUThread::HasGPUBackend() || System::IsRunning())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// When we're paused, we won't have trickled the key up event for escape yet. Do it now.
|
// When we're paused, we won't have trickled the key up event for escape yet. Do it now.
|
||||||
ImGui::UpdateInputEvents(false);
|
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()
|
void FullscreenUI::ClosePauseMenu()
|
||||||
{
|
{
|
||||||
if (!IsInitialized() || !System::IsValid())
|
if (!System::IsValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (System::GetState() == System::State::Paused && !s_state.was_paused_on_quick_menu_open)
|
const bool paused = System::IsPaused();
|
||||||
Host::RunOnCPUThread([]() { System::PauseSystem(false); });
|
GPUThread::RunOnThread([paused]() {
|
||||||
|
if (!IsInitialized())
|
||||||
|
return;
|
||||||
|
|
||||||
s_state.current_main_window = MainWindowType::None;
|
if (paused && !s_state.was_paused_on_quick_menu_open)
|
||||||
s_state.current_pause_submenu = PauseSubMenu::None;
|
Host::RunOnCPUThread([]() { System::PauseSystem(false); });
|
||||||
s_state.pause_menu_was_open = false;
|
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
s_state.current_main_window = MainWindowType::None;
|
||||||
FixStateIfPaused();
|
s_state.current_pause_submenu = PauseSubMenu::None;
|
||||||
|
s_state.pause_menu_was_open = false;
|
||||||
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
|
UpdateRunIdleState();
|
||||||
|
FixStateIfPaused();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
|
void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
|
||||||
|
@ -790,14 +838,21 @@ void FullscreenUI::Shutdown()
|
||||||
s_state.current_game_subtitle = {};
|
s_state.current_game_subtitle = {};
|
||||||
DestroyResources();
|
DestroyResources();
|
||||||
ImGuiFullscreen::Shutdown();
|
ImGuiFullscreen::Shutdown();
|
||||||
|
if (s_state.initialized)
|
||||||
|
Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(false); });
|
||||||
|
|
||||||
s_state.initialized = false;
|
s_state.initialized = false;
|
||||||
s_state.tried_to_initialize = false;
|
s_state.tried_to_initialize = false;
|
||||||
|
UpdateRunIdleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::Render()
|
void FullscreenUI::Render()
|
||||||
{
|
{
|
||||||
if (!s_state.initialized)
|
if (!s_state.initialized)
|
||||||
|
{
|
||||||
|
ImGuiFullscreen::RenderLoadingScreen();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiFullscreen::UploadAsyncTextures();
|
ImGuiFullscreen::UploadAsyncTextures();
|
||||||
|
|
||||||
|
@ -856,6 +911,8 @@ void FullscreenUI::Render()
|
||||||
|
|
||||||
ImGuiFullscreen::EndLayout();
|
ImGuiFullscreen::EndLayout();
|
||||||
|
|
||||||
|
ImGuiFullscreen::RenderLoadingScreen();
|
||||||
|
|
||||||
if (s_state.settings_changed.load(std::memory_order_relaxed))
|
if (s_state.settings_changed.load(std::memory_order_relaxed))
|
||||||
{
|
{
|
||||||
Host::CommitBaseSettingChanges();
|
Host::CommitBaseSettingChanges();
|
||||||
|
@ -889,7 +946,7 @@ void FullscreenUI::Render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System::IsValid())
|
if (GPUThread::HasGPUBackend())
|
||||||
Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); });
|
Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); });
|
||||||
}
|
}
|
||||||
s_state.game_settings_changed.store(false, std::memory_order_release);
|
s_state.game_settings_changed.store(false, std::memory_order_release);
|
||||||
|
@ -908,7 +965,7 @@ void FullscreenUI::InvalidateCoverCache()
|
||||||
|
|
||||||
void FullscreenUI::ReturnToPreviousWindow()
|
void FullscreenUI::ReturnToPreviousWindow()
|
||||||
{
|
{
|
||||||
if (System::IsValid() && s_state.pause_menu_was_open)
|
if (GPUThread::HasGPUBackend() && s_state.pause_menu_was_open)
|
||||||
{
|
{
|
||||||
s_state.current_main_window = MainWindowType::PauseMenu;
|
s_state.current_main_window = MainWindowType::PauseMenu;
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
|
@ -922,7 +979,8 @@ void FullscreenUI::ReturnToPreviousWindow()
|
||||||
void FullscreenUI::ReturnToMainWindow()
|
void FullscreenUI::ReturnToMainWindow()
|
||||||
{
|
{
|
||||||
ClosePauseMenu();
|
ClosePauseMenu();
|
||||||
s_state.current_main_window = System::IsValid() ? MainWindowType::None : MainWindowType::Landing;
|
s_state.current_main_window = GPUThread::HasGPUBackend() ? MainWindowType::None : MainWindowType::Landing;
|
||||||
|
UpdateRunIdleState();
|
||||||
FixStateIfPaused();
|
FixStateIfPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,9 +1016,14 @@ ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
|
||||||
|
|
||||||
void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot)
|
void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot)
|
||||||
{
|
{
|
||||||
if (System::IsValid())
|
if (GPUThread::HasGPUBackend())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Switch to nothing, we'll get called back via OnSystemDestroyed() if startup fails.
|
||||||
|
s_state.current_main_window = MainWindowType::None;
|
||||||
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
|
UpdateRunIdleState();
|
||||||
|
|
||||||
SystemBootParameters params;
|
SystemBootParameters params;
|
||||||
params.filename = std::move(path);
|
params.filename = std::move(path);
|
||||||
params.save_state = std::move(state);
|
params.save_state = std::move(state);
|
||||||
|
@ -1155,108 +1218,116 @@ void FullscreenUI::DoChangeDiscFromFile()
|
||||||
|
|
||||||
void FullscreenUI::DoChangeDisc()
|
void FullscreenUI::DoChangeDisc()
|
||||||
{
|
{
|
||||||
ImGuiFullscreen::ChoiceDialogOptions options;
|
Host::RunOnCPUThread([]() {
|
||||||
|
ImGuiFullscreen::ChoiceDialogOptions options;
|
||||||
|
|
||||||
if (System::HasMediaSubImages())
|
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)
|
|
||||||
{
|
{
|
||||||
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);
|
options.emplace_back(FSUI_STR("From File..."), false);
|
||||||
|
|
||||||
std::vector<std::string> paths;
|
for (u32 i = 0; i < count; i++)
|
||||||
paths.reserve(matches.size());
|
options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
|
||||||
|
|
||||||
const std::string& current_path = System::GetDiscPath();
|
GPUThread::RunOnThread([options = std::move(options)]() mutable {
|
||||||
for (auto& [title, glentry] : matches)
|
auto callback = [](s32 index, const std::string& title, bool checked) {
|
||||||
{
|
if (index == 0)
|
||||||
options.emplace_back(std::move(title), current_path == glentry->path);
|
{
|
||||||
paths.push_back(glentry->path);
|
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();
|
CloseChoiceDialog();
|
||||||
DoChangeDiscFromFile();
|
ReturnToPreviousWindow();
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
else if (index > 0)
|
|
||||||
{
|
|
||||||
System::InsertMedia(paths[index - 1].c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseChoiceDialog();
|
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
|
||||||
ReturnToMainWindow();
|
std::move(callback));
|
||||||
};
|
});
|
||||||
|
|
||||||
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
|
|
||||||
std::move(callback));
|
|
||||||
|
|
||||||
return;
|
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()
|
void FullscreenUI::DoToggleAnalogMode()
|
||||||
{
|
{
|
||||||
// hacky way to toggle analog mode
|
// hacky way to toggle analog mode
|
||||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
Host::RunOnCPUThread([]() {
|
||||||
{
|
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)
|
|
||||||
{
|
{
|
||||||
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);
|
if (std::strcmp(bi.name, "Analog") == 0)
|
||||||
ctrl->SetBindState(bi.bind_index, 0.0f);
|
{
|
||||||
break;
|
ctrl->SetBindState(bi.bind_index, 1.0f);
|
||||||
|
ctrl->SetBindState(bi.bind_index, 0.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::DoRequestExit()
|
void FullscreenUI::DoRequestExit()
|
||||||
|
@ -2857,7 +2928,7 @@ void FullscreenUI::DrawSettingsWindow()
|
||||||
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
||||||
|
|
||||||
const float bg_alpha =
|
const float bg_alpha =
|
||||||
System::IsValid() ? (s_state.settings_page == SettingsPage::PostProcessing ? 0.50f : 0.90f) : 1.0f;
|
GPUThread::HasGPUBackend() ? (s_state.settings_page == SettingsPage::PostProcessing ? 0.50f : 0.90f) : 1.0f;
|
||||||
|
|
||||||
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category",
|
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category",
|
||||||
ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z, bg_alpha)))
|
ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z, bg_alpha)))
|
||||||
|
@ -3802,12 +3873,9 @@ void FullscreenUI::DrawControllerSettingsPage()
|
||||||
&Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count);
|
&Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count);
|
||||||
|
|
||||||
// load mtap settings
|
// load mtap settings
|
||||||
MultitapMode mtap_mode = g_settings.multitap_mode;
|
const MultitapMode mtap_mode =
|
||||||
if (IsEditingGameSettings(bsi))
|
Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
|
||||||
{
|
.value_or(Settings::DEFAULT_MULTITAP_MODE);
|
||||||
mtap_mode = Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
|
|
||||||
.value_or(g_settings.multitap_mode);
|
|
||||||
}
|
|
||||||
const std::array<bool, 2> mtap_enabled = {
|
const std::array<bool, 2> mtap_enabled = {
|
||||||
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
|
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
|
||||||
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
|
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
|
||||||
|
@ -4674,7 +4742,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage()
|
||||||
FSUI_CSTR("Reloads the shaders from disk, applying any changes."),
|
FSUI_CSTR("Reloads the shaders from disk, applying any changes."),
|
||||||
bsi->GetBoolValue("PostProcessing", "Enabled", false)))
|
bsi->GetBoolValue("PostProcessing", "Enabled", false)))
|
||||||
{
|
{
|
||||||
if (System::IsValid() && PostProcessing::ReloadShaders())
|
if (GPUThread::HasGPUBackend() && PostProcessing::ReloadShaders())
|
||||||
ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded."));
|
ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5110,7 +5178,7 @@ void FullscreenUI::DrawAchievementsSettingsPage()
|
||||||
"cheats, and slowdown functions."),
|
"cheats, and slowdown functions."),
|
||||||
"Cheevos", "ChallengeMode", false, enabled))
|
"Cheevos", "ChallengeMode", false, enabled))
|
||||||
{
|
{
|
||||||
if (System::IsValid() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false))
|
if (GPUThread::HasGPUBackend() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false))
|
||||||
ShowToast(std::string(), FSUI_STR("Hardcore mode will be enabled on next game restart."));
|
ShowToast(std::string(), FSUI_STR("Hardcore mode will be enabled on next game restart."));
|
||||||
}
|
}
|
||||||
DrawToggleSetting(
|
DrawToggleSetting(
|
||||||
|
@ -5272,28 +5340,30 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
|
|
||||||
Host::RunOnCPUThread([username = std::string(username), password = std::string(password)]() {
|
Host::RunOnCPUThread([username = std::string(username), password = std::string(password)]() {
|
||||||
ImGuiFullscreen::CloseBackgroundProgressDialog(LOGIN_PROGRESS_NAME);
|
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
if (Achievements::Login(username.c_str(), password.c_str(), &error))
|
const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
|
||||||
{
|
GPUThread::RunOnThread([result, error = std::move(error)]() {
|
||||||
// TODO-GPU-THREAD: Synchronize access to s_achievements_login_window_open.
|
ImGuiFullscreen::CloseBackgroundProgressDialog(LOGIN_PROGRESS_NAME);
|
||||||
actually_close_popup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep popup open on failure
|
if (result)
|
||||||
// because of the whole popup stack thing, we need to hide the dialog while this popup is visible
|
{
|
||||||
s_state.achievements_login_window_open = false;
|
actually_close_popup();
|
||||||
ImGuiFullscreen::OpenInfoMessageDialog(
|
return;
|
||||||
FSUI_STR("Login Error"),
|
}
|
||||||
fmt::format(FSUI_FSTR("Login Failed.\nError: {}\nPlease check your username and password, and try again."),
|
|
||||||
error.GetDescription()),
|
// keep popup open on failure
|
||||||
[]() {
|
// because of the whole popup stack thing, we need to hide the dialog while this popup is visible
|
||||||
s_state.achievements_login_window_open = true;
|
s_state.achievements_login_window_open = false;
|
||||||
QueueResetFocus(FocusResetType::PopupOpened);
|
ImGuiFullscreen::OpenInfoMessageDialog(
|
||||||
},
|
FSUI_STR("Login Error"),
|
||||||
FSUI_ICONSTR(ICON_FA_TIMES, "Close"));
|
fmt::format(FSUI_FSTR("Login Failed.\nError: {}\nPlease check your username and password, and try again."),
|
||||||
|
error.GetDescription()),
|
||||||
|
[]() {
|
||||||
|
s_state.achievements_login_window_open = true;
|
||||||
|
QueueResetFocus(FocusResetType::PopupOpened);
|
||||||
|
},
|
||||||
|
FSUI_ICONSTR(ICON_FA_TIMES, "Close"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5804,7 +5874,7 @@ void FullscreenUI::DrawPauseMenu()
|
||||||
case PauseSubMenu::None:
|
case PauseSubMenu::None:
|
||||||
{
|
{
|
||||||
// NOTE: Menu close must come first, because otherwise VM destruction options will race.
|
// NOTE: Menu close must come first, because otherwise VM destruction options will race.
|
||||||
const bool has_game = System::IsValid() && !System::GetGameSerial().empty();
|
const bool has_game = GPUThread::HasGPUBackend() && !System::GetGameSerial().empty();
|
||||||
|
|
||||||
if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false) || WantsToCloseMenu())
|
if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false) || WantsToCloseMenu())
|
||||||
ClosePauseMenu();
|
ClosePauseMenu();
|
||||||
|
@ -6667,7 +6737,7 @@ void FullscreenUI::DrawGameListWindow()
|
||||||
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
|
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
|
||||||
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
||||||
|
|
||||||
const float bg_alpha = System::IsValid() ? 0.90f : 1.0f;
|
const float bg_alpha = GPUThread::HasGPUBackend() ? 0.90f : 1.0f;
|
||||||
|
|
||||||
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
|
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
|
||||||
MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
|
MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
|
||||||
|
@ -7217,7 +7287,7 @@ void FullscreenUI::DrawGameListSettingsWindow()
|
||||||
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
|
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
|
||||||
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
||||||
|
|
||||||
const float bg_alpha = System::IsValid() ? 0.90f : 1.0f;
|
const float bg_alpha = GPUThread::HasGPUBackend() ? 0.90f : 1.0f;
|
||||||
|
|
||||||
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
|
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
|
||||||
MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
|
MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
|
||||||
|
@ -7533,31 +7603,36 @@ void FullscreenUI::DrawAboutWindow()
|
||||||
|
|
||||||
void FullscreenUI::OpenAchievementsWindow()
|
void FullscreenUI::OpenAchievementsWindow()
|
||||||
{
|
{
|
||||||
|
if (!System::IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
if (!Achievements::IsActive())
|
if (!Achievements::IsActive())
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."),
|
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."),
|
||||||
Host::OSD_INFO_DURATION);
|
Host::OSD_INFO_DURATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (!Achievements::HasAchievements())
|
||||||
if (!System::IsValid() || !Initialize())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!Achievements::HasAchievements() || !Achievements::PrepareAchievementsWindow())
|
|
||||||
{
|
{
|
||||||
ShowToast(std::string(), FSUI_STR("This game has no achievements."));
|
ShowToast(std::string(), FSUI_STR("This game has no achievements."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_state.current_main_window != MainWindowType::PauseMenu)
|
GPUThread::RunOnThread([]() {
|
||||||
{
|
if (!Initialize() || !Achievements::PrepareAchievementsWindow())
|
||||||
PauseForMenuOpen(false);
|
return;
|
||||||
ForceKeyNavEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
s_state.current_main_window = MainWindowType::Achievements;
|
if (s_state.current_main_window != MainWindowType::PauseMenu)
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
{
|
||||||
FixStateIfPaused();
|
PauseForMenuOpen(false);
|
||||||
|
ForceKeyNavEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_state.current_main_window = MainWindowType::Achievements;
|
||||||
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
|
UpdateRunIdleState();
|
||||||
|
FixStateIfPaused();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FullscreenUI::IsAchievementsWindowOpen()
|
bool FullscreenUI::IsAchievementsWindowOpen()
|
||||||
|
@ -7567,31 +7642,36 @@ bool FullscreenUI::IsAchievementsWindowOpen()
|
||||||
|
|
||||||
void FullscreenUI::OpenLeaderboardsWindow()
|
void FullscreenUI::OpenLeaderboardsWindow()
|
||||||
{
|
{
|
||||||
|
if (!System::IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
if (!Achievements::IsActive())
|
if (!Achievements::IsActive())
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."),
|
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."),
|
||||||
Host::OSD_INFO_DURATION);
|
Host::OSD_INFO_DURATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (!Achievements::HasLeaderboards())
|
||||||
if (!System::IsValid() || !Initialize())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!Achievements::HasLeaderboards() || !Achievements::PrepareLeaderboardsWindow())
|
|
||||||
{
|
{
|
||||||
ShowToast(std::string(), FSUI_STR("This game has no leaderboards."));
|
ShowToast(std::string(), FSUI_STR("This game has no leaderboards."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_state.current_main_window != MainWindowType::PauseMenu)
|
GPUThread::RunOnThread([]() {
|
||||||
{
|
if (!Initialize() || !Achievements::PrepareLeaderboardsWindow())
|
||||||
PauseForMenuOpen(false);
|
return;
|
||||||
ForceKeyNavEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
s_state.current_main_window = MainWindowType::Leaderboards;
|
if (s_state.current_main_window != MainWindowType::PauseMenu)
|
||||||
QueueResetFocus(FocusResetType::ViewChanged);
|
{
|
||||||
FixStateIfPaused();
|
PauseForMenuOpen(false);
|
||||||
|
ForceKeyNavEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_state.current_main_window = MainWindowType::Leaderboards;
|
||||||
|
QueueResetFocus(FocusResetType::ViewChanged);
|
||||||
|
UpdateRunIdleState();
|
||||||
|
FixStateIfPaused();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FullscreenUI::IsLeaderboardsWindowOpen()
|
bool FullscreenUI::IsLeaderboardsWindowOpen()
|
||||||
|
@ -7601,6 +7681,155 @@ bool FullscreenUI::IsLeaderboardsWindowOpen()
|
||||||
|
|
||||||
#endif // __ANDROID__
|
#endif // __ANDROID__
|
||||||
|
|
||||||
|
LoadingScreenProgressCallback::LoadingScreenProgressCallback()
|
||||||
|
: ProgressCallback(), m_open_time(Timer::GetCurrentValue()), m_on_gpu_thread(GPUThread::IsOnThread())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingScreenProgressCallback::~LoadingScreenProgressCallback()
|
||||||
|
{
|
||||||
|
// Did we activate?
|
||||||
|
if (m_last_progress_percent < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_on_gpu_thread)
|
||||||
|
{
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
ImGuiFullscreen::CloseLoadingScreen();
|
||||||
|
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
|
||||||
|
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// since this was pushing frames, we need to restore the context
|
||||||
|
GPUThread::Internal::RestoreContextAfterPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::PushState()
|
||||||
|
{
|
||||||
|
ProgressCallback::PushState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::PopState()
|
||||||
|
{
|
||||||
|
ProgressCallback::PopState();
|
||||||
|
Redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::SetCancellable(bool cancellable)
|
||||||
|
{
|
||||||
|
ProgressCallback::SetCancellable(cancellable);
|
||||||
|
Redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::SetTitle(const std::string_view title)
|
||||||
|
{
|
||||||
|
// todo?
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::SetStatusText(const std::string_view text)
|
||||||
|
{
|
||||||
|
ProgressCallback::SetStatusText(text);
|
||||||
|
Redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::SetProgressRange(u32 range)
|
||||||
|
{
|
||||||
|
u32 last_range = m_progress_range;
|
||||||
|
|
||||||
|
ProgressCallback::SetProgressRange(range);
|
||||||
|
|
||||||
|
if (m_progress_range != last_range)
|
||||||
|
Redraw(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::SetProgressValue(u32 value)
|
||||||
|
{
|
||||||
|
u32 lastValue = m_progress_value;
|
||||||
|
|
||||||
|
ProgressCallback::SetProgressValue(value);
|
||||||
|
|
||||||
|
if (m_progress_value != lastValue)
|
||||||
|
Redraw(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::Redraw(bool force)
|
||||||
|
{
|
||||||
|
if (m_last_progress_percent < 0 &&
|
||||||
|
Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - m_open_time) < m_open_delay)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int percent =
|
||||||
|
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
|
||||||
|
DebugAssert(percent >= 0);
|
||||||
|
if (percent == m_last_progress_percent && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// activation?
|
||||||
|
if (m_last_progress_percent < 0 && !m_on_gpu_thread)
|
||||||
|
{
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
Assert(!GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
|
||||||
|
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_last_progress_percent = percent;
|
||||||
|
if (m_on_gpu_thread)
|
||||||
|
{
|
||||||
|
ImGuiFullscreen::RenderLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, m_status_text, 0,
|
||||||
|
static_cast<s32>(m_progress_range), static_cast<s32>(m_progress_value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GPUThread::RunOnThread([status_text = SmallString(std::string_view(m_status_text)),
|
||||||
|
range = static_cast<s32>(m_progress_range), value = static_cast<s32>(m_progress_value)]() {
|
||||||
|
ImGuiFullscreen::OpenOrUpdateLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, status_text, 0, range, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreenProgressCallback::ModalError(const std::string_view message)
|
||||||
|
{
|
||||||
|
ERROR_LOG(message);
|
||||||
|
Host::ReportErrorAsync("Error", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadingScreenProgressCallback::ModalConfirmation(const std::string_view message)
|
||||||
|
{
|
||||||
|
INFO_LOG(message);
|
||||||
|
return Host::ConfirmMessage("Confirm", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FullscreenUI::OpenLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
|
||||||
|
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
|
||||||
|
{
|
||||||
|
Assert(GPUThread::IsOnThread());
|
||||||
|
Assert(!GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
|
||||||
|
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, true);
|
||||||
|
ImGuiFullscreen::OpenOrUpdateLoadingScreen(image, message, progress_min, progress_max, progress_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FullscreenUI::UpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
|
||||||
|
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
|
||||||
|
{
|
||||||
|
Assert(GPUThread::IsOnThread());
|
||||||
|
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
|
||||||
|
ImGuiFullscreen::OpenOrUpdateLoadingScreen(image, message, progress_min, progress_max, progress_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FullscreenUI::CloseLoadingScreen()
|
||||||
|
{
|
||||||
|
Assert(GPUThread::IsOnThread());
|
||||||
|
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
|
||||||
|
ImGuiFullscreen::CloseLoadingScreen();
|
||||||
|
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, false);
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Translation String Area
|
// Translation String Area
|
||||||
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top
|
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top
|
||||||
|
|
|
@ -21,7 +21,6 @@ bool IsInitialized();
|
||||||
bool HasActiveWindow();
|
bool HasActiveWindow();
|
||||||
void CheckForConfigChanges(const Settings& old_settings);
|
void CheckForConfigChanges(const Settings& old_settings);
|
||||||
void OnSystemStarted();
|
void OnSystemStarted();
|
||||||
void OnSystemPaused();
|
|
||||||
void OnSystemResumed();
|
void OnSystemResumed();
|
||||||
void OnSystemDestroyed();
|
void OnSystemDestroyed();
|
||||||
void OnRunningGameChanged();
|
void OnRunningGameChanged();
|
||||||
|
@ -43,13 +42,51 @@ void Render();
|
||||||
void InvalidateCoverCache();
|
void InvalidateCoverCache();
|
||||||
void TimeToPrintableString(SmallStringBase* str, time_t t);
|
void TimeToPrintableString(SmallStringBase* str, time_t t);
|
||||||
|
|
||||||
|
void OpenLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
|
||||||
|
s32 progress_value = -1);
|
||||||
|
void UpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
|
||||||
|
s32 progress_value = -1);
|
||||||
|
void CloseLoadingScreen();
|
||||||
|
|
||||||
} // namespace FullscreenUI
|
} // namespace FullscreenUI
|
||||||
|
|
||||||
|
class LoadingScreenProgressCallback final : public ProgressCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LoadingScreenProgressCallback();
|
||||||
|
~LoadingScreenProgressCallback() override;
|
||||||
|
|
||||||
|
ALWAYS_INLINE void SetOpenDelay(float delay) { m_open_delay = delay; }
|
||||||
|
|
||||||
|
void PushState() override;
|
||||||
|
void PopState() override;
|
||||||
|
|
||||||
|
void SetCancellable(bool cancellable) override;
|
||||||
|
void SetTitle(const std::string_view title) override;
|
||||||
|
void SetStatusText(const std::string_view text) override;
|
||||||
|
void SetProgressRange(u32 range) override;
|
||||||
|
void SetProgressValue(u32 value) override;
|
||||||
|
|
||||||
|
void ModalError(const std::string_view message) override;
|
||||||
|
bool ModalConfirmation(const std::string_view message) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Redraw(bool force);
|
||||||
|
|
||||||
|
u64 m_open_time = 0;
|
||||||
|
float m_open_delay = 1.0f;
|
||||||
|
s32 m_last_progress_percent = -1;
|
||||||
|
bool m_on_gpu_thread = false;
|
||||||
|
};
|
||||||
|
|
||||||
// Host UI triggers from Big Picture mode.
|
// Host UI triggers from Big Picture mode.
|
||||||
namespace Host {
|
namespace Host {
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
|
|
||||||
|
/// Called whenever fullscreen UI starts/stops.
|
||||||
|
void OnFullscreenUIStartedOrStopped(bool started);
|
||||||
|
|
||||||
/// Requests shut down and exit of the hosting application. This may not actually exit,
|
/// Requests shut down and exit of the hosting application. This may not actually exit,
|
||||||
/// if the user cancels the shutdown confirmation.
|
/// if the user cancels the shutdown confirmation.
|
||||||
void RequestExitApplication(bool allow_confirm);
|
void RequestExitApplication(bool allow_confirm);
|
||||||
|
|
1320
src/core/gpu.cpp
1320
src/core/gpu.cpp
File diff suppressed because it is too large
Load Diff
182
src/core/gpu.h
182
src/core/gpu.h
|
@ -39,17 +39,18 @@ enum class PacketType : u8;
|
||||||
class Recorder;
|
class Recorder;
|
||||||
class Player;
|
class Player;
|
||||||
} // namespace GPUDump
|
} // namespace GPUDump
|
||||||
struct Settings;
|
|
||||||
|
|
||||||
namespace Threading {
|
class GPUBackend;
|
||||||
class Thread;
|
struct Settings;
|
||||||
}
|
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
struct MemorySaveState;
|
struct MemorySaveState;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GPU
|
struct GPUBackendCommand;
|
||||||
|
struct GPUBackendDrawCommand;
|
||||||
|
|
||||||
|
class GPU final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class BlitterState : u8
|
enum class BlitterState : u8
|
||||||
|
@ -66,7 +67,6 @@ public:
|
||||||
DOT_TIMER_INDEX = 0,
|
DOT_TIMER_INDEX = 0,
|
||||||
HBLANK_TIMER_INDEX = 1,
|
HBLANK_TIMER_INDEX = 1,
|
||||||
MAX_RESOLUTION_SCALE = 32,
|
MAX_RESOLUTION_SCALE = 32,
|
||||||
DEINTERLACE_BUFFER_COUNT = 4,
|
|
||||||
DRAWING_AREA_COORD_MASK = 1023,
|
DRAWING_AREA_COORD_MASK = 1023,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,26 +92,15 @@ public:
|
||||||
|
|
||||||
// Base class constructor.
|
// Base class constructor.
|
||||||
GPU();
|
GPU();
|
||||||
virtual ~GPU();
|
~GPU();
|
||||||
|
|
||||||
virtual const Threading::Thread* GetSWThread() const = 0;
|
void Initialize();
|
||||||
virtual bool IsHardwareRenderer() const = 0;
|
void Reset(bool clear_vram);
|
||||||
|
bool DoState(StateWrapper& sw, bool update_display);
|
||||||
virtual bool Initialize(Error* error);
|
void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display);
|
||||||
virtual void Reset(bool clear_vram);
|
|
||||||
virtual bool DoState(StateWrapper& sw, bool update_display);
|
|
||||||
virtual bool DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display);
|
|
||||||
|
|
||||||
// Graphics API state reset/restore - call when drawing the UI etc.
|
|
||||||
// TODO: replace with "invalidate cached state"
|
|
||||||
virtual void RestoreDeviceContext();
|
|
||||||
|
|
||||||
// Render statistics debug window.
|
// Render statistics debug window.
|
||||||
void DrawDebugStateWindow(float scale);
|
void DrawDebugStateWindow(float scale);
|
||||||
void GetStatsString(SmallStringBase& str);
|
|
||||||
void GetMemoryStatsString(SmallStringBase& str);
|
|
||||||
void ResetStatistics();
|
|
||||||
void UpdateStatistics(u32 frame_count);
|
|
||||||
|
|
||||||
void CPUClockChanged();
|
void CPUClockChanged();
|
||||||
|
|
||||||
|
@ -175,31 +164,26 @@ public:
|
||||||
void SynchronizeCRTC();
|
void SynchronizeCRTC();
|
||||||
|
|
||||||
/// Recompile shaders/recreate framebuffers when needed.
|
/// 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.
|
/// Returns the full display resolution of the GPU, including padding.
|
||||||
std::tuple<u32, u32> GetFullDisplayResolution() const;
|
std::tuple<u32, u32> GetFullDisplayResolution() const;
|
||||||
|
|
||||||
|
/// Computes clamped drawing area.
|
||||||
|
static GSVector4i GetClampedDrawingArea(const GPUDrawingArea& drawing_area);
|
||||||
|
|
||||||
float ComputeHorizontalFrequency() const;
|
float ComputeHorizontalFrequency() const;
|
||||||
float ComputeVerticalFrequency() const;
|
float ComputeVerticalFrequency() const;
|
||||||
float ComputeDisplayAspectRatio() const;
|
float ComputeDisplayAspectRatio() const;
|
||||||
float ComputeSourceAspectRatio() const;
|
float ComputeSourceAspectRatio() const;
|
||||||
|
float ComputePixelAspectRatio() const;
|
||||||
|
|
||||||
/// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve
|
/// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve
|
||||||
/// the original pixel aspect ratio regardless of how much cropping has been applied.
|
/// the original pixel aspect ratio regardless of how much cropping has been applied.
|
||||||
float ComputeAspectRatioCorrection() const;
|
float ComputeAspectRatioCorrection() const;
|
||||||
|
|
||||||
/// Applies the pixel aspect ratio to a given size, preserving the larger dimension.
|
/// Applies the pixel aspect ratio to a given size, preserving the larger dimension.
|
||||||
void ApplyPixelAspectRatioToSize(float* width, float* height) const;
|
static void ApplyPixelAspectRatioToSize(float par, float* width, float* height);
|
||||||
|
|
||||||
static std::unique_ptr<GPU> CreateHardwareRenderer();
|
|
||||||
static std::unique_ptr<GPU> CreateSoftwareRenderer();
|
|
||||||
|
|
||||||
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns.
|
// 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,
|
void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
|
||||||
|
@ -231,38 +215,17 @@ public:
|
||||||
// Dumps raw VRAM to a file.
|
// Dumps raw VRAM to a file.
|
||||||
bool DumpVRAMToFile(const char* filename);
|
bool DumpVRAMToFile(const char* filename);
|
||||||
|
|
||||||
// Ensures all buffered vertices are drawn.
|
// Queues the current frame for presentation. Should only be used with runahead.
|
||||||
virtual void FlushRender() = 0;
|
void QueuePresentCurrentFrame();
|
||||||
|
|
||||||
/// Helper function for computing the draw rectangle in a larger window.
|
/// 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,
|
static void CalculateDrawRect(u32 window_width, u32 window_height, u32 crtc_display_width, u32 crtc_display_height,
|
||||||
GSVector4i* display_rect, GSVector4i* draw_rect) const;
|
s32 display_origin_left, s32 display_origin_top, u32 display_vram_width,
|
||||||
|
u32 display_vram_height, DisplayRotation rotation, DisplayAlignment alignment,
|
||||||
|
float pixel_aspect_ratio, bool stretch_vertically, bool integer_scale,
|
||||||
|
GSVector4i* display_rect, GSVector4i* draw_rect);
|
||||||
|
|
||||||
/// Helper function for computing screenshot bounds.
|
private:
|
||||||
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, Image* out_image);
|
|
||||||
|
|
||||||
/// 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:
|
|
||||||
TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const;
|
TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const;
|
||||||
TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
|
TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
|
||||||
|
|
||||||
|
@ -273,16 +236,6 @@ protected:
|
||||||
}
|
}
|
||||||
ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; }
|
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,
|
static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer,
|
||||||
bool remove_alpha);
|
bool remove_alpha);
|
||||||
|
|
||||||
|
@ -302,10 +255,10 @@ protected:
|
||||||
void UpdateGPUIdle();
|
void UpdateGPUIdle();
|
||||||
|
|
||||||
/// Returns 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...).
|
/// 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.
|
/// 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.
|
/// Updates drawing area that's suitablef or clamping.
|
||||||
void SetClampedDrawingArea();
|
void SetClampedDrawingArea();
|
||||||
|
@ -340,16 +293,13 @@ protected:
|
||||||
void InvalidateCLUT();
|
void InvalidateCLUT();
|
||||||
bool IsCLUTValid() const;
|
bool IsCLUTValid() const;
|
||||||
|
|
||||||
// Rendering in the backend
|
void ReadVRAM(u16 x, u16 y, u16 width, u16 height);
|
||||||
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
|
void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask);
|
||||||
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) = 0;
|
void UpdateDisplay(bool submit_frame);
|
||||||
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;
|
void PrepareForDraw();
|
||||||
virtual void DispatchRenderCommand() = 0;
|
void FinishPolyline();
|
||||||
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
|
void FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const;
|
||||||
virtual void UpdateDisplay() = 0;
|
|
||||||
virtual void DrawRendererStats();
|
|
||||||
virtual void OnBufferSwapped();
|
|
||||||
|
|
||||||
ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
|
ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
|
||||||
bool textured, bool semitransparent)
|
bool textured, bool semitransparent)
|
||||||
|
@ -446,14 +396,10 @@ protected:
|
||||||
u32 texture_window_value;
|
u32 texture_window_value;
|
||||||
|
|
||||||
// decoded values
|
// decoded values
|
||||||
|
// TODO: Make this a command
|
||||||
GPUTextureWindow texture_window;
|
GPUTextureWindow texture_window;
|
||||||
bool texture_x_flip;
|
bool texture_x_flip;
|
||||||
bool texture_y_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 = {};
|
} m_draw_mode = {};
|
||||||
|
|
||||||
GPUDrawingArea m_drawing_area = {};
|
GPUDrawingArea m_drawing_area = {};
|
||||||
|
@ -587,65 +533,7 @@ protected:
|
||||||
TickCount m_max_run_ahead = 128;
|
TickCount m_max_run_ahead = 128;
|
||||||
u32 m_fifo_size = 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:
|
private:
|
||||||
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
|
|
||||||
|
|
||||||
using GP0CommandHandler = bool (GPU::*)();
|
using GP0CommandHandler = bool (GPU::*)();
|
||||||
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
|
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
|
||||||
static GP0CommandHandlerTable GenerateGP0CommandHandlerTable();
|
static GP0CommandHandlerTable GenerateGP0CommandHandlerTable();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gpu_types.h"
|
#include "gpu_thread_commands.h"
|
||||||
|
|
||||||
|
#include "util/gpu_device.h"
|
||||||
|
|
||||||
#include "common/heap_array.h"
|
#include "common/heap_array.h"
|
||||||
#include "common/threading.h"
|
#include "common/threading.h"
|
||||||
|
@ -12,85 +14,193 @@
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
class Error;
|
||||||
#pragma warning(push)
|
class SmallStringBase;
|
||||||
#pragma warning(disable : 4324) // warning C4324: 'GPUBackend': structure was padded due to alignment specifier
|
|
||||||
#endif
|
class GPUFramebuffer;
|
||||||
|
class GPUPipeline;
|
||||||
|
|
||||||
|
struct Settings;
|
||||||
|
class StateWrapper;
|
||||||
|
|
||||||
|
namespace System {
|
||||||
|
struct MemorySaveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
class GPUBackend
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
static GPUThreadCommand* NewClearVRAMCommand();
|
||||||
|
static GPUThreadCommand* NewClearDisplayCommand();
|
||||||
|
static GPUBackendUpdateDisplayCommand* NewUpdateDisplayCommand();
|
||||||
|
static GPUBackendSubmitFrameCommand* NewSubmitFrameCommand();
|
||||||
|
static GPUThreadCommand* NewClearCacheCommand();
|
||||||
|
static GPUThreadCommand* NewBufferSwappedCommand();
|
||||||
|
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 void SyncGPUThread(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, Image* out_image);
|
||||||
|
static void RenderScreenshotToFile(const std::string_view path, DisplayScreenshotMode mode, u8 quality,
|
||||||
|
bool compress_on_thread, bool show_osd_message);
|
||||||
|
|
||||||
|
static bool BeginQueueFrame();
|
||||||
|
static void WaitForOneQueuedFrame();
|
||||||
|
static u32 GetQueuedFrameCount();
|
||||||
|
|
||||||
|
static bool AllocateMemorySaveStates(std::span<System::MemorySaveState> states, Error* error);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GPUBackend();
|
GPUBackend();
|
||||||
virtual ~GPUBackend();
|
virtual ~GPUBackend();
|
||||||
|
|
||||||
ALWAYS_INLINE const Threading::Thread* GetThread() const { return m_use_gpu_thread ? &m_gpu_thread : nullptr; }
|
virtual bool Initialize(bool upload_vram, Error* error);
|
||||||
ALWAYS_INLINE bool IsUsingThread() const { return m_use_gpu_thread; }
|
|
||||||
|
|
||||||
virtual bool Initialize(bool use_thread);
|
virtual void UpdateSettings(const Settings& old_settings);
|
||||||
virtual void Reset();
|
|
||||||
virtual void Shutdown();
|
|
||||||
|
|
||||||
void SetThreadEnabled(bool use_thread);
|
/// Returns the current resolution scale.
|
||||||
|
virtual u32 GetResolutionScale() const = 0;
|
||||||
|
|
||||||
GPUBackendFillVRAMCommand* NewFillVRAMCommand();
|
/// Updates the resolution scale when it's set to automatic.
|
||||||
GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
|
virtual void UpdateResolutionScale() = 0;
|
||||||
GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
|
|
||||||
GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
|
|
||||||
GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
|
|
||||||
GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
|
|
||||||
GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
|
|
||||||
GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
|
|
||||||
|
|
||||||
void PushCommand(GPUBackendCommand* cmd);
|
// Graphics API state reset/restore - call when drawing the UI etc.
|
||||||
void Sync(bool allow_sleep);
|
// TODO: replace with "invalidate cached state"
|
||||||
|
virtual void RestoreDeviceContext() = 0;
|
||||||
|
|
||||||
/// Processes all pending GPU commands.
|
/// Main command handler for GPU thread.
|
||||||
void RunGPULoop();
|
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);
|
||||||
|
|
||||||
|
/// Helper function for computing screenshot bounds.
|
||||||
|
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
|
||||||
|
GSVector4i* draw_rect) const;
|
||||||
|
|
||||||
|
void GetStatsString(SmallStringBase& str) const;
|
||||||
|
void GetMemoryStatsString(SmallStringBase& str) const;
|
||||||
|
|
||||||
|
void ResetStatistics();
|
||||||
|
void UpdateStatistics(u32 frame_count);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void* AllocateCommand(GPUBackendCommandType command, u32 size);
|
|
||||||
u32 GetPendingCommandSize() const;
|
|
||||||
void WakeGPUThread();
|
|
||||||
void StartGPUThread();
|
|
||||||
void StopGPUThread();
|
|
||||||
|
|
||||||
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 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 UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
|
|
||||||
|
|
||||||
void HandleCommand(const GPUBackendCommand* cmd);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
COMMAND_QUEUE_SIZE = 4 * 1024 * 1024,
|
DEINTERLACE_BUFFER_COUNT = 4,
|
||||||
THRESHOLD_TO_WAKE_GPU = 256
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FixedHeapArray<u8, COMMAND_QUEUE_SIZE> m_command_fifo_data;
|
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
|
||||||
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_read_ptr{0};
|
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering,
|
||||||
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_write_ptr{0};
|
u8 interlaced_display_field) = 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, bool set_mask,
|
||||||
|
bool check_mask) = 0;
|
||||||
|
|
||||||
|
virtual void DrawPolygon(const GPUBackendDrawPolygonCommand* 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 DrawingAreaChanged() = 0;
|
||||||
|
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
|
||||||
|
virtual void ClearCache() = 0;
|
||||||
|
virtual void OnBufferSwapped() = 0;
|
||||||
|
virtual void ClearVRAM() = 0;
|
||||||
|
|
||||||
|
virtual void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) = 0;
|
||||||
|
|
||||||
|
virtual void LoadState(const GPUBackendLoadStateCommand* cmd) = 0;
|
||||||
|
|
||||||
|
virtual bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) = 0;
|
||||||
|
virtual void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) = 0;
|
||||||
|
|
||||||
|
/// Ensures all pending draws are flushed to the host GPU.
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// 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, Image* out_image);
|
||||||
|
|
||||||
|
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
|
||||||
|
|
||||||
|
void HandleUpdateDisplayCommand(const GPUBackendUpdateDisplayCommand* cmd);
|
||||||
|
void HandleSubmitFrameCommand(const GPUBackendFramePresentationParameters* 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_pixel_aspect_ratio = 1.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;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
namespace Host {
|
||||||
#pragma warning(pop)
|
|
||||||
#endif
|
/// Called at the end of the frame, before presentation.
|
||||||
|
void FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number);
|
||||||
|
|
||||||
|
} // namespace Host
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#include "cpu_pgxp.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_backend.h"
|
||||||
#include "gpu_dump.h"
|
#include "gpu_dump.h"
|
||||||
#include "gpu_hw_texture_cache.h"
|
#include "gpu_hw_texture_cache.h"
|
||||||
|
#include "gpu_thread_commands.h"
|
||||||
#include "interrupt_controller.h"
|
#include "interrupt_controller.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/gsvector_formatter.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
@ -93,7 +97,7 @@ void GPU::TryExecuteCommands()
|
||||||
// drop terminator
|
// drop terminator
|
||||||
m_fifo.RemoveOne();
|
m_fifo.RemoveOne();
|
||||||
DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
|
DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
|
||||||
DispatchRenderCommand();
|
FinishPolyline();
|
||||||
m_blit_buffer.clear();
|
m_blit_buffer.clear();
|
||||||
EndCommand();
|
EndCommand();
|
||||||
continue;
|
continue;
|
||||||
|
@ -200,8 +204,8 @@ bool GPU::HandleNOPCommand()
|
||||||
bool GPU::HandleClearCacheCommand()
|
bool GPU::HandleClearCacheCommand()
|
||||||
{
|
{
|
||||||
DEBUG_LOG("GP0 clear cache");
|
DEBUG_LOG("GP0 clear cache");
|
||||||
m_draw_mode.SetTexturePageChanged();
|
|
||||||
InvalidateCLUT();
|
InvalidateCLUT();
|
||||||
|
GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand());
|
||||||
m_fifo.RemoveOne();
|
m_fifo.RemoveOne();
|
||||||
AddCommandTicks(1);
|
AddCommandTicks(1);
|
||||||
EndCommand();
|
EndCommand();
|
||||||
|
@ -248,8 +252,6 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand()
|
||||||
DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
|
DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
|
||||||
if (m_drawing_area.left != left || m_drawing_area.top != top)
|
if (m_drawing_area.left != left || m_drawing_area.top != top)
|
||||||
{
|
{
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_area.left = left;
|
m_drawing_area.left = left;
|
||||||
m_drawing_area.top = top;
|
m_drawing_area.top = top;
|
||||||
m_drawing_area_changed = true;
|
m_drawing_area_changed = true;
|
||||||
|
@ -270,8 +272,6 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand()
|
||||||
DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom);
|
DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom);
|
||||||
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
|
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
|
||||||
{
|
{
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_area.right = right;
|
m_drawing_area.right = right;
|
||||||
m_drawing_area.bottom = bottom;
|
m_drawing_area.bottom = bottom;
|
||||||
m_drawing_area_changed = true;
|
m_drawing_area_changed = true;
|
||||||
|
@ -291,8 +291,6 @@ bool GPU::HandleSetDrawingOffsetCommand()
|
||||||
DEBUG_LOG("Set drawing offset ({}, {})", x, y);
|
DEBUG_LOG("Set drawing offset ({}, {})", x, y);
|
||||||
if (m_drawing_offset.x != x || m_drawing_offset.y != y)
|
if (m_drawing_offset.x != x || m_drawing_offset.y != y)
|
||||||
{
|
{
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_offset.x = x;
|
m_drawing_offset.x = x;
|
||||||
m_drawing_offset.y = y;
|
m_drawing_offset.y = y;
|
||||||
}
|
}
|
||||||
|
@ -308,11 +306,7 @@ bool GPU::HandleSetMaskBitCommand()
|
||||||
|
|
||||||
constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
|
constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
|
||||||
const u32 gpustat_bits = (param & 0x03) << 11;
|
const u32 gpustat_bits = (param & 0x03) << 11;
|
||||||
if ((m_GPUSTAT.bits & gpustat_mask) != gpustat_bits)
|
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
|
||||||
{
|
|
||||||
FlushRender();
|
|
||||||
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
|
|
||||||
}
|
|
||||||
DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
|
DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
|
||||||
BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
|
BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
|
||||||
|
|
||||||
|
@ -321,6 +315,35 @@ bool GPU::HandleSetMaskBitCommand()
|
||||||
return true;
|
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::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const
|
||||||
|
{
|
||||||
|
cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
|
||||||
|
cmd->active_line_lsb = ConvertToBoolUnchecked(m_crtc_state.active_line_lsb);
|
||||||
|
cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
|
||||||
|
cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
|
||||||
|
cmd->texture_enable = rc.IsTexturingEnabled();
|
||||||
|
cmd->raw_texture_enable = rc.raw_texture_enable;
|
||||||
|
cmd->transparency_enable = rc.transparency_enable;
|
||||||
|
cmd->shading_enable = rc.shading_enable;
|
||||||
|
cmd->quad_polygon = rc.quad_polygon;
|
||||||
|
cmd->dither_enable = rc.IsDitheringEnabled() && m_draw_mode.mode_reg.dither_enable;
|
||||||
|
|
||||||
|
cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
|
||||||
|
cmd->palette.bits = m_draw_mode.palette_reg.bits;
|
||||||
|
cmd->window = m_draw_mode.texture_window;
|
||||||
|
}
|
||||||
|
|
||||||
bool GPU::HandleRenderPolygonCommand()
|
bool GPU::HandleRenderPolygonCommand()
|
||||||
{
|
{
|
||||||
const GPURenderCommand rc{FifoPeek(0)};
|
const GPURenderCommand rc{FifoPeek(0)};
|
||||||
|
@ -346,6 +369,7 @@ bool GPU::HandleRenderPolygonCommand()
|
||||||
words_per_vertex, setup_ticks);
|
words_per_vertex, setup_ticks);
|
||||||
|
|
||||||
// set draw state up
|
// set draw state up
|
||||||
|
// TODO: Get rid of SetTexturePalette() and just fill it as needed
|
||||||
if (rc.texture_enable)
|
if (rc.texture_enable)
|
||||||
{
|
{
|
||||||
const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
|
const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
|
||||||
|
@ -355,12 +379,233 @@ bool GPU::HandleRenderPolygonCommand()
|
||||||
UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
|
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_render_command.bits = rc.bits;
|
||||||
m_fifo.RemoveOne();
|
m_fifo.RemoveOne();
|
||||||
|
|
||||||
DispatchRenderCommand();
|
PrepareForDraw();
|
||||||
|
|
||||||
|
if (g_settings.gpu_pgxp_enable)
|
||||||
|
{
|
||||||
|
GPUBackendDrawPrecisePolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPrecisePolygonCommand(num_vertices);
|
||||||
|
FillDrawCommand(cmd, rc);
|
||||||
|
cmd->num_vertices = Truncate16(num_vertices);
|
||||||
|
|
||||||
|
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* RESTRICT 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<false>(&v.x, GSVector2(GSVector2i::load<false>(&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<false>(&cmd->vertices[0].x);
|
||||||
|
const GSVector2 v1f = GSVector2::load<false>(&cmd->vertices[1].x);
|
||||||
|
const GSVector2 v2f = GSVector2::load<false>(&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<false>(&cmd->vertices[0].native_x),
|
||||||
|
GSVector2i::load<false>(&cmd->vertices[1].native_x),
|
||||||
|
GSVector2i::load<false>(&cmd->vertices[2].native_x), rc.shading_enable, rc.texture_enable,
|
||||||
|
rc.transparency_enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// quads
|
||||||
|
if (rc.quad_polygon)
|
||||||
|
{
|
||||||
|
const GSVector2 v3f = GSVector2::load<false>(&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.
|
||||||
|
// NOTE: Culling this way results in subtle differences with UV clamping, since the fourth vertex is no
|
||||||
|
// longer considered in the range. This is mainly apparent when the UV gradient is zero. Seems like it
|
||||||
|
// generally looks better this way, so I'm keeping it.
|
||||||
|
cmd->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
|
||||||
|
3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
|
||||||
|
cmd->num_vertices = 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddDrawTriangleTicks(GSVector2i::load<false>(&cmd->vertices[2].native_x),
|
||||||
|
GSVector2i::load<false>(&cmd->vertices[1].native_x),
|
||||||
|
GSVector2i::load<false>(&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->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
|
||||||
|
3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
|
||||||
|
cmd->num_vertices = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUBackend::PushCommand(cmd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GPUBackendDrawPolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPolygonCommand(num_vertices);
|
||||||
|
FillDrawCommand(cmd, rc);
|
||||||
|
cmd->num_vertices = Truncate16(num_vertices);
|
||||||
|
|
||||||
|
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* RESTRICT 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<false>(&cmd->vertices[0].x);
|
||||||
|
const GSVector2i v1 = GSVector2i::load<false>(&cmd->vertices[1].x);
|
||||||
|
const GSVector2i v2 = GSVector2i::load<false>(&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<false>(&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->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
|
||||||
|
3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
|
||||||
|
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->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
|
||||||
|
3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
|
||||||
|
cmd->num_vertices = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUBackend::PushCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
EndCommand();
|
EndCommand();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -389,12 +634,65 @@ bool GPU::HandleRenderRectangleCommand()
|
||||||
rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
|
rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
|
||||||
rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
|
rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
|
||||||
|
|
||||||
m_counters.num_vertices++;
|
|
||||||
m_counters.num_primitives++;
|
|
||||||
m_render_command.bits = rc.bits;
|
m_render_command.bits = rc.bits;
|
||||||
m_fifo.RemoveOne();
|
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();
|
EndCommand();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -411,12 +709,55 @@ bool GPU::HandleRenderLineCommand()
|
||||||
TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
|
TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
|
||||||
rc.shading_enable ? "shaded" : "monochrome", total_words);
|
rc.shading_enable ? "shaded" : "monochrome", total_words);
|
||||||
|
|
||||||
m_counters.num_vertices += 2;
|
|
||||||
m_counters.num_primitives++;
|
|
||||||
m_render_command.bits = rc.bits;
|
m_render_command.bits = rc.bits;
|
||||||
m_fifo.RemoveOne();
|
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<false>(&cmd->vertices[0].x);
|
||||||
|
const GSVector2i v1 = GSVector2i::load<false>(&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();
|
EndCommand();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -453,6 +794,64 @@ bool GPU::HandleRenderPolyLineCommand()
|
||||||
return true;
|
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<false>(&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<false>(&out_vertex[0].x, start_pos);
|
||||||
|
out_vertex[0].color = start_color;
|
||||||
|
GSVector2i::store<false>(&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()
|
bool GPU::HandleFillRectangleCommand()
|
||||||
{
|
{
|
||||||
CHECK_COMMAND_SIZE(3);
|
CHECK_COMMAND_SIZE(3);
|
||||||
|
@ -460,8 +859,6 @@ bool GPU::HandleFillRectangleCommand()
|
||||||
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
|
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
|
||||||
SynchronizeCRTC();
|
SynchronizeCRTC();
|
||||||
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
const u32 color = FifoPop() & 0x00FFFFFF;
|
const u32 color = FifoPop() & 0x00FFFFFF;
|
||||||
const u32 dst_x = FifoPeek() & 0x3F0;
|
const u32 dst_x = FifoPeek() & 0x3F0;
|
||||||
const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
|
const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
|
||||||
|
@ -471,9 +868,18 @@ bool GPU::HandleFillRectangleCommand()
|
||||||
DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
|
DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
|
||||||
|
|
||||||
if (width > 0 && height > 0)
|
if (width > 0 && height > 0)
|
||||||
FillVRAM(dst_x, dst_y, width, height, color);
|
{
|
||||||
|
GPUBackendFillVRAMCommand* cmd = GPUBackend::NewFillVRAMCommand();
|
||||||
|
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;
|
||||||
|
cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
|
||||||
|
cmd->active_line_lsb = m_crtc_state.active_line_lsb;
|
||||||
|
GPUBackend::PushCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
m_counters.num_writes++;
|
|
||||||
AddCommandTicks(46 + ((width / 8) + 9) * height);
|
AddCommandTicks(46 + ((width / 8) + 9) * height);
|
||||||
EndCommand();
|
EndCommand();
|
||||||
return true;
|
return true;
|
||||||
|
@ -523,8 +929,6 @@ void GPU::FinishVRAMWrite()
|
||||||
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
|
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
|
||||||
SynchronizeCRTC();
|
SynchronizeCRTC();
|
||||||
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
if (m_blit_remaining_words == 0)
|
if (m_blit_remaining_words == 0)
|
||||||
{
|
{
|
||||||
if (g_settings.debugging.dump_cpu_to_vram_copies)
|
if (g_settings.debugging.dump_cpu_to_vram_copies)
|
||||||
|
@ -557,18 +961,18 @@ void GPU::FinishVRAMWrite()
|
||||||
const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data());
|
const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data());
|
||||||
if (transferred_full_rows > 0)
|
if (transferred_full_rows > 0)
|
||||||
{
|
{
|
||||||
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, transferred_full_rows, blit_ptr,
|
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, static_cast<u16>(transferred_full_rows),
|
||||||
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
|
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);
|
blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16);
|
||||||
}
|
}
|
||||||
if (transferred_width_last_row > 0)
|
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,
|
UpdateVRAM(m_vram_transfer.x, static_cast<u16>(m_vram_transfer.y + transferred_full_rows),
|
||||||
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
|
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_blit_buffer.clear();
|
||||||
m_vram_transfer = {};
|
m_vram_transfer = {};
|
||||||
m_blitter_state = BlitterState::Idle;
|
m_blitter_state = BlitterState::Idle;
|
||||||
|
@ -588,9 +992,6 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
|
||||||
m_vram_transfer.width, m_vram_transfer.height);
|
m_vram_transfer.width, m_vram_transfer.height);
|
||||||
DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
|
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
|
// ensure VRAM shadow is up to date
|
||||||
ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
|
ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
|
||||||
|
|
||||||
|
@ -602,7 +1003,6 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch to pixel-by-pixel read state
|
// switch to pixel-by-pixel read state
|
||||||
m_counters.num_reads++;
|
|
||||||
m_blitter_state = BlitterState::ReadingVRAM;
|
m_blitter_state = BlitterState::ReadingVRAM;
|
||||||
m_command_total_words = 0;
|
m_command_total_words = 0;
|
||||||
|
|
||||||
|
@ -633,10 +1033,16 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
|
||||||
width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
|
width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
|
||||||
if (!skip_copy)
|
if (!skip_copy)
|
||||||
{
|
{
|
||||||
m_counters.num_copies++;
|
GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand();
|
||||||
|
cmd->src_x = static_cast<u16>(src_x);
|
||||||
FlushRender();
|
cmd->src_y = static_cast<u16>(src_y);
|
||||||
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
|
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);
|
||||||
|
cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
|
||||||
|
cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
|
||||||
|
GPUBackend::PushCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCommandTicks(width * height * 2);
|
AddCommandTicks(width * height * 2);
|
||||||
|
|
1546
src/core/gpu_hw.cpp
1546
src/core/gpu_hw.cpp
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gpu.h"
|
#include "gpu_backend.h"
|
||||||
#include "gpu_hw_texture_cache.h"
|
#include "gpu_hw_texture_cache.h"
|
||||||
|
|
||||||
#include "util/gpu_device.h"
|
#include "util/gpu_device.h"
|
||||||
|
@ -21,7 +21,9 @@ class GPU_SW_Backend;
|
||||||
struct GPUBackendCommand;
|
struct GPUBackendCommand;
|
||||||
struct GPUBackendDrawCommand;
|
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:
|
public:
|
||||||
enum class BatchRenderMode : u8
|
enum class BatchRenderMode : u8
|
||||||
|
@ -63,22 +65,41 @@ public:
|
||||||
GPU_HW();
|
GPU_HW();
|
||||||
~GPU_HW() override;
|
~GPU_HW() override;
|
||||||
|
|
||||||
const Threading::Thread* GetSWThread() const override;
|
bool Initialize(bool upload_vram, Error* error) override;
|
||||||
bool IsHardwareRenderer() const override;
|
|
||||||
|
|
||||||
bool Initialize(Error* error) override;
|
u32 GetResolutionScale() const override;
|
||||||
void Reset(bool clear_vram) override;
|
|
||||||
bool DoState(StateWrapper& sw, bool update_display) override;
|
|
||||||
bool DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display) override;
|
|
||||||
|
|
||||||
void RestoreDeviceContext() override;
|
void RestoreDeviceContext() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
void UpdateSettings(const Settings& old_settings) override;
|
void UpdateSettings(const Settings& old_settings) override;
|
||||||
|
|
||||||
u32 GetResolutionScale() const override;
|
|
||||||
void UpdateResolutionScale() override;
|
void UpdateResolutionScale() override;
|
||||||
|
|
||||||
void UpdateDisplay() override;
|
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) 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, bool set_mask,
|
||||||
|
bool check_mask) 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;
|
||||||
|
|
||||||
|
bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override;
|
||||||
|
void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override;
|
||||||
|
|
||||||
|
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum : u32
|
enum : u32
|
||||||
|
@ -87,6 +108,7 @@ private:
|
||||||
MAX_VERTICES_FOR_RECTANGLE = 6 * (((MAX_PRIMITIVE_WIDTH + (TEXTURE_PAGE_WIDTH - 1)) / TEXTURE_PAGE_WIDTH) + 1u) *
|
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),
|
(((MAX_PRIMITIVE_HEIGHT + (TEXTURE_PAGE_HEIGHT - 1)) / TEXTURE_PAGE_HEIGHT) + 1u),
|
||||||
NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount),
|
NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount),
|
||||||
|
INVALID_DRAW_MODE_BITS = 0xFFFFFFFFu,
|
||||||
};
|
};
|
||||||
enum : u8
|
enum : u8
|
||||||
{
|
{
|
||||||
|
@ -165,8 +187,6 @@ private:
|
||||||
bool CompileResolutionDependentPipelines(Error* error);
|
bool CompileResolutionDependentPipelines(Error* error);
|
||||||
bool CompileDownsamplePipelines(Error* error);
|
bool CompileDownsamplePipelines(Error* error);
|
||||||
|
|
||||||
void LoadVertices();
|
|
||||||
|
|
||||||
void PrintSettingsToLog();
|
void PrintSettingsToLog();
|
||||||
void CheckSettings();
|
void CheckSettings();
|
||||||
|
|
||||||
|
@ -185,8 +205,10 @@ private:
|
||||||
u32 CalculateResolutionScale() const;
|
u32 CalculateResolutionScale() const;
|
||||||
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
|
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
|
||||||
|
|
||||||
|
bool ShouldDrawWithSoftwareRenderer() const;
|
||||||
|
|
||||||
bool IsUsingMultisampling() const;
|
bool IsUsingMultisampling() const;
|
||||||
bool IsUsingDownsampling() const;
|
bool IsUsingDownsampling(const GPUBackendUpdateDisplayCommand* cmd) const;
|
||||||
|
|
||||||
void SetFullVRAMDirtyRectangle();
|
void SetFullVRAMDirtyRectangle();
|
||||||
void ClearVRAMDirtyRectangle();
|
void ClearVRAMDirtyRectangle();
|
||||||
|
@ -196,12 +218,15 @@ private:
|
||||||
void AddUnclampedDrawnRectangle(const GSVector4i rect);
|
void AddUnclampedDrawnRectangle(const GSVector4i rect);
|
||||||
void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
|
void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
|
||||||
|
|
||||||
void CheckForTexPageOverlap(GSVector4i uv_rect);
|
void CheckForTexPageOverlap(const GPUBackendDrawCommand* cmd, GSVector4i uv_rect);
|
||||||
bool ShouldCheckForTexPageOverlap() const;
|
bool ShouldCheckForTexPageOverlap() const;
|
||||||
|
|
||||||
bool IsFlushed() const;
|
bool IsFlushed() const;
|
||||||
void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices);
|
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_precise, bool is_3d);
|
||||||
void ResetBatchVertexDepth();
|
void ResetBatchVertexDepth();
|
||||||
|
|
||||||
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
|
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
|
||||||
|
@ -213,20 +238,6 @@ private:
|
||||||
/// Returns true if the draw is going to use shader blending/framebuffer fetch.
|
/// Returns true if the draw is going to use shader blending/framebuffer fetch.
|
||||||
bool NeedsShaderBlending(GPUTransparencyMode transparency, BatchTextureMode texture, bool check_mask) const;
|
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,
|
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 check_mask, const GSVector4i bounds);
|
||||||
bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
|
bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
|
||||||
|
@ -235,17 +246,17 @@ private:
|
||||||
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
|
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
|
||||||
|
|
||||||
/// Handles quads with flipped texture coordinate directions.
|
/// 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 IsPossibleSpritePolygon(const BatchVertex* vertices) const;
|
||||||
bool ExpandLineTriangles(BatchVertex* vertices);
|
bool ExpandLineTriangles(BatchVertex* vertices);
|
||||||
|
|
||||||
/// Computes polygon U/V boundaries, and for overlap with the current texture page.
|
/// 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.
|
/// Sets the depth test flag for PGXP depth buffering.
|
||||||
void SetBatchDepthBuffer(bool enabled);
|
void SetBatchDepthBuffer(const GPUBackendDrawCommand* cmd, bool enabled);
|
||||||
void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices);
|
void CheckForDepthClear(const GPUBackendDrawCommand* cmd, const BatchVertex* vertices, u32 num_vertices);
|
||||||
void SetBatchSpriteMode(bool enabled);
|
void SetBatchSpriteMode(const GPUBackendDrawCommand* cmd, bool enabled);
|
||||||
|
|
||||||
void UpdateDownsamplingLevels();
|
void UpdateDownsamplingLevels();
|
||||||
|
|
||||||
|
@ -263,8 +274,6 @@ private:
|
||||||
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
|
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
|
||||||
std::unique_ptr<GPUTexture> m_vram_write_texture;
|
std::unique_ptr<GPUTexture> m_vram_write_texture;
|
||||||
|
|
||||||
std::unique_ptr<GPU_SW_Backend> m_sw_renderer;
|
|
||||||
|
|
||||||
BatchVertex* m_batch_vertex_ptr = nullptr;
|
BatchVertex* m_batch_vertex_ptr = nullptr;
|
||||||
u16* m_batch_index_ptr = nullptr;
|
u16* m_batch_index_ptr = nullptr;
|
||||||
u32 m_batch_base_vertex = 0;
|
u32 m_batch_base_vertex = 0;
|
||||||
|
@ -306,18 +315,32 @@ private:
|
||||||
u8 m_texpage_dirty = 0;
|
u8 m_texpage_dirty = 0;
|
||||||
|
|
||||||
bool m_batch_ubo_dirty = true;
|
bool m_batch_ubo_dirty = true;
|
||||||
|
bool m_drawing_area_changed = true;
|
||||||
BatchConfig m_batch;
|
BatchConfig m_batch;
|
||||||
|
|
||||||
// Changed state
|
// Changed state
|
||||||
BatchUBOData m_batch_ubo_data = {};
|
BatchUBOData m_batch_ubo_data = {};
|
||||||
|
|
||||||
// Bounding box of VRAM area that the GPU has drawn into.
|
// 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_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_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_uv_rect = INVALID_RECT;
|
||||||
GSVector4i m_current_draw_rect = INVALID_RECT;
|
GSVector4i m_current_draw_rect = INVALID_RECT;
|
||||||
alignas(8) s32 m_current_texture_page_offset[2] = {};
|
alignas(8) 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;
|
std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
|
||||||
|
|
||||||
// [wrapped][interlaced]
|
// [wrapped][interlaced]
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
#include "gpu_hw.h"
|
#include "gpu_hw.h"
|
||||||
#include "gpu_hw_shadergen.h"
|
#include "gpu_hw_shadergen.h"
|
||||||
#include "gpu_sw_rasterizer.h"
|
#include "gpu_sw_rasterizer.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
#include "imgui_overlays.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
|
||||||
#include "util/gpu_device.h"
|
#include "util/gpu_device.h"
|
||||||
|
#include "util/imgui_fullscreen.h"
|
||||||
#include "util/imgui_manager.h"
|
#include "util/imgui_manager.h"
|
||||||
#include "util/state_wrapper.h"
|
#include "util/state_wrapper.h"
|
||||||
|
|
||||||
|
@ -50,6 +53,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 GPUTexture::Format REPLACEMENT_TEXTURE_FORMAT = GPUTexture::Format::RGBA8;
|
||||||
static constexpr const char LOCAL_CONFIG_FILENAME[] = "config.yaml";
|
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.
|
// Has to be public because it's referenced in Source.
|
||||||
struct HashCacheEntry
|
struct HashCacheEntry
|
||||||
{
|
{
|
||||||
|
@ -518,6 +524,7 @@ struct GPUTextureCacheState
|
||||||
|
|
||||||
GPUTexture::Format hash_cache_texture_format = GPUTexture::Format::Unknown;
|
GPUTexture::Format hash_cache_texture_format = GPUTexture::Format::Unknown;
|
||||||
HashCache hash_cache;
|
HashCache hash_cache;
|
||||||
|
GPU_HW* hw_backend = nullptr; // TODO:FIXME: remove me
|
||||||
|
|
||||||
/// List of candidates for purging when the hash cache gets too large.
|
/// List of candidates for purging when the hash cache gets too large.
|
||||||
std::vector<std::pair<HashCache::iterator, s32>> hash_cache_purge_list;
|
std::vector<std::pair<HashCache::iterator, s32>> hash_cache_purge_list;
|
||||||
|
@ -529,7 +536,6 @@ struct GPUTextureCacheState
|
||||||
std::unique_ptr<GPUPipeline> replacement_draw_pipeline; // copies alpha as-is
|
std::unique_ptr<GPUPipeline> replacement_draw_pipeline; // copies alpha as-is
|
||||||
std::unique_ptr<GPUPipeline> replacement_semitransparent_draw_pipeline; // inverts alpha (i.e. semitransparent)
|
std::unique_ptr<GPUPipeline> replacement_semitransparent_draw_pipeline; // inverts alpha (i.e. semitransparent)
|
||||||
|
|
||||||
std::string game_id;
|
|
||||||
VRAMReplacementMap vram_replacements;
|
VRAMReplacementMap vram_replacements;
|
||||||
|
|
||||||
// TODO: Combine these into one map?
|
// TODO: Combine these into one map?
|
||||||
|
@ -555,26 +561,28 @@ ALIGN_TO_CACHE_LINE GPUTextureCacheState s_state;
|
||||||
|
|
||||||
bool GPUTextureCache::ShouldTrackVRAMWrites()
|
bool GPUTextureCache::ShouldTrackVRAMWrites()
|
||||||
{
|
{
|
||||||
if (!g_settings.gpu_texture_cache)
|
if (!g_gpu_settings.gpu_texture_cache)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
#ifdef ALWAYS_TRACK_VRAM_WRITES
|
#ifdef ALWAYS_TRACK_VRAM_WRITES
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
return (IsDumpingVRAMWriteTextures() ||
|
return (IsDumpingVRAMWriteTextures() ||
|
||||||
(g_settings.texture_replacements.enable_texture_replacements && HasVRAMWriteTextureReplacements()));
|
(g_gpu_settings.texture_replacements.enable_texture_replacements && HasVRAMWriteTextureReplacements()));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPUTextureCache::IsDumpingVRAMWriteTextures()
|
bool GPUTextureCache::IsDumpingVRAMWriteTextures()
|
||||||
{
|
{
|
||||||
return (g_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages);
|
return (g_gpu_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPUTextureCache::Initialize()
|
bool GPUTextureCache::Initialize(GPU_HW* backend)
|
||||||
{
|
{
|
||||||
|
s_state.hw_backend = backend;
|
||||||
|
|
||||||
SetHashCacheTextureFormat();
|
SetHashCacheTextureFormat();
|
||||||
LoadLocalConfiguration(false, false);
|
ReloadTextureReplacements(false);
|
||||||
UpdateVRAMTrackingState();
|
UpdateVRAMTrackingState();
|
||||||
if (!CompilePipelines())
|
if (!CompilePipelines())
|
||||||
return false;
|
return false;
|
||||||
|
@ -588,7 +596,7 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
|
||||||
{
|
{
|
||||||
UpdateVRAMTrackingState();
|
UpdateVRAMTrackingState();
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_texture_replacements !=
|
if (g_gpu_settings.texture_replacements.enable_texture_replacements !=
|
||||||
old_settings.texture_replacements.enable_texture_replacements)
|
old_settings.texture_replacements.enable_texture_replacements)
|
||||||
{
|
{
|
||||||
Invalidate();
|
Invalidate();
|
||||||
|
@ -602,9 +610,9 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
|
||||||
// Reload textures if configuration changes.
|
// Reload textures if configuration changes.
|
||||||
const bool old_replacement_scale_linear_filter = s_state.config.replacement_scale_linear_filter;
|
const bool old_replacement_scale_linear_filter = s_state.config.replacement_scale_linear_filter;
|
||||||
if (LoadLocalConfiguration(false, false) ||
|
if (LoadLocalConfiguration(false, false) ||
|
||||||
g_settings.texture_replacements.enable_texture_replacements !=
|
g_gpu_settings.texture_replacements.enable_texture_replacements !=
|
||||||
old_settings.texture_replacements.enable_texture_replacements ||
|
old_settings.texture_replacements.enable_texture_replacements ||
|
||||||
g_settings.texture_replacements.enable_vram_write_replacements !=
|
g_gpu_settings.texture_replacements.enable_vram_write_replacements !=
|
||||||
old_settings.texture_replacements.enable_vram_write_replacements)
|
old_settings.texture_replacements.enable_vram_write_replacements)
|
||||||
{
|
{
|
||||||
if (use_texture_cache)
|
if (use_texture_cache)
|
||||||
|
@ -620,6 +628,37 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GPUTextureCache::GetStateSize(StateWrapper& sw, u32* size)
|
||||||
|
{
|
||||||
|
if (sw.GetVersion() < 73)
|
||||||
|
{
|
||||||
|
*size = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t start = sw.GetPosition();
|
||||||
|
if (!sw.DoMarker("GPUTextureCache")) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
u32 num_vram_writes = 0;
|
||||||
|
sw.Do(&num_vram_writes);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < num_vram_writes; i++)
|
||||||
|
{
|
||||||
|
sw.SkipBytes(sizeof(GSVector4i) * 2 + sizeof(HashType));
|
||||||
|
|
||||||
|
u32 num_palette_records = 0;
|
||||||
|
sw.Do(&num_palette_records);
|
||||||
|
sw.SkipBytes(num_palette_records * STATE_PALETTE_RECORD_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sw.HasError()) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*size = static_cast<u32>(sw.GetPosition() - start);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
|
bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
|
||||||
{
|
{
|
||||||
if (sw.GetVersion() < 73)
|
if (sw.GetVersion() < 73)
|
||||||
|
@ -668,7 +707,7 @@ bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
|
||||||
sw.Do(&num_palette_records);
|
sw.Do(&num_palette_records);
|
||||||
|
|
||||||
// Skip palette records if we're not dumping now.
|
// Skip palette records if we're not dumping now.
|
||||||
if (g_settings.texture_replacements.dump_textures)
|
if (g_gpu_settings.texture_replacements.dump_textures)
|
||||||
{
|
{
|
||||||
vrw->palette_records.reserve(num_palette_records);
|
vrw->palette_records.reserve(num_palette_records);
|
||||||
for (u32 j = 0; j < num_palette_records; j++)
|
for (u32 j = 0; j < num_palette_records; j++)
|
||||||
|
@ -760,6 +799,7 @@ void GPUTextureCache::Shutdown()
|
||||||
s_state.hash_cache_purge_list = {};
|
s_state.hash_cache_purge_list = {};
|
||||||
s_state.temp_vram_write_list = {};
|
s_state.temp_vram_write_list = {};
|
||||||
s_state.track_vram_writes = false;
|
s_state.track_vram_writes = false;
|
||||||
|
s_state.hw_backend = nullptr;
|
||||||
|
|
||||||
for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end();)
|
for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end();)
|
||||||
{
|
{
|
||||||
|
@ -773,7 +813,6 @@ void GPUTextureCache::Shutdown()
|
||||||
s_state.vram_write_texture_replacements.clear();
|
s_state.vram_write_texture_replacements.clear();
|
||||||
s_state.texture_page_texture_replacements.clear();
|
s_state.texture_page_texture_replacements.clear();
|
||||||
s_state.dumped_textures.clear();
|
s_state.dumped_textures.clear();
|
||||||
s_state.game_id = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUTextureCache::SetHashCacheTextureFormat()
|
void GPUTextureCache::SetHashCacheTextureFormat()
|
||||||
|
@ -791,7 +830,7 @@ void GPUTextureCache::SetHashCacheTextureFormat()
|
||||||
|
|
||||||
bool GPUTextureCache::CompilePipelines()
|
bool GPUTextureCache::CompilePipelines()
|
||||||
{
|
{
|
||||||
if (!g_settings.texture_replacements.enable_texture_replacements)
|
if (!g_gpu_settings.texture_replacements.enable_texture_replacements)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
GPUPipeline::GraphicsConfig plconfig = {};
|
GPUPipeline::GraphicsConfig plconfig = {};
|
||||||
|
@ -1390,7 +1429,7 @@ const GPUTextureCache::Source* GPUTextureCache::ReturnSource(Source* source, con
|
||||||
source->from_hash_cache->last_used_frame = System::GetFrameNumber();
|
source->from_hash_cache->last_used_frame = System::GetFrameNumber();
|
||||||
|
|
||||||
// TODO: Cache var.
|
// TODO: Cache var.
|
||||||
if (g_settings.texture_replacements.dump_textures)
|
if (g_gpu_settings.texture_replacements.dump_textures)
|
||||||
{
|
{
|
||||||
source->active_uv_rect = source->active_uv_rect.runion(uv_rect);
|
source->active_uv_rect = source->active_uv_rect.runion(uv_rect);
|
||||||
source->palette_record_flags |= flags;
|
source->palette_record_flags |= flags;
|
||||||
|
@ -1548,7 +1587,7 @@ void GPUTextureCache::DestroySource(Source* src, bool remove_from_hash_cache)
|
||||||
{
|
{
|
||||||
GL_INS_FMT("Invalidate source {}", SourceToString(src));
|
GL_INS_FMT("Invalidate source {}", SourceToString(src));
|
||||||
|
|
||||||
if (g_settings.texture_replacements.dump_textures && !src->active_uv_rect.eq(INVALID_RECT))
|
if (g_gpu_settings.texture_replacements.dump_textures && !src->active_uv_rect.eq(INVALID_RECT))
|
||||||
{
|
{
|
||||||
if (!s_state.config.dump_texture_pages)
|
if (!s_state.config.dump_texture_pages)
|
||||||
{
|
{
|
||||||
|
@ -1950,7 +1989,7 @@ void GPUTextureCache::RemoveVRAMWrite(VRAMWrite* entry)
|
||||||
|
|
||||||
void GPUTextureCache::DumpTexturesFromVRAMWrite(VRAMWrite* entry)
|
void GPUTextureCache::DumpTexturesFromVRAMWrite(VRAMWrite* entry)
|
||||||
{
|
{
|
||||||
if (g_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages)
|
if (g_gpu_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages)
|
||||||
{
|
{
|
||||||
for (const VRAMWrite::PaletteRecord& prec : entry->palette_records)
|
for (const VRAMWrite::PaletteRecord& prec : entry->palette_records)
|
||||||
{
|
{
|
||||||
|
@ -2200,7 +2239,7 @@ GPUTextureCache::HashCacheEntry* GPUTextureCache::LookupHashCache(SourceKey key,
|
||||||
|
|
||||||
DecodeTexture(key.page, key.palette, key.mode, entry.texture.get());
|
DecodeTexture(key.page, key.palette, key.mode, entry.texture.get());
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_texture_replacements)
|
if (g_gpu_settings.texture_replacements.enable_texture_replacements)
|
||||||
ApplyTextureReplacements(key, tex_hash, pal_hash, &entry);
|
ApplyTextureReplacements(key, tex_hash, pal_hash, &entry);
|
||||||
|
|
||||||
s_state.hash_cache_memory_usage += entry.texture->GetVRAMUsage();
|
s_state.hash_cache_memory_usage += entry.texture->GetVRAMUsage();
|
||||||
|
@ -2603,12 +2642,8 @@ size_t GPUTextureCache::DumpedTextureKeyHash::operator()(const DumpedTextureKey&
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUTextureCache::SetGameID(std::string game_id)
|
void GPUTextureCache::GameSerialChanged()
|
||||||
{
|
{
|
||||||
if (s_state.game_id == game_id)
|
|
||||||
return;
|
|
||||||
|
|
||||||
s_state.game_id = game_id;
|
|
||||||
ReloadTextureReplacements(false);
|
ReloadTextureReplacements(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2625,7 +2660,8 @@ GPUTexture* GPUTextureCache::GetVRAMReplacement(u32 width, u32 height, const voi
|
||||||
|
|
||||||
bool GPUTextureCache::ShouldDumpVRAMWrite(u32 width, u32 height)
|
bool GPUTextureCache::ShouldDumpVRAMWrite(u32 width, u32 height)
|
||||||
{
|
{
|
||||||
return (g_settings.texture_replacements.dump_vram_writes && width >= s_state.config.vram_write_dump_width_threshold &&
|
return (g_gpu_settings.texture_replacements.dump_vram_writes &&
|
||||||
|
width >= s_state.config.vram_write_dump_width_threshold &&
|
||||||
height >= s_state.config.vram_write_dump_height_threshold);
|
height >= s_state.config.vram_write_dump_height_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2716,7 +2752,7 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32
|
||||||
};
|
};
|
||||||
|
|
||||||
// skip if dumped already
|
// skip if dumped already
|
||||||
if (!g_settings.texture_replacements.dump_replaced_textures)
|
if (!g_gpu_settings.texture_replacements.dump_replaced_textures)
|
||||||
{
|
{
|
||||||
const TextureReplacementMap& map = (type == TextureReplacementType::TextureFromPage) ?
|
const TextureReplacementMap& map = (type == TextureReplacementType::TextureFromPage) ?
|
||||||
s_state.texture_page_texture_replacements :
|
s_state.texture_page_texture_replacements :
|
||||||
|
@ -2942,7 +2978,7 @@ bool GPUTextureCache::HasValidReplacementExtension(const std::string_view path)
|
||||||
|
|
||||||
void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements, bool load_texture_replacements)
|
void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements, bool load_texture_replacements)
|
||||||
{
|
{
|
||||||
if (s_state.game_id.empty())
|
if (GPUThread::GetGameSerial().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileSystem::FindResultsArray files;
|
FileSystem::FindResultsArray files;
|
||||||
|
@ -3015,23 +3051,23 @@ void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_texture_replacements)
|
if (g_gpu_settings.texture_replacements.enable_texture_replacements)
|
||||||
{
|
{
|
||||||
INFO_LOG("Found {} replacement upload textures for '{}'", s_state.vram_write_texture_replacements.size(),
|
INFO_LOG("Found {} replacement upload textures for '{}'", s_state.vram_write_texture_replacements.size(),
|
||||||
s_state.game_id);
|
GPUThread::GetGameSerial());
|
||||||
INFO_LOG("Found {} replacement page textures for '{}'", s_state.texture_page_texture_replacements.size(),
|
INFO_LOG("Found {} replacement page textures for '{}'", s_state.texture_page_texture_replacements.size(),
|
||||||
s_state.game_id);
|
GPUThread::GetGameSerial());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_vram_write_replacements)
|
if (g_gpu_settings.texture_replacements.enable_vram_write_replacements)
|
||||||
INFO_LOG("Found {} replacement VRAM for '{}'", s_state.vram_replacements.size(), s_state.game_id);
|
INFO_LOG("Found {} replacement VRAM for '{}'", s_state.vram_replacements.size(), GPUThread::GetGameSerial());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& root,
|
void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& root,
|
||||||
bool load_vram_write_replacement_aliases,
|
bool load_vram_write_replacement_aliases,
|
||||||
bool load_texture_replacement_aliases)
|
bool load_texture_replacement_aliases)
|
||||||
{
|
{
|
||||||
if (s_state.game_id.empty())
|
if (GPUThread::GetGameSerial().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::string source_dir = GetTextureReplacementDirectory();
|
const std::string source_dir = GetTextureReplacementDirectory();
|
||||||
|
@ -3107,17 +3143,19 @@ void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_texture_replacements)
|
if (g_gpu_settings.texture_replacements.enable_texture_replacements)
|
||||||
{
|
{
|
||||||
INFO_LOG("Found {} replacement upload textures after applying aliases for '{}'",
|
INFO_LOG("Found {} replacement upload textures after applying aliases for '{}'",
|
||||||
s_state.vram_write_texture_replacements.size(), s_state.game_id);
|
s_state.vram_write_texture_replacements.size(), GPUThread::GetGameSerial());
|
||||||
INFO_LOG("Found {} replacement page textures after applying aliases for '{}'",
|
INFO_LOG("Found {} replacement page textures after applying aliases for '{}'",
|
||||||
s_state.texture_page_texture_replacements.size(), s_state.game_id);
|
s_state.texture_page_texture_replacements.size(), GPUThread::GetGameSerial());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.texture_replacements.enable_vram_write_replacements)
|
if (g_gpu_settings.texture_replacements.enable_vram_write_replacements)
|
||||||
|
{
|
||||||
INFO_LOG("Found {} replacement VRAM after applying aliases for '{}'", s_state.vram_replacements.size(),
|
INFO_LOG("Found {} replacement VRAM after applying aliases for '{}'", s_state.vram_replacements.size(),
|
||||||
s_state.game_id);
|
GPUThread::GetGameSerial());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path)
|
const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path)
|
||||||
|
@ -3241,8 +3279,8 @@ void GPUTextureCache::PreloadReplacementTextures()
|
||||||
#define UPDATE_PROGRESS() \
|
#define UPDATE_PROGRESS() \
|
||||||
if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \
|
if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \
|
||||||
{ \
|
{ \
|
||||||
Host::DisplayLoadingScreen("Preloading replacement textures...", 0, static_cast<int>(total_textures), \
|
ImGuiFullscreen::RenderLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, "Preloading replacement textures...", 0, \
|
||||||
static_cast<int>(num_textures_loaded)); \
|
static_cast<int>(total_textures), static_cast<int>(num_textures_loaded)); \
|
||||||
last_update_time.Reset(); \
|
last_update_time.Reset(); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3269,10 +3307,10 @@ void GPUTextureCache::PreloadReplacementTextures()
|
||||||
|
|
||||||
bool GPUTextureCache::EnsureGameDirectoryExists()
|
bool GPUTextureCache::EnsureGameDirectoryExists()
|
||||||
{
|
{
|
||||||
if (s_state.game_id.empty())
|
if (GPUThread::GetGameSerial().empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const std::string game_directory = Path::Combine(EmuFolders::Textures, s_state.game_id);
|
const std::string game_directory = Path::Combine(EmuFolders::Textures, GPUThread::GetGameSerial());
|
||||||
if (FileSystem::DirectoryExists(game_directory.c_str()))
|
if (FileSystem::DirectoryExists(game_directory.c_str()))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -3309,12 +3347,13 @@ bool GPUTextureCache::EnsureGameDirectoryExists()
|
||||||
|
|
||||||
std::string GPUTextureCache::GetTextureReplacementDirectory()
|
std::string GPUTextureCache::GetTextureReplacementDirectory()
|
||||||
{
|
{
|
||||||
std::string dir = Path::Combine(
|
std::string dir =
|
||||||
EmuFolders::Textures, SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", s_state.game_id));
|
Path::Combine(EmuFolders::Textures,
|
||||||
|
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", GPUThread::GetGameSerial()));
|
||||||
if (!FileSystem::DirectoryExists(dir.c_str()))
|
if (!FileSystem::DirectoryExists(dir.c_str()))
|
||||||
{
|
{
|
||||||
// Check for the old directory structure without a replacements subdirectory.
|
// Check for the old directory structure without a replacements subdirectory.
|
||||||
std::string altdir = Path::Combine(EmuFolders::Textures, s_state.game_id);
|
std::string altdir = Path::Combine(EmuFolders::Textures, GPUThread::GetGameSerial());
|
||||||
if (FileSystem::DirectoryExists(altdir.c_str()))
|
if (FileSystem::DirectoryExists(altdir.c_str()))
|
||||||
WARNING_LOG("Using deprecated texture replacement directory {}", altdir);
|
WARNING_LOG("Using deprecated texture replacement directory {}", altdir);
|
||||||
dir = std::move(altdir);
|
dir = std::move(altdir);
|
||||||
|
@ -3326,7 +3365,7 @@ std::string GPUTextureCache::GetTextureReplacementDirectory()
|
||||||
std::string GPUTextureCache::GetTextureDumpDirectory()
|
std::string GPUTextureCache::GetTextureDumpDirectory()
|
||||||
{
|
{
|
||||||
return Path::Combine(EmuFolders::Textures,
|
return Path::Combine(EmuFolders::Textures,
|
||||||
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "dumps", s_state.game_id));
|
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "dumps", GPUThread::GetGameSerial()));
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUTextureCache::VRAMReplacementName GPUTextureCache::GetVRAMWriteHash(u32 width, u32 height, const void* pixels)
|
GPUTextureCache::VRAMReplacementName GPUTextureCache::GetVRAMWriteHash(u32 width, u32 height, const void* pixels)
|
||||||
|
@ -3354,14 +3393,15 @@ bool GPUTextureCache::LoadLocalConfiguration(bool load_vram_write_replacement_al
|
||||||
const Settings::TextureReplacementSettings::Configuration old_config = s_state.config;
|
const Settings::TextureReplacementSettings::Configuration old_config = s_state.config;
|
||||||
|
|
||||||
// load settings from ini
|
// load settings from ini
|
||||||
s_state.config = g_settings.texture_replacements.config;
|
s_state.config = g_gpu_settings.texture_replacements.config;
|
||||||
|
|
||||||
if (s_state.game_id.empty())
|
const std::string& game_serial = GPUThread::GetGameSerial();
|
||||||
|
if (game_serial.empty())
|
||||||
return (s_state.config != old_config);
|
return (s_state.config != old_config);
|
||||||
|
|
||||||
const std::optional<std::string> ini_data = FileSystem::ReadFileToString(
|
const std::optional<std::string> ini_data = FileSystem::ReadFileToString(
|
||||||
Path::Combine(EmuFolders::Textures,
|
Path::Combine(EmuFolders::Textures,
|
||||||
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "{}", s_state.game_id, LOCAL_CONFIG_FILENAME))
|
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "{}", game_serial, LOCAL_CONFIG_FILENAME))
|
||||||
.c_str());
|
.c_str());
|
||||||
if (!ini_data.has_value() || ini_data->empty())
|
if (!ini_data.has_value() || ini_data->empty())
|
||||||
return (s_state.config != old_config);
|
return (s_state.config != old_config);
|
||||||
|
@ -3430,15 +3470,15 @@ void GPUTextureCache::ReloadTextureReplacements(bool show_info)
|
||||||
s_state.vram_write_texture_replacements.clear();
|
s_state.vram_write_texture_replacements.clear();
|
||||||
s_state.texture_page_texture_replacements.clear();
|
s_state.texture_page_texture_replacements.clear();
|
||||||
|
|
||||||
const bool load_vram_write_replacements = (g_settings.texture_replacements.enable_vram_write_replacements);
|
const bool load_vram_write_replacements = (g_gpu_settings.texture_replacements.enable_vram_write_replacements);
|
||||||
const bool load_texture_replacements =
|
const bool load_texture_replacements =
|
||||||
(g_settings.gpu_texture_cache && g_settings.texture_replacements.enable_texture_replacements);
|
(g_gpu_settings.gpu_texture_cache && g_gpu_settings.texture_replacements.enable_texture_replacements);
|
||||||
if (load_vram_write_replacements || load_texture_replacements)
|
if (load_vram_write_replacements || load_texture_replacements)
|
||||||
FindTextureReplacements(load_vram_write_replacements, load_texture_replacements);
|
FindTextureReplacements(load_vram_write_replacements, load_texture_replacements);
|
||||||
|
|
||||||
LoadLocalConfiguration(load_vram_write_replacements, load_texture_replacements);
|
LoadLocalConfiguration(load_vram_write_replacements, load_texture_replacements);
|
||||||
|
|
||||||
if (g_settings.texture_replacements.preload_textures)
|
if (g_gpu_settings.texture_replacements.preload_textures)
|
||||||
PreloadReplacementTextures();
|
PreloadReplacementTextures();
|
||||||
|
|
||||||
PurgeUnreferencedTexturesFromCache();
|
PurgeUnreferencedTexturesFromCache();
|
||||||
|
@ -3596,5 +3636,5 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
|
||||||
g_gpu_device->RecycleTexture(std::move(entry->texture));
|
g_gpu_device->RecycleTexture(std::move(entry->texture));
|
||||||
entry->texture = std::move(replacement_tex);
|
entry->texture = std::move(replacement_tex);
|
||||||
|
|
||||||
g_gpu->RestoreDeviceContext();
|
s_state.hw_backend->RestoreDeviceContext();
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ class GPUTexture;
|
||||||
class StateWrapper;
|
class StateWrapper;
|
||||||
|
|
||||||
struct Settings;
|
struct Settings;
|
||||||
|
class GPU_HW;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Texture Cache
|
// Texture Cache
|
||||||
|
@ -102,9 +103,12 @@ struct Source
|
||||||
TListNode<Source> hash_cache_ref;
|
TListNode<Source> hash_cache_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize(GPU_HW* backend);
|
||||||
void UpdateSettings(bool use_texture_cache, const Settings& old_settings);
|
void UpdateSettings(bool use_texture_cache, const Settings& old_settings);
|
||||||
|
|
||||||
|
bool GetStateSize(StateWrapper& sw, u32* size);
|
||||||
bool DoState(StateWrapper& sw, bool skip);
|
bool DoState(StateWrapper& sw, bool skip);
|
||||||
|
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void Invalidate();
|
void Invalidate();
|
||||||
|
@ -124,7 +128,7 @@ bool AreSourcePagesDrawn(SourceKey key, const GSVector4i rect);
|
||||||
|
|
||||||
void Compact();
|
void Compact();
|
||||||
|
|
||||||
void SetGameID(std::string game_id);
|
void GameSerialChanged();
|
||||||
void ReloadTextureReplacements(bool show_info);
|
void ReloadTextureReplacements(bool show_info);
|
||||||
|
|
||||||
// VRAM Write Replacements
|
// VRAM Write Replacements
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
#include "gpu_sw.h"
|
#include "gpu_sw.h"
|
||||||
#include "gpu_hw_texture_cache.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_sw_rasterizer.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "system.h"
|
#include "system_private.h"
|
||||||
|
|
||||||
#include "util/gpu_device.h"
|
#include "util/gpu_device.h"
|
||||||
#include "util/state_wrapper.h"
|
#include "util/state_wrapper.h"
|
||||||
|
|
||||||
#include "common/align.h"
|
#include "common/align.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/gsvector.h"
|
#include "common/intrin.h"
|
||||||
#include "common/gsvector_formatter.h"
|
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -21,25 +21,16 @@ LOG_CHANNEL(GPU);
|
||||||
|
|
||||||
GPU_SW::GPU_SW() = default;
|
GPU_SW::GPU_SW() = default;
|
||||||
|
|
||||||
GPU_SW::~GPU_SW()
|
GPU_SW::~GPU_SW() = default;
|
||||||
|
|
||||||
|
u32 GPU_SW::GetResolutionScale() const
|
||||||
{
|
{
|
||||||
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
|
return 1u;
|
||||||
m_backend.Shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Threading::Thread* GPU_SW::GetSWThread() const
|
bool GPU_SW::Initialize(bool upload_vram, Error* error)
|
||||||
{
|
{
|
||||||
return m_backend.GetThread();
|
if (!GPUBackend::Initialize(upload_vram, error))
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU_SW::IsHardwareRenderer() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU_SW::Initialize(Error* error)
|
|
||||||
{
|
|
||||||
if (!GPU::Initialize(error) || !m_backend.Initialize(g_settings.gpu_use_thread))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB5A1, GPUTexture::Format::A1BGR5,
|
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB5A1, GPUTexture::Format::A1BGR5,
|
||||||
|
@ -56,41 +47,133 @@ bool GPU_SW::Initialize(Error* error)
|
||||||
// RGBA8 will always be supported, hence we'll find one.
|
// RGBA8 will always be supported, hence we'll find one.
|
||||||
INFO_LOG("Using {} format for 16-bit display", GPUTexture::GetFormatName(m_16bit_display_format));
|
INFO_LOG("Using {} format for 16-bit display", GPUTexture::GetFormatName(m_16bit_display_format));
|
||||||
Assert(m_16bit_display_format != GPUTexture::Format::Unknown);
|
Assert(m_16bit_display_format != GPUTexture::Format::Unknown);
|
||||||
|
|
||||||
|
// if we're using "new" vram, clear it out here
|
||||||
|
if (!upload_vram)
|
||||||
|
std::memset(g_vram, 0, sizeof(g_vram));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU_SW::DoState(StateWrapper& sw, bool update_display)
|
void GPU_SW::ClearVRAM()
|
||||||
{
|
{
|
||||||
// need to ensure the worker thread is done
|
std::memset(g_vram, 0, sizeof(g_vram));
|
||||||
m_backend.Sync(true);
|
std::memset(g_gpu_clut, 0, sizeof(g_gpu_clut));
|
||||||
|
|
||||||
// ignore the host texture for software mode, since we want to save vram here
|
|
||||||
if (!GPU::DoState(sw, update_display))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// need to still call the TC, to toss any data in the state
|
|
||||||
return GPUTextureCache::DoState(sw, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU_SW::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display)
|
void GPU_SW::UpdateResolutionScale()
|
||||||
{
|
{
|
||||||
m_backend.Sync(true);
|
|
||||||
sw.DoBytes(g_vram, VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16));
|
|
||||||
return GPU::DoMemoryState(sw, mss, update_display);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU_SW::Reset(bool clear_vram)
|
void GPU_SW::LoadState(const GPUBackendLoadStateCommand* cmd)
|
||||||
{
|
{
|
||||||
GPU::Reset(clear_vram);
|
std::memcpy(g_vram, cmd->vram_data, sizeof(g_vram));
|
||||||
|
std::memcpy(g_gpu_clut, cmd->clut_data, sizeof(g_gpu_clut));
|
||||||
m_backend.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU_SW::UpdateSettings(const Settings& old_settings)
|
bool GPU_SW::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error)
|
||||||
|
{
|
||||||
|
mss.gpu_state_data.resize(sizeof(g_vram) + sizeof(g_gpu_clut));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU_SW::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss)
|
||||||
|
{
|
||||||
|
sw.DoBytes(g_vram, sizeof(g_vram));
|
||||||
|
sw.DoBytes(g_gpu_clut, sizeof(g_gpu_clut));
|
||||||
|
DebugAssert(!sw.HasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bool interlaced_rendering, u8 active_line_lsb)
|
||||||
|
{
|
||||||
|
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, interlaced_rendering, active_line_lsb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU_SW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask)
|
||||||
|
{
|
||||||
|
GPU_SW_Rasterizer::WriteVRAM(x, y, width, height, data, set_mask, check_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, bool check_mask)
|
||||||
|
{
|
||||||
|
GPU_SW_Rasterizer::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, set_mask, check_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
|
||||||
|
{
|
||||||
|
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
|
||||||
|
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->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 GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
|
||||||
|
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->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 GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
|
||||||
|
GPU_SW_Rasterizer::GetDrawRectangleFunction(cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
|
||||||
|
|
||||||
|
DrawFunction(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU_SW::DrawLine(const GPUBackendDrawLineCommand* cmd)
|
||||||
|
{
|
||||||
|
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
|
||||||
|
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->shading_enable, cmd->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()
|
||||||
{
|
{
|
||||||
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)
|
GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format)
|
||||||
|
@ -271,32 +354,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 (!g_settings.debugging.show_vram)
|
||||||
{
|
{
|
||||||
if (IsDisplayDisabled())
|
if (cmd->display_disabled)
|
||||||
{
|
{
|
||||||
ClearDisplayTexture();
|
ClearDisplayTexture();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool is_24bit = m_GPUSTAT.display_area_color_depth_24;
|
const bool is_24bit = cmd->display_24bit;
|
||||||
const bool interlaced = IsInterlacedDisplayEnabled();
|
const bool interlaced = cmd->interlaced_display_enabled;
|
||||||
const u32 field = GetInterlacedDisplayField();
|
const u32 field = BoolToUInt32(cmd->interlaced_display_field);
|
||||||
const u32 vram_offset_x = is_24bit ? m_crtc_state.regs.X : m_crtc_state.display_vram_left;
|
const u32 vram_offset_x = is_24bit ? cmd->X : cmd->display_vram_left;
|
||||||
const u32 vram_offset_y =
|
const u32 vram_offset_y = cmd->display_vram_top + ((interlaced && cmd->interlaced_display_interleaved) ? field : 0);
|
||||||
m_crtc_state.display_vram_top + ((interlaced && m_GPUSTAT.vertical_resolution) ? field : 0);
|
const u32 skip_x = is_24bit ? (cmd->display_vram_left - cmd->X) : 0;
|
||||||
const u32 skip_x = is_24bit ? (m_crtc_state.display_vram_left - m_crtc_state.regs.X) : 0;
|
const u32 read_width = cmd->display_vram_width;
|
||||||
const u32 read_width = m_crtc_state.display_vram_width;
|
const u32 read_height = interlaced ? (cmd->display_vram_height / 2) : cmd->display_vram_height;
|
||||||
const u32 read_height = interlaced ? (m_crtc_state.display_vram_height / 2) : m_crtc_state.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))
|
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);
|
SetDisplayTexture(m_upload_texture.get(), nullptr, 0, 0, read_width, read_height);
|
||||||
|
@ -328,347 +407,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<false>(&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<false>(&cmd->vertices[0].x);
|
|
||||||
const GSVector4i v1 = GSVector4i::loadl<false>(&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<false>(&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<false>(&out_vertex[0].x, start_pos);
|
|
||||||
out_vertex[0].color = start_color;
|
|
||||||
GSVector2i::store<false>(&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()
|
|
||||||
{
|
{
|
||||||
return std::make_unique<GPU_SW>();
|
return std::make_unique<GPU_SW>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
#include "gpu_sw_backend.h"
|
#include "gpu_backend.h"
|
||||||
|
|
||||||
#include "util/gpu_device.h"
|
#include "util/gpu_device.h"
|
||||||
|
|
||||||
|
@ -12,36 +12,51 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace Threading {
|
// TODO: Move to cpp
|
||||||
class Thread;
|
// TODO: Rename to GPUSWBackend, preserved to avoid conflicts.
|
||||||
}
|
class GPU_SW final : public GPUBackend
|
||||||
|
|
||||||
class GPUTexture;
|
|
||||||
|
|
||||||
class GPU_SW final : public GPU
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GPU_SW();
|
GPU_SW();
|
||||||
~GPU_SW() override;
|
~GPU_SW() override;
|
||||||
|
|
||||||
ALWAYS_INLINE const GPU_SW_Backend& GetBackend() const { return m_backend; }
|
bool Initialize(bool upload_vram, Error* error) override;
|
||||||
|
|
||||||
const Threading::Thread* GetSWThread() const override;
|
void RestoreDeviceContext() override;
|
||||||
bool IsHardwareRenderer() const override;
|
|
||||||
|
|
||||||
bool Initialize(Error* error) override;
|
u32 GetResolutionScale() const override;
|
||||||
bool DoState(StateWrapper& sw, bool update_display) override;
|
|
||||||
bool DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display) override;
|
|
||||||
void Reset(bool clear_vram) override;
|
|
||||||
void UpdateSettings(const Settings& old_settings) override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
|
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 FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) override;
|
||||||
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) 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 CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
|
||||||
void FlushRender() override;
|
bool check_mask) 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 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;
|
||||||
|
|
||||||
|
bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override;
|
||||||
|
void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr GPUTexture::Format FORMAT_FOR_24BIT = GPUTexture::Format::RGBA8; // RGBA8 always supported.
|
||||||
|
|
||||||
template<GPUTexture::Format display_format>
|
template<GPUTexture::Format display_format>
|
||||||
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
|
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
|
||||||
|
@ -50,21 +65,9 @@ protected:
|
||||||
|
|
||||||
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
|
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;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr GPUTexture::Format FORMAT_FOR_24BIT = GPUTexture::Format::RGBA8; // RGBA8 always supported.
|
|
||||||
|
|
||||||
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
|
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
|
||||||
|
|
||||||
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
|
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
|
||||||
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::Unknown;
|
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::Unknown;
|
||||||
std::unique_ptr<GPUTexture> m_upload_texture;
|
std::unique_ptr<GPUTexture> m_upload_texture;
|
||||||
|
|
||||||
GPU_SW_Backend m_backend;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 += 2)
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -38,6 +38,31 @@ CopyVRAMFunction CopyVRAM = nullptr;
|
||||||
GPUDrawingArea g_drawing_area = {};
|
GPUDrawingArea g_drawing_area = {};
|
||||||
} // namespace GPU_SW_Rasterizer
|
} // 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.
|
// Default scalar implementation definitions.
|
||||||
namespace GPU_SW_Rasterizer::Scalar {
|
namespace GPU_SW_Rasterizer::Scalar {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_thread_commands.h"
|
||||||
#include "gpu_types.h"
|
#include "gpu_types.h"
|
||||||
|
|
||||||
#include "common/intrin.h"
|
#include "common/intrin.h"
|
||||||
|
@ -18,13 +19,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>;
|
using DitherLUT = std::array<std::array<std::array<u8, DITHER_LUT_SIZE>, DITHER_MATRIX_SIZE>, DITHER_MATRIX_SIZE>;
|
||||||
extern const DitherLUT g_dither_lut;
|
extern const DitherLUT g_dither_lut;
|
||||||
|
|
||||||
|
// TODO: Pack in struct
|
||||||
extern GPUDrawingArea g_drawing_area;
|
extern GPUDrawingArea g_drawing_area;
|
||||||
|
|
||||||
|
extern void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit);
|
||||||
|
|
||||||
using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd);
|
using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd);
|
||||||
typedef const DrawRectangleFunction DrawRectangleFunctionTable[2][2][2];
|
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* v0,
|
|
||||||
const GPUBackendDrawPolygonCommand::Vertex* v1,
|
const GPUBackendDrawPolygonCommand::Vertex* v1,
|
||||||
const GPUBackendDrawPolygonCommand::Vertex* v2);
|
const GPUBackendDrawPolygonCommand::Vertex* v2);
|
||||||
typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][2][2][2];
|
typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][2][2][2];
|
||||||
|
|
|
@ -129,7 +129,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const bool dithering_enable = cmd->draw_mode.dither_enable;
|
const bool dithering_enable = cmd->dither_enable;
|
||||||
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
|
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
|
||||||
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
|
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const bool dithering_enable = cmd->draw_mode.dither_enable;
|
const bool dithering_enable = cmd->dither_enable;
|
||||||
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
|
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
|
||||||
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
|
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
|
||||||
|
|
||||||
|
@ -215,12 +215,12 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const u16 mask_and = cmd->params.GetMaskAND();
|
const u16 mask_and = cmd->GetMaskAND();
|
||||||
if ((bg_color & mask_and) != 0)
|
if ((bg_color & mask_and) != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DebugAssert(static_cast<u32>(x) < VRAM_WIDTH && static_cast<u32>(y) < VRAM_HEIGHT);
|
DebugAssert(static_cast<u32>(x) < VRAM_WIDTH && static_cast<u32>(y) < VRAM_HEIGHT);
|
||||||
SetPixel(static_cast<u32>(x), static_cast<u32>(y), color | cmd->params.GetMaskOR());
|
SetPixel(static_cast<u32>(x), static_cast<u32>(y), color | cmd->GetMaskOR());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef USE_VECTOR
|
#ifndef USE_VECTOR
|
||||||
|
@ -237,7 +237,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
|
||||||
{
|
{
|
||||||
const s32 y = origin_y + static_cast<s32>(offset_y);
|
const s32 y = origin_y + static_cast<s32>(offset_y);
|
||||||
if (y < static_cast<s32>(g_drawing_area.top) || y > static_cast<s32>(g_drawing_area.bottom) ||
|
if (y < static_cast<s32>(g_drawing_area.top) || y > static_cast<s32>(g_drawing_area.bottom) ||
|
||||||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (Truncate8(static_cast<u32>(y)) & 1u)))
|
(cmd->interlaced_rendering &&
|
||||||
|
cmd->active_line_lsb == ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -488,8 +489,8 @@ struct PixelVectors
|
||||||
clip_left = GSVectorNi(g_drawing_area.left);
|
clip_left = GSVectorNi(g_drawing_area.left);
|
||||||
clip_right = GSVectorNi(g_drawing_area.right);
|
clip_right = GSVectorNi(g_drawing_area.right);
|
||||||
|
|
||||||
mask_and = GSVectorNi(cmd->params.GetMaskAND());
|
mask_and = GSVectorNi(cmd->GetMaskAND());
|
||||||
mask_or = GSVectorNi(cmd->params.GetMaskOR());
|
mask_or = GSVectorNi(cmd->GetMaskOR());
|
||||||
|
|
||||||
if constexpr (texture_enable)
|
if constexpr (texture_enable)
|
||||||
{
|
{
|
||||||
|
@ -717,7 +718,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
|
||||||
{
|
{
|
||||||
const s32 y = origin_y + static_cast<s32>(offset_y);
|
const s32 y = origin_y + static_cast<s32>(offset_y);
|
||||||
if (y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom) &&
|
if (y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom) &&
|
||||||
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (Truncate8(static_cast<u32>(y)) & 1u)))
|
(!cmd->interlaced_rendering ||
|
||||||
|
cmd->active_line_lsb != ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)))
|
||||||
{
|
{
|
||||||
const s32 draw_y = (y & VRAM_HEIGHT_MASK);
|
const s32 draw_y = (y & VRAM_HEIGHT_MASK);
|
||||||
|
|
||||||
|
@ -817,7 +819,8 @@ static void DrawLine(const GPUBackendDrawLineCommand* cmd, const GPUBackendDrawL
|
||||||
const s32 x = unfp_xy(curx);
|
const s32 x = unfp_xy(curx);
|
||||||
const s32 y = unfp_xy(cury);
|
const s32 y = unfp_xy(cury);
|
||||||
|
|
||||||
if ((!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (Truncate8(static_cast<u32>(y)) & 1u)) &&
|
if ((!cmd->interlaced_rendering ||
|
||||||
|
cmd->active_line_lsb != ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)) &&
|
||||||
x >= static_cast<s32>(g_drawing_area.left) && x <= static_cast<s32>(g_drawing_area.right) &&
|
x >= static_cast<s32>(g_drawing_area.left) && x <= static_cast<s32>(g_drawing_area.right) &&
|
||||||
y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom))
|
y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom))
|
||||||
{
|
{
|
||||||
|
@ -968,7 +971,7 @@ struct TrianglePart
|
||||||
#ifndef USE_VECTOR
|
#ifndef USE_VECTOR
|
||||||
|
|
||||||
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
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 UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep)
|
||||||
{
|
{
|
||||||
s32 width = x_bound - x_start;
|
s32 width = x_bound - x_start;
|
||||||
|
@ -1008,7 +1011,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>
|
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 UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb,
|
||||||
const RGBSteps& rgbstep)
|
const RGBSteps& rgbstep)
|
||||||
{
|
{
|
||||||
|
@ -1051,7 +1054,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
|
||||||
lrgb.StepY<true>(rgbstep);
|
lrgb.StepY<true>(rgbstep);
|
||||||
|
|
||||||
if (y > static_cast<s32>(g_drawing_area.bottom) ||
|
if (y > static_cast<s32>(g_drawing_area.bottom) ||
|
||||||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (static_cast<u32>(current_y) & 1u)))
|
(cmd->interlaced_rendering &&
|
||||||
|
cmd->active_line_lsb == ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1082,7 +1086,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (y >= static_cast<s32>(g_drawing_area.top) &&
|
if (y >= static_cast<s32>(g_drawing_area.top) &&
|
||||||
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (static_cast<u32>(current_y) & 1u)))
|
(!cmd->interlaced_rendering ||
|
||||||
|
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
|
||||||
{
|
{
|
||||||
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
|
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
|
||||||
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
|
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
|
||||||
|
@ -1145,7 +1150,7 @@ struct TriangleVectors : PixelVectors<texture_enable>
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
||||||
ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start, s32 x_bound,
|
ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* cmd, s32 y, s32 x_start, s32 x_bound,
|
||||||
UVStepper uv, const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep,
|
UVStepper uv, const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep,
|
||||||
const TriangleVectors<shading_enable, texture_enable>& tv)
|
const TriangleVectors<shading_enable, texture_enable>& tv)
|
||||||
{
|
{
|
||||||
|
@ -1195,7 +1200,7 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* c
|
||||||
dv = GSVectorNi::zero();
|
dv = GSVectorNi::zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
const GSVectorNi dither = cmd->draw_mode.dither_enable ?
|
const GSVectorNi dither = cmd->dither_enable ?
|
||||||
GSVectorNi::broadcast128<false>(
|
GSVectorNi::broadcast128<false>(
|
||||||
&VECTOR_DITHER_MATRIX[static_cast<u32>(y) & 3][(static_cast<u32>(current_x) & 3) * 2]) :
|
&VECTOR_DITHER_MATRIX[static_cast<u32>(y) & 3][(static_cast<u32>(current_x) & 3) * 2]) :
|
||||||
GSVectorNi::zero();
|
GSVectorNi::zero();
|
||||||
|
@ -1250,7 +1255,7 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* c
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
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 UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb,
|
||||||
const RGBSteps& rgbstep)
|
const RGBSteps& rgbstep)
|
||||||
{
|
{
|
||||||
|
@ -1295,7 +1300,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
|
||||||
lrgb.StepY<true>(rgbstep);
|
lrgb.StepY<true>(rgbstep);
|
||||||
|
|
||||||
if (y > static_cast<s32>(g_drawing_area.bottom) ||
|
if (y > static_cast<s32>(g_drawing_area.bottom) ||
|
||||||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (static_cast<u32>(current_y) & 1u)))
|
(cmd->interlaced_rendering &&
|
||||||
|
cmd->active_line_lsb == ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1328,7 +1334,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (y >= static_cast<s32>(g_drawing_area.top) &&
|
if (y >= static_cast<s32>(g_drawing_area.top) &&
|
||||||
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (static_cast<u32>(current_y) & 1u)))
|
(!cmd->interlaced_rendering ||
|
||||||
|
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
|
||||||
{
|
{
|
||||||
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
|
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
|
||||||
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
|
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
|
||||||
|
@ -1349,7 +1356,7 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
|
||||||
#endif // USE_VECTOR
|
#endif // USE_VECTOR
|
||||||
|
|
||||||
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
|
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)
|
const GPUBackendDrawPolygonCommand::Vertex* v1, const GPUBackendDrawPolygonCommand::Vertex* v2)
|
||||||
{
|
{
|
||||||
#ifdef CHECK_VECTOR
|
#ifdef CHECK_VECTOR
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,106 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
struct GPUThreadCommand;
|
||||||
|
struct GPUBackendUpdateDisplayCommand;
|
||||||
|
|
||||||
|
namespace GPUThread {
|
||||||
|
using AsyncCallType = std::function<void()>;
|
||||||
|
using AsyncBackendCallType = std::function<void(GPUBackend*)>;
|
||||||
|
|
||||||
|
enum class RunIdleReason : u8
|
||||||
|
{
|
||||||
|
NoGPUBackend = (1 << 0),
|
||||||
|
SystemPaused = (1 << 1),
|
||||||
|
FullscreenUIActive = (1 << 2),
|
||||||
|
LoadingScreenActive = (1 << 3),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Starts Big Picture UI.
|
||||||
|
bool StartFullscreenUI(bool fullscreen, Error* error);
|
||||||
|
bool IsFullscreenUIRequested();
|
||||||
|
void StopFullscreenUI();
|
||||||
|
|
||||||
|
/// Backend control.
|
||||||
|
std::optional<GPURenderer> GetRequestedRenderer();
|
||||||
|
bool CreateGPUBackend(std::string serial, GPURenderer renderer, bool upload_vram, bool fullscreen,
|
||||||
|
bool force_recreate_device, Error* error);
|
||||||
|
void DestroyGPUBackend();
|
||||||
|
bool HasGPUBackend();
|
||||||
|
bool IsGPUBackendRequested();
|
||||||
|
void SetGameSerial(std::string serial);
|
||||||
|
|
||||||
|
/// 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, bool device_settings_changed);
|
||||||
|
|
||||||
|
bool IsOnThread();
|
||||||
|
bool IsUsingThread();
|
||||||
|
void RunOnThread(AsyncCallType func);
|
||||||
|
void RunOnBackend(AsyncBackendCallType func, bool sync, bool spin_or_wake);
|
||||||
|
void SetVSync(GPUVSyncMode mode, bool allow_present_throttle);
|
||||||
|
|
||||||
|
// Should only be called on the GPU thread.
|
||||||
|
bool GetRunIdleReason(RunIdleReason reason);
|
||||||
|
void SetRunIdleReason(RunIdleReason reason, bool enabled);
|
||||||
|
bool IsRunningIdle();
|
||||||
|
bool IsSystemPaused();
|
||||||
|
const std::string& GetGameSerial();
|
||||||
|
|
||||||
|
GPUThreadCommand* AllocateCommand(GPUBackendCommandType command, u32 size);
|
||||||
|
void PushCommand(GPUThreadCommand* cmd);
|
||||||
|
void PushCommandAndWakeThread(GPUThreadCommand* cmd);
|
||||||
|
void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
|
||||||
|
void SyncGPUThread(bool spin);
|
||||||
|
|
||||||
|
// NOTE: Only called by GPUBackend
|
||||||
|
namespace Internal {
|
||||||
|
const Threading::ThreadHandle& GetThreadHandle();
|
||||||
|
void ProcessStartup();
|
||||||
|
void SetThreadEnabled(bool enabled);
|
||||||
|
void DoRunIdle();
|
||||||
|
void RequestShutdown();
|
||||||
|
void GPUThreadEntryPoint();
|
||||||
|
void PresentFrame(bool allow_skip_present, u64 present_time);
|
||||||
|
void RestoreContextAfterPresent();
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace GPUThread
|
||||||
|
|
||||||
|
namespace Host {
|
||||||
|
|
||||||
|
/// Called when the pause state changes, or fullscreen UI opens.
|
||||||
|
void OnGPUThreadRunIdleChanged(bool is_active);
|
||||||
|
|
||||||
|
} // namespace Host
|
|
@ -0,0 +1,324 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "gpu_types.h"
|
||||||
|
|
||||||
|
#include "common/align.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Error;
|
||||||
|
|
||||||
|
enum class GPUVSyncMode : u8;
|
||||||
|
class MediaCapture;
|
||||||
|
class StateWrapper;
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
|
||||||
|
namespace System {
|
||||||
|
struct MemorySaveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 4200) // warning C4200: nonstandard extension used: zero-sized array in struct/union
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum class GPUBackendCommandType : u8
|
||||||
|
{
|
||||||
|
Wraparound,
|
||||||
|
AsyncCall,
|
||||||
|
AsyncBackendCall,
|
||||||
|
Reconfigure,
|
||||||
|
Shutdown,
|
||||||
|
ClearVRAM,
|
||||||
|
ClearDisplay,
|
||||||
|
UpdateDisplay,
|
||||||
|
SubmitFrame,
|
||||||
|
BufferSwapped,
|
||||||
|
LoadState,
|
||||||
|
LoadMemoryState,
|
||||||
|
SaveMemoryState,
|
||||||
|
ReadVRAM,
|
||||||
|
FillVRAM,
|
||||||
|
UpdateVRAM,
|
||||||
|
CopyVRAM,
|
||||||
|
SetDrawingArea,
|
||||||
|
UpdateCLUT,
|
||||||
|
ClearCache,
|
||||||
|
DrawPolygon,
|
||||||
|
DrawPrecisePolygon,
|
||||||
|
DrawRectangle,
|
||||||
|
DrawLine,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUThreadCommand
|
||||||
|
{
|
||||||
|
u32 size;
|
||||||
|
GPUBackendCommandType type;
|
||||||
|
|
||||||
|
static constexpr u32 AlignCommandSize(u32 size)
|
||||||
|
{
|
||||||
|
// Ensure size is a multiple of 8 (minimum data size) so we don't end up with an unaligned command.
|
||||||
|
// NOTE: If we ever end up putting vectors in the command packets, this should be raised.
|
||||||
|
constexpr u32 COMMAND_QUEUE_ALLOCATION_ALIGNMENT = 8;
|
||||||
|
return Common::AlignUpPow2(size, COMMAND_QUEUE_ALLOCATION_ALIGNMENT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUThreadReconfigureCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
Error* error_ptr;
|
||||||
|
bool* out_result;
|
||||||
|
std::string game_serial;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUThreadAsyncCallCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
GPUThreadAsyncCallCommand(std::function<void()> func_) : func(std::move(func_)) {}
|
||||||
|
|
||||||
|
std::function<void()> func;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUThreadAsyncBackendCallCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
GPUThreadAsyncBackendCallCommand(std::function<void(GPUBackend*)> func_) : func(std::move(func_)) {}
|
||||||
|
|
||||||
|
std::function<void(GPUBackend*)> func;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendLoadStateCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
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 GPUBackendDoMemoryStateCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
System::MemorySaveState* memory_save_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendFramePresentationParameters
|
||||||
|
{
|
||||||
|
u32 frame_number;
|
||||||
|
u32 internal_frame_number;
|
||||||
|
|
||||||
|
u64 present_time;
|
||||||
|
MediaCapture* media_capture;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
u8 bits;
|
||||||
|
|
||||||
|
BitField<u16, bool, 0, 1> allow_present_skip;
|
||||||
|
BitField<u16, bool, 1, 1> present_frame;
|
||||||
|
BitField<u16, bool, 2, 1> update_performance_counters;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendUpdateDisplayCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
float display_pixel_aspect_ratio;
|
||||||
|
|
||||||
|
u16 X; // TODO: Can we get rid of this?
|
||||||
|
|
||||||
|
bool interlaced_display_enabled : 1;
|
||||||
|
bool interlaced_display_field : 1;
|
||||||
|
bool interlaced_display_interleaved : 1;
|
||||||
|
bool display_24bit : 1;
|
||||||
|
bool display_disabled : 1;
|
||||||
|
bool submit_frame : 1;
|
||||||
|
bool : 2;
|
||||||
|
|
||||||
|
GPUBackendFramePresentationParameters frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only used for runahead.
|
||||||
|
struct GPUBackendSubmitFrameCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
GPUBackendFramePresentationParameters frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendReadVRAMCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
u16 x;
|
||||||
|
u16 y;
|
||||||
|
u16 width;
|
||||||
|
u16 height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendFillVRAMCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
u16 x;
|
||||||
|
u16 y;
|
||||||
|
u16 width;
|
||||||
|
u16 height;
|
||||||
|
u32 color;
|
||||||
|
bool interlaced_rendering;
|
||||||
|
u8 active_line_lsb;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendUpdateVRAMCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
u16 x;
|
||||||
|
u16 y;
|
||||||
|
u16 width;
|
||||||
|
u16 height;
|
||||||
|
bool set_mask_while_drawing;
|
||||||
|
bool check_mask_before_draw;
|
||||||
|
u16 data[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendCopyVRAMCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
u16 src_x;
|
||||||
|
u16 src_y;
|
||||||
|
u16 dst_x;
|
||||||
|
u16 dst_y;
|
||||||
|
u16 width;
|
||||||
|
u16 height;
|
||||||
|
bool set_mask_while_drawing;
|
||||||
|
bool check_mask_before_draw;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendSetDrawingAreaCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
GPUDrawingArea new_area;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendUpdateCLUTCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
GPUTexturePaletteReg reg;
|
||||||
|
bool clut_is_8bit;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendDrawCommand : public GPUThreadCommand
|
||||||
|
{
|
||||||
|
bool interlaced_rendering : 1;
|
||||||
|
|
||||||
|
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
|
||||||
|
bool active_line_lsb : 1;
|
||||||
|
|
||||||
|
bool set_mask_while_drawing : 1;
|
||||||
|
bool check_mask_before_draw : 1;
|
||||||
|
|
||||||
|
bool texture_enable : 1;
|
||||||
|
bool raw_texture_enable : 1;
|
||||||
|
bool transparency_enable : 1;
|
||||||
|
bool shading_enable : 1;
|
||||||
|
bool quad_polygon : 1;
|
||||||
|
bool dither_enable : 1;
|
||||||
|
|
||||||
|
bool valid_w : 1; // only used for precise polygons
|
||||||
|
|
||||||
|
// During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or }
|
||||||
|
ALWAYS_INLINE u16 GetMaskAND() const { return check_mask_before_draw ? 0x8000 : 0x0000; }
|
||||||
|
ALWAYS_INLINE u16 GetMaskOR() const { return set_mask_while_drawing ? 0x8000 : 0x0000; }
|
||||||
|
|
||||||
|
u16 num_vertices;
|
||||||
|
GPUDrawModeReg draw_mode;
|
||||||
|
GPUTexturePaletteReg palette;
|
||||||
|
GPUTextureWindow window;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
s32 x, y;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u8 r, g, b, a;
|
||||||
|
};
|
||||||
|
u32 color;
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u8 u, v;
|
||||||
|
};
|
||||||
|
u16 texcoord;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Vertex vertices[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendDrawPrecisePolygonCommand : public GPUBackendDrawCommand
|
||||||
|
{
|
||||||
|
GPUBackendDrawCommand params;
|
||||||
|
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
float x, y, w;
|
||||||
|
s32 native_x, native_y;
|
||||||
|
u32 color;
|
||||||
|
u16 texcoord;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vertex vertices[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
|
||||||
|
{
|
||||||
|
u16 width, height;
|
||||||
|
u16 texcoord;
|
||||||
|
s32 x, y;
|
||||||
|
u32 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUBackendDrawLineCommand : public GPUBackendDrawCommand
|
||||||
|
{
|
||||||
|
u16 num_vertices;
|
||||||
|
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
s32 x, y;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u8 r, g, b, a;
|
||||||
|
};
|
||||||
|
u32 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_)
|
||||||
|
{
|
||||||
|
x = x_;
|
||||||
|
y = y_;
|
||||||
|
color = color_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vertex vertices[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
|
@ -12,7 +12,6 @@
|
||||||
#include "common/gsvector.h"
|
#include "common/gsvector.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
|
@ -405,12 +404,17 @@ union GPUTexturePaletteReg
|
||||||
ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); }
|
ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GPUTextureWindow
|
union GPUTextureWindow
|
||||||
{
|
{
|
||||||
u8 and_x;
|
struct
|
||||||
u8 and_y;
|
{
|
||||||
u8 or_x;
|
u8 and_x;
|
||||||
u8 or_y;
|
u8 and_y;
|
||||||
|
u8 or_x;
|
||||||
|
u8 or_y;
|
||||||
|
};
|
||||||
|
|
||||||
|
u32 bits;
|
||||||
|
|
||||||
ALWAYS_INLINE bool operator==(const GPUTextureWindow& rhs) const
|
ALWAYS_INLINE bool operator==(const GPUTextureWindow& rhs) const
|
||||||
{
|
{
|
||||||
|
@ -541,182 +545,3 @@ static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-
|
||||||
{+2, -2, +3, -1}, // row 1
|
{+2, -2, +3, -1}, // row 1
|
||||||
{-3, +1, -4, +0}, // row 2
|
{-3, +1, -4, +0}, // row 2
|
||||||
{+3, -1, +2, -2}}; // row 3
|
{+3, -1, +2, -2}}; // row 3
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#pragma warning(push)
|
|
||||||
#pragma warning(disable : 4200) // warning C4200: nonstandard extension used: zero-sized array in struct/union
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum class GPUBackendCommandType : u8
|
|
||||||
{
|
|
||||||
Wraparound,
|
|
||||||
Sync,
|
|
||||||
FillVRAM,
|
|
||||||
UpdateVRAM,
|
|
||||||
CopyVRAM,
|
|
||||||
SetDrawingArea,
|
|
||||||
UpdateCLUT,
|
|
||||||
DrawPolygon,
|
|
||||||
DrawRectangle,
|
|
||||||
DrawLine,
|
|
||||||
};
|
|
||||||
|
|
||||||
union GPUBackendCommandParameters
|
|
||||||
{
|
|
||||||
u8 bits;
|
|
||||||
|
|
||||||
BitField<u8, bool, 0, 1> interlaced_rendering;
|
|
||||||
|
|
||||||
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
|
|
||||||
BitField<u8, u8, 1, 1> active_line_lsb;
|
|
||||||
|
|
||||||
BitField<u8, bool, 2, 1> set_mask_while_drawing;
|
|
||||||
BitField<u8, bool, 3, 1> check_mask_before_draw;
|
|
||||||
|
|
||||||
// During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or }
|
|
||||||
u16 GetMaskAND() const
|
|
||||||
{
|
|
||||||
// return check_mask_before_draw ? 0x8000 : 0x0000;
|
|
||||||
return Truncate16((bits << 12) & 0x8000);
|
|
||||||
}
|
|
||||||
u16 GetMaskOR() const
|
|
||||||
{
|
|
||||||
// return set_mask_while_drawing ? 0x8000 : 0x0000;
|
|
||||||
return Truncate16((bits << 13) & 0x8000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendCommand
|
|
||||||
{
|
|
||||||
u32 size;
|
|
||||||
GPUBackendCommandType type;
|
|
||||||
GPUBackendCommandParameters params;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendSyncCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
bool allow_sleep;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendFillVRAMCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
u16 x;
|
|
||||||
u16 y;
|
|
||||||
u16 width;
|
|
||||||
u16 height;
|
|
||||||
u32 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendUpdateVRAMCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
u16 x;
|
|
||||||
u16 y;
|
|
||||||
u16 width;
|
|
||||||
u16 height;
|
|
||||||
u16 data[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendCopyVRAMCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
u16 src_x;
|
|
||||||
u16 src_y;
|
|
||||||
u16 dst_x;
|
|
||||||
u16 dst_y;
|
|
||||||
u16 width;
|
|
||||||
u16 height;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendSetDrawingAreaCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
GPUDrawingArea new_area;
|
|
||||||
s32 new_clamped_area[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendUpdateCLUTCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
GPUTexturePaletteReg reg;
|
|
||||||
bool clut_is_8bit;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendDrawCommand : public GPUBackendCommand
|
|
||||||
{
|
|
||||||
GPUDrawModeReg draw_mode;
|
|
||||||
GPURenderCommand rc;
|
|
||||||
GPUTexturePaletteReg palette;
|
|
||||||
GPUTextureWindow window;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
|
|
||||||
{
|
|
||||||
u16 num_vertices;
|
|
||||||
|
|
||||||
struct Vertex
|
|
||||||
{
|
|
||||||
s32 x, y;
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
u8 r, g, b, a;
|
|
||||||
};
|
|
||||||
u32 color;
|
|
||||||
};
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
u8 u, v;
|
|
||||||
};
|
|
||||||
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 GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
|
|
||||||
{
|
|
||||||
s32 x, y;
|
|
||||||
u16 width, height;
|
|
||||||
u16 texcoord;
|
|
||||||
u32 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUBackendDrawLineCommand : public GPUBackendDrawCommand
|
|
||||||
{
|
|
||||||
u16 num_vertices;
|
|
||||||
|
|
||||||
struct Vertex
|
|
||||||
{
|
|
||||||
s32 x, y;
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
u8 r, g, b, a;
|
|
||||||
};
|
|
||||||
u32 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_)
|
|
||||||
{
|
|
||||||
x = x_;
|
|
||||||
y = y_;
|
|
||||||
color = color_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Vertex vertices[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#pragma warning(pop)
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -2,19 +2,13 @@
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "fullscreen_ui.h"
|
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
#include "imgui_overlays.h"
|
|
||||||
#include "shader_cache_version.h"
|
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "system_private.h"
|
#include "system_private.h"
|
||||||
|
|
||||||
#include "scmversion/scmversion.h"
|
#include "scmversion/scmversion.h"
|
||||||
|
|
||||||
#include "util/compress_helpers.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/assert.h"
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
|
@ -342,181 +336,3 @@ std::string Host::GetHTTPUserAgent()
|
||||||
{
|
{
|
||||||
return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str);
|
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;
|
|
||||||
if (g_settings.gpu_disable_compute_shaders)
|
|
||||||
disabled_features |= GPUDevice::FEATURE_MASK_COMPUTE_SHADERS;
|
|
||||||
if (g_settings.gpu_disable_compressed_textures)
|
|
||||||
disabled_features |= GPUDevice::FEATURE_MASK_COMPRESSED_TEXTURES;
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
{
|
|
||||||
DEV_LOG("Switching to surfaceless device");
|
|
||||||
if (!g_gpu_device->SwitchToSurfacelessRendering(&error))
|
|
||||||
ERROR_LOG("Failed to switch to surfaceless, rendering commands may fail: {}", error.GetDescription());
|
|
||||||
|
|
||||||
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 float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
|
|
||||||
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
|
|
||||||
ImGuiManager::WindowResized(f_width, f_height);
|
|
||||||
InputManager::SetDisplayWindowSize(f_width, f_height);
|
|
||||||
System::DisplayWindowResized();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
|
|
||||||
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
|
|
||||||
ImGuiManager::WindowResized(f_width, f_height);
|
|
||||||
InputManager::SetDisplayWindowSize(f_width, f_height);
|
|
||||||
System::DisplayWindowResized();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
|
@ -75,10 +75,6 @@ std::span<const std::pair<const char*, const char*>> GetAvailableLanguageList();
|
||||||
/// Refreshes the UI when the language is changed.
|
/// Refreshes the UI when the language is changed.
|
||||||
bool ChangeLanguage(const char* new_language);
|
bool ChangeLanguage(const char* new_language);
|
||||||
|
|
||||||
/// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks
|
|
||||||
/// such as compiling shaders when starting up.
|
|
||||||
void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1);
|
|
||||||
|
|
||||||
/// Safely executes a function on the VM thread.
|
/// Safely executes a function on the VM thread.
|
||||||
void RunOnCPUThread(std::function<void()> function, bool block = false);
|
void RunOnCPUThread(std::function<void()> function, bool block = false);
|
||||||
|
|
||||||
|
@ -96,21 +92,6 @@ bool IsFullscreen();
|
||||||
/// Alters fullscreen state of hosting application.
|
/// Alters fullscreen state of hosting application.
|
||||||
void SetFullscreen(bool enabled);
|
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 {
|
namespace Internal {
|
||||||
|
|
||||||
/// Returns true if the host should use portable mode.
|
/// Returns true if the host should use portable mode.
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
|
||||||
|
|
||||||
#include "host_interface_progress_callback.h"
|
|
||||||
#include "host.h"
|
|
||||||
|
|
||||||
#include "common/log.h"
|
|
||||||
|
|
||||||
LOG_CHANNEL(Host);
|
|
||||||
|
|
||||||
HostInterfaceProgressCallback::HostInterfaceProgressCallback() : ProgressCallback()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::PushState()
|
|
||||||
{
|
|
||||||
ProgressCallback::PushState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::PopState()
|
|
||||||
{
|
|
||||||
ProgressCallback::PopState();
|
|
||||||
Redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::SetCancellable(bool cancellable)
|
|
||||||
{
|
|
||||||
ProgressCallback::SetCancellable(cancellable);
|
|
||||||
Redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::SetTitle(const std::string_view title)
|
|
||||||
{
|
|
||||||
// todo?
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::SetStatusText(const std::string_view text)
|
|
||||||
{
|
|
||||||
ProgressCallback::SetStatusText(text);
|
|
||||||
Redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::SetProgressRange(u32 range)
|
|
||||||
{
|
|
||||||
u32 last_range = m_progress_range;
|
|
||||||
|
|
||||||
ProgressCallback::SetProgressRange(range);
|
|
||||||
|
|
||||||
if (m_progress_range != last_range)
|
|
||||||
Redraw(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::SetProgressValue(u32 value)
|
|
||||||
{
|
|
||||||
u32 lastValue = m_progress_value;
|
|
||||||
|
|
||||||
ProgressCallback::SetProgressValue(value);
|
|
||||||
|
|
||||||
if (m_progress_value != lastValue)
|
|
||||||
Redraw(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::Redraw(bool force)
|
|
||||||
{
|
|
||||||
if (m_last_progress_percent < 0 && m_open_time.GetTimeSeconds() < m_open_delay)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int percent =
|
|
||||||
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
|
|
||||||
if (percent == m_last_progress_percent && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_last_progress_percent = percent;
|
|
||||||
Host::DisplayLoadingScreen(m_status_text.c_str(), 0, static_cast<int>(m_progress_range),
|
|
||||||
static_cast<int>(m_progress_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostInterfaceProgressCallback::ModalError(const std::string_view message)
|
|
||||||
{
|
|
||||||
ERROR_LOG(message);
|
|
||||||
Host::ReportErrorAsync("Error", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HostInterfaceProgressCallback::ModalConfirmation(const std::string_view message)
|
|
||||||
{
|
|
||||||
INFO_LOG(message);
|
|
||||||
return Host::ConfirmMessage("Confirm", message);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/progress_callback.h"
|
|
||||||
#include "common/timer.h"
|
|
||||||
|
|
||||||
class HostInterfaceProgressCallback final : public ProgressCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HostInterfaceProgressCallback();
|
|
||||||
|
|
||||||
ALWAYS_INLINE void SetOpenDelay(float delay) { m_open_delay = delay; }
|
|
||||||
|
|
||||||
void PushState() override;
|
|
||||||
void PopState() override;
|
|
||||||
|
|
||||||
void SetCancellable(bool cancellable) override;
|
|
||||||
void SetTitle(const std::string_view title) override;
|
|
||||||
void SetStatusText(const std::string_view text) override;
|
|
||||||
void SetProgressRange(u32 range) override;
|
|
||||||
void SetProgressValue(u32 value) override;
|
|
||||||
|
|
||||||
void ModalError(const std::string_view message) override;
|
|
||||||
bool ModalConfirmation(const std::string_view message) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Redraw(bool force);
|
|
||||||
|
|
||||||
Timer m_open_time;
|
|
||||||
float m_open_delay = 1.0f;
|
|
||||||
int m_last_progress_percent = -1;
|
|
||||||
};
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "fullscreen_ui.h"
|
#include "fullscreen_ui.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
#include "gpu_hw_texture_cache.h"
|
#include "gpu_hw_texture_cache.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "imgui_overlays.h"
|
#include "imgui_overlays.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
@ -59,8 +60,7 @@ static void HotkeyModifyResolutionScale(s32 increment)
|
||||||
if (System::IsValid())
|
if (System::IsValid())
|
||||||
{
|
{
|
||||||
System::ClearMemorySaveStates(true);
|
System::ClearMemorySaveStates(true);
|
||||||
g_gpu->RestoreDeviceContext();
|
GPUThread::UpdateSettings(true, false);
|
||||||
g_gpu->UpdateSettings(old_settings);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,10 +377,9 @@ DEFINE_HOTKEY("TogglePGXP", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOO
|
||||||
{
|
{
|
||||||
System::ClearMemorySaveStates(true);
|
System::ClearMemorySaveStates(true);
|
||||||
|
|
||||||
Settings old_settings = g_settings;
|
|
||||||
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
|
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
|
||||||
g_gpu->RestoreDeviceContext();
|
GPUThread::UpdateSettings(true, false);
|
||||||
g_gpu->UpdateSettings(old_settings);
|
|
||||||
Host::AddKeyedOSDMessage("TogglePGXP",
|
Host::AddKeyedOSDMessage("TogglePGXP",
|
||||||
g_settings.gpu_pgxp_enable ?
|
g_settings.gpu_pgxp_enable ?
|
||||||
TRANSLATE_STR("OSDMessage", "PGXP is now enabled.") :
|
TRANSLATE_STR("OSDMessage", "PGXP is now enabled.") :
|
||||||
|
@ -427,13 +426,18 @@ DEFINE_HOTKEY("ToggleInternalPostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphic
|
||||||
DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Reload Post Processing Shaders"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Reload Post Processing Shaders"), [](s32 pressed) {
|
||||||
if (!pressed && System::IsValid())
|
if (!pressed && System::IsValid())
|
||||||
PostProcessing::ReloadShaders();
|
{
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
if (GPUThread::HasGPUBackend())
|
||||||
|
PostProcessing::ReloadShaders();
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Reload Texture Replacements"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Reload Texture Replacements"), [](s32 pressed) {
|
||||||
if (!pressed && System::IsValid())
|
if (!pressed && System::IsValid())
|
||||||
GPUTextureCache::ReloadTextureReplacements(true);
|
GPUThread::RunOnThread([]() { GPUTextureCache::ReloadTextureReplacements(true); });
|
||||||
})
|
})
|
||||||
|
|
||||||
DEFINE_HOTKEY("ToggleWidescreen", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle Widescreen"),
|
DEFINE_HOTKEY("ToggleWidescreen", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle Widescreen"),
|
||||||
|
@ -451,11 +455,9 @@ DEFINE_HOTKEY("TogglePGXPDepth", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||||
|
|
||||||
System::ClearMemorySaveStates(true);
|
System::ClearMemorySaveStates(true);
|
||||||
|
|
||||||
const Settings old_settings = g_settings;
|
|
||||||
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
|
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
|
||||||
|
GPUThread::UpdateSettings(true, false);
|
||||||
|
|
||||||
g_gpu->RestoreDeviceContext();
|
|
||||||
g_gpu->UpdateSettings(old_settings);
|
|
||||||
Host::AddKeyedOSDMessage("TogglePGXPDepth",
|
Host::AddKeyedOSDMessage("TogglePGXPDepth",
|
||||||
g_settings.gpu_pgxp_depth_buffer ?
|
g_settings.gpu_pgxp_depth_buffer ?
|
||||||
TRANSLATE_STR("OSDMessage", "PGXP Depth Buffer is now enabled.") :
|
TRANSLATE_STR("OSDMessage", "PGXP Depth Buffer is now enabled.") :
|
||||||
|
@ -473,11 +475,9 @@ DEFINE_HOTKEY("TogglePGXPCPU", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_
|
||||||
|
|
||||||
System::ClearMemorySaveStates(true);
|
System::ClearMemorySaveStates(true);
|
||||||
|
|
||||||
const Settings old_settings = g_settings;
|
// GPU thread is unchanged
|
||||||
g_settings.gpu_pgxp_cpu = !g_settings.gpu_pgxp_cpu;
|
g_settings.gpu_pgxp_cpu = !g_settings.gpu_pgxp_cpu;
|
||||||
|
|
||||||
g_gpu->RestoreDeviceContext();
|
|
||||||
g_gpu->UpdateSettings(old_settings);
|
|
||||||
Host::AddKeyedOSDMessage("TogglePGXPCPU",
|
Host::AddKeyedOSDMessage("TogglePGXPCPU",
|
||||||
g_settings.gpu_pgxp_cpu ?
|
g_settings.gpu_pgxp_cpu ?
|
||||||
TRANSLATE_STR("OSDMessage", "PGXP CPU mode is now enabled.") :
|
TRANSLATE_STR("OSDMessage", "PGXP CPU mode is now enabled.") :
|
||||||
|
@ -587,29 +587,31 @@ DEFINE_HOTKEY("AudioVolumeDown", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_N
|
||||||
DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread(SaveStateSelectorUI::LoadCurrentSlot);
|
GPUThread::RunOnThread(SaveStateSelectorUI::LoadCurrentSlot);
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread(SaveStateSelectorUI::SaveCurrentSlot);
|
GPUThread::RunOnThread(SaveStateSelectorUI::SaveCurrentSlot);
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
|
GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
|
GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
|
||||||
if (!pressed && System::IsValid())
|
if (!pressed && System::IsValid())
|
||||||
{
|
{
|
||||||
SaveStateSelectorUI::SaveCurrentSlot();
|
GPUThread::RunOnThread([]() {
|
||||||
SaveStateSelectorUI::SelectNextSlot(false);
|
SaveStateSelectorUI::SaveCurrentSlot();
|
||||||
|
SaveStateSelectorUI::SelectNextSlot(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include "dma.h"
|
#include "dma.h"
|
||||||
#include "fullscreen_ui.h"
|
#include "fullscreen_ui.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_backend.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "mdec.h"
|
#include "mdec.h"
|
||||||
#include "performance_counters.h"
|
#include "performance_counters.h"
|
||||||
|
@ -70,10 +72,10 @@ struct DebugWindowInfo
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static void FormatProcessorStat(SmallStringBase& text, double usage, double time);
|
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 DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing);
|
||||||
static void DrawFrameTimeOverlay(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();
|
static void DrawInputsOverlay();
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
|
@ -119,93 +121,22 @@ static std::tuple<float, float> GetMinMax(std::span<const float> values)
|
||||||
return std::tie(min, max);
|
return std::tie(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/,
|
bool ImGuiManager::AreAnyDebugWindowsEnabled(const SettingsInterface& si)
|
||||||
int progress_value /*= -1*/)
|
|
||||||
{
|
{
|
||||||
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
#ifndef __ANDROID__
|
||||||
|
const bool block_all = Achievements::IsHardcoreModeActive();
|
||||||
|
if (block_all)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < NUM_DEBUG_WINDOWS; i++)
|
||||||
{
|
{
|
||||||
INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
|
const DebugWindowInfo& info = s_debug_window_info[i];
|
||||||
return;
|
if (si.GetBoolValue(DEBUG_WINDOW_CONFIG_SECTION, info.name, false))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto& io = ImGui::GetIO();
|
return false;
|
||||||
const float scale = ImGuiManager::GetGlobalScale();
|
|
||||||
const float width = (400.0f * scale);
|
|
||||||
const bool has_progress = (progress_min < progress_max);
|
|
||||||
|
|
||||||
// eat the last imgui frame, it might've been partially rendered by the caller.
|
|
||||||
ImGui::EndFrame();
|
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
const float logo_width = 260.0f * scale;
|
|
||||||
const float logo_height = 260.0f * scale;
|
|
||||||
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(logo_width, logo_height), ImGuiCond_Always);
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) - (50.0f * scale)),
|
|
||||||
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
|
||||||
if (ImGui::Begin("LoadingScreenLogo", nullptr,
|
|
||||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
|
|
||||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
|
||||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing |
|
|
||||||
ImGuiWindowFlags_NoBackground))
|
|
||||||
{
|
|
||||||
GPUTexture* tex = ImGuiFullscreen::GetCachedTexture("images/duck.png");
|
|
||||||
if (tex)
|
|
||||||
ImGui::Image(tex, ImVec2(logo_width, logo_height));
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
const float padding_and_rounding = 18.0f * scale;
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, padding_and_rounding);
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding_and_rounding, padding_and_rounding));
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(width, (has_progress ? 90.0f : 55.0f) * scale), ImGuiCond_Always);
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) + (100.0f * scale)),
|
|
||||||
ImGuiCond_Always, ImVec2(0.5f, 0.0f));
|
|
||||||
if (ImGui::Begin("LoadingScreen", nullptr,
|
|
||||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
|
|
||||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
|
||||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
|
|
||||||
{
|
|
||||||
if (has_progress)
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted(message);
|
|
||||||
|
|
||||||
TinyString buf;
|
|
||||||
buf.format("{}/{}", progress_value, progress_max);
|
|
||||||
|
|
||||||
const ImVec2 prog_size = ImGui::CalcTextSize(buf.c_str(), buf.end_ptr());
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::SetCursorPosX(width - padding_and_rounding - prog_size.x);
|
|
||||||
ImGui::TextUnformatted(buf.c_str(), buf.end_ptr());
|
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f);
|
|
||||||
|
|
||||||
ImGui::ProgressBar(static_cast<float>(progress_value) / static_cast<float>(progress_max - progress_min),
|
|
||||||
ImVec2(-1.0f, 0.0f), "");
|
|
||||||
INFO_LOG("{}: {}", message, buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const ImVec2 text_size(ImGui::CalcTextSize(message));
|
|
||||||
ImGui::SetCursorPosX((width - text_size.x) / 2.0f);
|
|
||||||
ImGui::TextUnformatted(message);
|
|
||||||
INFO_LOG(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
ImGui::PopStyleVar(2);
|
|
||||||
|
|
||||||
ImGui::EndFrame();
|
|
||||||
|
|
||||||
// TODO: Glass effect or something.
|
|
||||||
|
|
||||||
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
|
|
||||||
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
|
|
||||||
{
|
|
||||||
g_gpu_device->RenderImGui(swap_chain);
|
|
||||||
g_gpu_device->EndPresent(swap_chain, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::NewFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImGuiManager::UpdateDebugWindowConfig()
|
bool ImGuiManager::UpdateDebugWindowConfig()
|
||||||
|
@ -284,26 +215,28 @@ void ImGuiManager::DestroyAllDebugWindows()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiManager::RenderTextOverlays()
|
void ImGuiManager::RenderTextOverlays(const GPUBackend* gpu)
|
||||||
{
|
{
|
||||||
const System::State state = System::GetState();
|
// Don't draw anything with loading screen open, it'll be nonsensical.
|
||||||
if (state != System::State::Shutdown)
|
if (ImGuiFullscreen::IsLoadingScreenOpen())
|
||||||
{
|
return;
|
||||||
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)
|
const bool paused = GPUThread::IsSystemPaused();
|
||||||
DrawEnhancementsOverlay();
|
|
||||||
|
|
||||||
if (g_settings.display_show_inputs && state != System::State::Paused)
|
const float scale = ImGuiManager::GetGlobalScale();
|
||||||
DrawInputsOverlay();
|
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_gpu_settings.display_show_enhancements && !paused)
|
||||||
|
DrawEnhancementsOverlay(gpu);
|
||||||
|
|
||||||
|
if (g_gpu_settings.display_show_inputs && !paused)
|
||||||
|
DrawInputsOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, double time)
|
void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, double time)
|
||||||
|
@ -317,12 +250,13 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub
|
||||||
text.append_format("{:.1f}% ({:.2f}ms)", usage, time);
|
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 ||
|
if (!(g_gpu_settings.display_show_fps || g_gpu_settings.display_show_speed || g_gpu_settings.display_show_gpu_stats ||
|
||||||
g_settings.display_show_resolution || g_settings.display_show_cpu_usage ||
|
g_gpu_settings.display_show_resolution || g_gpu_settings.display_show_cpu_usage ||
|
||||||
(g_settings.display_show_status_indicators &&
|
(g_gpu_settings.display_show_status_indicators &&
|
||||||
(System::IsPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
|
(GPUThread::IsSystemPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -352,9 +286,9 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
if (state == System::State::Running)
|
if (state == System::State::Running)
|
||||||
{
|
{
|
||||||
const float speed = PerformanceCounters::GetEmulationSpeed();
|
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());
|
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)));
|
text.append_format("{}{}%", text.empty() ? "" : " | ", static_cast<u32>(std::round(speed)));
|
||||||
|
|
||||||
|
@ -377,19 +311,19 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
DRAW_LINE(fixed_font, text, color);
|
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));
|
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));
|
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 u32 resolution_scale = gpu->GetResolutionScale();
|
||||||
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution(); // wrong
|
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution(); // NOTE: Racey read.
|
||||||
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||||
const bool pal = g_gpu->IsInPALMode();
|
const bool pal = g_gpu->IsInPALMode();
|
||||||
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
|
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
|
||||||
|
@ -397,13 +331,13 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
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);
|
System::FormatLatencyStats(text);
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
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(),
|
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", PerformanceCounters::GetMinimumFrameTime(),
|
||||||
PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime());
|
PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime());
|
||||||
|
@ -454,11 +388,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
PerformanceCounters::GetCPUThreadAverageTime());
|
PerformanceCounters::GetCPUThreadAverageTime());
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
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: ");
|
text.assign("RNDR: ");
|
||||||
FormatProcessorStat(text, PerformanceCounters::GetSWThreadUsage(),
|
FormatProcessorStat(text, PerformanceCounters::GetGPUThreadUsage(),
|
||||||
PerformanceCounters::GetSWThreadAverageTime());
|
PerformanceCounters::GetGPUThreadAverageTime());
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,14 +406,14 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
#endif
|
#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: ");
|
text.assign("GPU: ");
|
||||||
FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime());
|
FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime());
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
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();
|
const bool rewinding = System::IsRewinding();
|
||||||
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
|
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
|
||||||
|
@ -489,7 +423,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())
|
!FullscreenUI::HasActiveWindow())
|
||||||
{
|
{
|
||||||
text.assign(ICON_EMOJI_PAUSE);
|
text.assign(ICON_EMOJI_PAUSE);
|
||||||
|
@ -499,12 +433,12 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
#undef DRAW_LINE
|
#undef DRAW_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiManager::DrawEnhancementsOverlay()
|
void ImGuiManager::DrawEnhancementsOverlay(const GPUBackend* gpu)
|
||||||
{
|
{
|
||||||
LargeString text;
|
LargeString text;
|
||||||
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
|
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
|
||||||
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
|
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
|
||||||
g_gpu->IsHardwareRenderer() ? "HW" : "SW");
|
GPUBackend::IsUsingHardwareBackend() ? "HW" : "SW");
|
||||||
|
|
||||||
if (g_settings.rewind_enable)
|
if (g_settings.rewind_enable)
|
||||||
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
|
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
|
||||||
|
@ -626,7 +560,7 @@ void ImGuiManager::DrawMediaCaptureOverlay(float& position_y, float scale, float
|
||||||
|
|
||||||
void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing)
|
void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing)
|
||||||
{
|
{
|
||||||
if (!g_settings.display_show_frame_times || System::IsPaused())
|
if (!g_settings.display_show_frame_times || GPUThread::IsSystemPaused())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float shadow_offset = std::ceil(1.0f * scale);
|
const float shadow_offset = std::ceil(1.0f * scale);
|
||||||
|
@ -864,8 +798,6 @@ bool SaveStateSelectorUI::IsOpen()
|
||||||
|
|
||||||
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
||||||
{
|
{
|
||||||
const std::string& serial = System::GetGameSerial();
|
|
||||||
|
|
||||||
s_state.open_time = 0.0f;
|
s_state.open_time = 0.0f;
|
||||||
s_state.close_time = open_time;
|
s_state.close_time = open_time;
|
||||||
|
|
||||||
|
@ -876,7 +808,7 @@ void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
||||||
s_state.placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
|
s_state.placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
|
||||||
|
|
||||||
s_state.is_open = true;
|
s_state.is_open = true;
|
||||||
RefreshList(serial);
|
RefreshList();
|
||||||
RefreshHotkeyLegend();
|
RefreshHotkeyLegend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -889,7 +821,7 @@ void SaveStateSelectorUI::Close()
|
||||||
s_state.next_legend = {};
|
s_state.next_legend = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
void SaveStateSelectorUI::RefreshList()
|
||||||
{
|
{
|
||||||
for (ListEntry& entry : s_state.slots)
|
for (ListEntry& entry : s_state.slots)
|
||||||
{
|
{
|
||||||
|
@ -898,9 +830,7 @@ void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
||||||
}
|
}
|
||||||
s_state.slots.clear();
|
s_state.slots.clear();
|
||||||
|
|
||||||
if (System::IsShutdown())
|
const std::string& serial = GPUThread::GetGameSerial();
|
||||||
return;
|
|
||||||
|
|
||||||
if (!serial.empty())
|
if (!serial.empty())
|
||||||
{
|
{
|
||||||
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
|
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
|
||||||
|
@ -956,6 +886,7 @@ void SaveStateSelectorUI::Clear()
|
||||||
|
|
||||||
void SaveStateSelectorUI::ClearList()
|
void SaveStateSelectorUI::ClearList()
|
||||||
{
|
{
|
||||||
|
DebugAssert(GPUThread::IsOnThread());
|
||||||
for (ListEntry& li : s_state.slots)
|
for (ListEntry& li : s_state.slots)
|
||||||
{
|
{
|
||||||
if (li.preview_texture)
|
if (li.preview_texture)
|
||||||
|
@ -1001,7 +932,7 @@ void SaveStateSelectorUI::SelectNextSlot(bool open_selector)
|
||||||
s_state.current_slot++;
|
s_state.current_slot++;
|
||||||
if (s_state.current_slot >= total_slots)
|
if (s_state.current_slot >= total_slots)
|
||||||
{
|
{
|
||||||
if (!System::GetGameSerial().empty())
|
if (!GPUThread::GetGameSerial().empty())
|
||||||
s_state.current_slot_global ^= true;
|
s_state.current_slot_global ^= true;
|
||||||
s_state.current_slot -= total_slots;
|
s_state.current_slot -= total_slots;
|
||||||
}
|
}
|
||||||
|
@ -1024,7 +955,7 @@ void SaveStateSelectorUI::SelectPreviousSlot(bool open_selector)
|
||||||
s_state.current_slot--;
|
s_state.current_slot--;
|
||||||
if (s_state.current_slot < 0)
|
if (s_state.current_slot < 0)
|
||||||
{
|
{
|
||||||
if (!System::GetGameSerial().empty())
|
if (!GPUThread::GetGameSerial().empty())
|
||||||
s_state.current_slot_global ^= true;
|
s_state.current_slot_global ^= true;
|
||||||
s_state.current_slot +=
|
s_state.current_slot +=
|
||||||
s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||||
|
@ -1245,7 +1176,7 @@ std::string SaveStateSelectorUI::GetCurrentSlotPath()
|
||||||
std::string filename;
|
std::string filename;
|
||||||
if (!s_state.current_slot_global)
|
if (!s_state.current_slot_global)
|
||||||
{
|
{
|
||||||
if (const std::string& serial = System::GetGameSerial(); !serial.empty())
|
if (const std::string& serial = GPUThread::GetGameSerial(); !serial.empty())
|
||||||
filename = System::GetGameSaveStateFileName(serial, s_state.current_slot + 1);
|
filename = System::GetGameSaveStateFileName(serial, s_state.current_slot + 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1258,18 +1189,22 @@ std::string SaveStateSelectorUI::GetCurrentSlotPath()
|
||||||
|
|
||||||
void SaveStateSelectorUI::LoadCurrentSlot()
|
void SaveStateSelectorUI::LoadCurrentSlot()
|
||||||
{
|
{
|
||||||
|
DebugAssert(GPUThread::IsOnThread());
|
||||||
|
|
||||||
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||||
{
|
{
|
||||||
if (FileSystem::FileExists(path.c_str()))
|
if (FileSystem::FileExists(path.c_str()))
|
||||||
{
|
{
|
||||||
Error error;
|
Host::RunOnCPUThread([path = std::move(path)]() {
|
||||||
if (!System::LoadState(path.c_str(), &error, true))
|
Error error;
|
||||||
{
|
if (!System::LoadState(path.c_str(), &error, true))
|
||||||
Host::AddKeyedOSDMessage("LoadState",
|
{
|
||||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"),
|
Host::AddKeyedOSDMessage("LoadState",
|
||||||
GetCurrentSlot(), error.GetDescription()),
|
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"),
|
||||||
Host::OSD_ERROR_DURATION);
|
GetCurrentSlot(), error.GetDescription()),
|
||||||
}
|
Host::OSD_ERROR_DURATION);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1289,14 +1224,16 @@ void SaveStateSelectorUI::SaveCurrentSlot()
|
||||||
{
|
{
|
||||||
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||||
{
|
{
|
||||||
Error error;
|
Host::RunOnCPUThread([path = std::move(path)]() {
|
||||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
Error error;
|
||||||
{
|
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||||
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
|
{
|
||||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
|
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
|
||||||
GetCurrentSlot(), error.GetDescription()),
|
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
|
||||||
Host::OSD_ERROR_DURATION);
|
GetCurrentSlot(), error.GetDescription()),
|
||||||
}
|
Host::OSD_ERROR_DURATION);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
@ -1323,7 +1260,7 @@ void SaveStateSelectorUI::ShowSlotOSDMessage()
|
||||||
void ImGuiManager::RenderOverlayWindows()
|
void ImGuiManager::RenderOverlayWindows()
|
||||||
{
|
{
|
||||||
const System::State state = System::GetState();
|
const System::State state = System::GetState();
|
||||||
if (state != System::State::Shutdown)
|
if (state == System::State::Paused || state == System::State::Running)
|
||||||
{
|
{
|
||||||
if (SaveStateSelectorUI::s_state.is_open)
|
if (SaveStateSelectorUI::s_state.is_open)
|
||||||
SaveStateSelectorUI::Draw();
|
SaveStateSelectorUI::Draw();
|
||||||
|
|
|
@ -7,14 +7,23 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
class SettingsInterface;
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
|
||||||
namespace ImGuiManager {
|
namespace ImGuiManager {
|
||||||
void RenderTextOverlays();
|
|
||||||
|
static constexpr const char* LOGO_IMAGE_NAME = "images/duck.png";
|
||||||
|
|
||||||
|
void RenderTextOverlays(const GPUBackend* gpu);
|
||||||
|
bool AreAnyDebugWindowsEnabled(const SettingsInterface& si);
|
||||||
void RenderDebugWindows();
|
void RenderDebugWindows();
|
||||||
bool UpdateDebugWindowConfig();
|
bool UpdateDebugWindowConfig();
|
||||||
void DestroyAllDebugWindows();
|
void DestroyAllDebugWindows();
|
||||||
|
|
||||||
void RenderOverlayWindows();
|
void RenderOverlayWindows();
|
||||||
void DestroyOverlayTextures();
|
void DestroyOverlayTextures();
|
||||||
|
|
||||||
} // namespace ImGuiManager
|
} // namespace ImGuiManager
|
||||||
|
|
||||||
namespace SaveStateSelectorUI {
|
namespace SaveStateSelectorUI {
|
||||||
|
@ -23,7 +32,7 @@ static constexpr float DEFAULT_OPEN_TIME = 7.5f;
|
||||||
|
|
||||||
bool IsOpen();
|
bool IsOpen();
|
||||||
void Open(float open_time = DEFAULT_OPEN_TIME);
|
void Open(float open_time = DEFAULT_OPEN_TIME);
|
||||||
void RefreshList(const std::string& serial);
|
void RefreshList();
|
||||||
void Clear();
|
void Clear();
|
||||||
void ClearList();
|
void ClearList();
|
||||||
void Close();
|
void Close();
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "performance_counters.h"
|
#include "performance_counters.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
#include "gpu_backend.h"
|
||||||
|
#include "gpu_thread.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "system_private.h"
|
#include "system_private.h"
|
||||||
|
|
||||||
|
@ -45,9 +47,9 @@ struct State
|
||||||
float cpu_thread_usage;
|
float cpu_thread_usage;
|
||||||
float cpu_thread_time;
|
float cpu_thread_time;
|
||||||
|
|
||||||
u64 last_sw_time;
|
u64 last_gpu_thread_time;
|
||||||
float sw_thread_usage;
|
float gpu_thread_usage;
|
||||||
float sw_thread_time;
|
float gpu_thread_time;
|
||||||
|
|
||||||
float average_gpu_time;
|
float average_gpu_time;
|
||||||
float accumulated_gpu_time;
|
float accumulated_gpu_time;
|
||||||
|
@ -105,14 +107,14 @@ float PerformanceCounters::GetCPUThreadAverageTime()
|
||||||
return s_state.cpu_thread_time;
|
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()
|
float PerformanceCounters::GetGPUUsage()
|
||||||
|
@ -150,17 +152,16 @@ void PerformanceCounters::Reset()
|
||||||
s_state.last_frame_number = System::GetFrameNumber();
|
s_state.last_frame_number = System::GetFrameNumber();
|
||||||
s_state.last_internal_frame_number = System::GetInternalFrameNumber();
|
s_state.last_internal_frame_number = System::GetInternalFrameNumber();
|
||||||
s_state.last_cpu_time = System::GetCPUThreadHandle().GetCPUTime();
|
s_state.last_cpu_time = System::GetCPUThreadHandle().GetCPUTime();
|
||||||
if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread)
|
s_state.last_gpu_thread_time = GPUThread::Internal::GetThreadHandle().GetCPUTime();
|
||||||
s_state.last_sw_time = sw_thread->GetCPUTime();
|
|
||||||
else
|
|
||||||
s_state.last_sw_time = 0;
|
|
||||||
|
|
||||||
s_state.average_frame_time_accumulator = 0.0f;
|
s_state.average_frame_time_accumulator = 0.0f;
|
||||||
s_state.minimum_frame_time_accumulator = 0.0f;
|
s_state.minimum_frame_time_accumulator = 0.0f;
|
||||||
s_state.maximum_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 Timer::Value now_ticks = Timer::GetCurrentValue();
|
const Timer::Value now_ticks = Timer::GetCurrentValue();
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
|
||||||
// update fps counter
|
// update fps counter
|
||||||
const Timer::Value ticks_diff = now_ticks - s_state.last_update_time;
|
const Timer::Value ticks_diff = now_ticks - s_state.last_update_time;
|
||||||
const float time = static_cast<float>(Timer::ConvertValueToSeconds(ticks_diff));
|
const float time = static_cast<float>(Timer::ConvertValueToSeconds(ticks_diff));
|
||||||
if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL)
|
if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL || s_state.last_frame_number == frame_number)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_state.last_update_time = now_ticks;
|
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.fps = static_cast<float>(internal_frames_run) / time;
|
||||||
s_state.speed = (s_state.vps / System::GetVideoFrameRate()) * 100.0f;
|
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 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 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_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_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.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.gpu_thread_usage = static_cast<float>(static_cast<double>(gpu_thread_delta) * pct_divider);
|
||||||
s_state.sw_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider);
|
s_state.gpu_thread_time = static_cast<float>(static_cast<double>(gpu_thread_delta) * time_divider);
|
||||||
|
|
||||||
if (MediaCapture* cap = System::GetMediaCapture())
|
if (MediaCapture* cap = System::GetMediaCapture())
|
||||||
cap->UpdateCaptureThreadUsage(pct_divider, time_divider);
|
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;
|
s_state.presents_since_last_update = 0;
|
||||||
|
|
||||||
if (g_settings.display_show_gpu_stats)
|
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,
|
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} RNDR: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms",
|
||||||
s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time,
|
s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_thread_usage, s_state.gpu_usage,
|
||||||
s_state.minimum_frame_time, s_state.maximum_frame_time);
|
s_state.average_frame_time, s_state.minimum_frame_time, s_state.maximum_frame_time);
|
||||||
|
|
||||||
Host::OnPerformanceCountersUpdated();
|
Host::OnPerformanceCountersUpdated(gpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceCounters::AccumulateGPUTime()
|
void PerformanceCounters::AccumulateGPUTime()
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
|
||||||
namespace PerformanceCounters
|
namespace PerformanceCounters
|
||||||
{
|
{
|
||||||
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
|
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
|
||||||
|
@ -18,8 +20,8 @@ float GetMinimumFrameTime();
|
||||||
float GetMaximumFrameTime();
|
float GetMaximumFrameTime();
|
||||||
float GetCPUThreadUsage();
|
float GetCPUThreadUsage();
|
||||||
float GetCPUThreadAverageTime();
|
float GetCPUThreadAverageTime();
|
||||||
float GetSWThreadUsage();
|
float GetGPUThreadUsage();
|
||||||
float GetSWThreadAverageTime();
|
float GetGPUThreadAverageTime();
|
||||||
float GetGPUUsage();
|
float GetGPUUsage();
|
||||||
float GetGPUAverageTime();
|
float GetGPUAverageTime();
|
||||||
const FrameTimeHistory& GetFrameTimeHistory();
|
const FrameTimeHistory& GetFrameTimeHistory();
|
||||||
|
@ -27,7 +29,7 @@ u32 GetFrameTimeHistoryPos();
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
void Reset();
|
void Reset();
|
||||||
void Update(u32 frame_number, u32 internal_frame_number);
|
void Update(GPUBackend* gpu, u32 frame_number, u32 internal_frame_number);
|
||||||
void AccumulateGPUTime();
|
void AccumulateGPUTime();
|
||||||
|
|
||||||
} // namespace Host
|
} // namespace Host
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "achievements.h"
|
#include "achievements.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
#include "imgui_overlays.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
|
||||||
#include "util/gpu_device.h"
|
#include "util/gpu_device.h"
|
||||||
|
@ -28,7 +29,8 @@
|
||||||
|
|
||||||
LOG_CHANNEL(Settings);
|
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
|
const char* SettingInfo::StringDefaultValue() const
|
||||||
{
|
{
|
||||||
|
@ -206,6 +208,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
|
||||||
gpu_disable_compressed_textures = si.GetBoolValue("GPU", "DisableCompressedTextures", false);
|
gpu_disable_compressed_textures = si.GetBoolValue("GPU", "DisableCompressedTextures", false);
|
||||||
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
|
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
|
||||||
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
|
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_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
|
||||||
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
|
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
|
||||||
gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
|
gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
|
||||||
|
@ -554,6 +557,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||||
}
|
}
|
||||||
|
|
||||||
si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading);
|
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", "UseThread", gpu_use_thread);
|
||||||
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
|
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
|
||||||
si.SetBoolValue("GPU", "TrueColor", gpu_true_color);
|
si.SetBoolValue("GPU", "TrueColor", gpu_true_color);
|
||||||
|
@ -950,7 +954,7 @@ std::string Settings::TextureReplacementSettings::Configuration::ExportToYAML(bo
|
||||||
comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter
|
comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
void Settings::FixIncompatibleSettings(const SettingsInterface& si, bool display_osd_messages)
|
||||||
{
|
{
|
||||||
if (g_settings.disable_all_enhancements)
|
if (g_settings.disable_all_enhancements)
|
||||||
{
|
{
|
||||||
|
@ -1022,6 +1026,13 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
||||||
(g_settings.gpu_renderer != GPURenderer::Software && g_settings.gpu_texture_cache);
|
(g_settings.gpu_renderer != GPURenderer::Software && g_settings.gpu_texture_cache);
|
||||||
g_settings.texture_replacements.enable_vram_write_replacements &= (g_settings.gpu_renderer != GPURenderer::Software);
|
g_settings.texture_replacements.enable_vram_write_replacements &= (g_settings.gpu_renderer != GPURenderer::Software);
|
||||||
|
|
||||||
|
// GPU thread should be disabled if any debug windows are active, since they will be racing to read CPU thread state.
|
||||||
|
if (g_settings.gpu_use_thread && g_settings.gpu_max_queued_frames > 0 && ImGuiManager::AreAnyDebugWindowsEnabled(si))
|
||||||
|
{
|
||||||
|
WARNING_LOG("Setting maximum queued frames to 0 because one or more debug windows are enabled.");
|
||||||
|
g_settings.gpu_max_queued_frames = 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef ENABLE_MMAP_FASTMEM
|
#ifndef ENABLE_MMAP_FASTMEM
|
||||||
if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
|
if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
|
||||||
{
|
{
|
||||||
|
@ -1081,6 +1092,21 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Settings::AreGPUDeviceSettingsChanged(const Settings& old_settings) const
|
||||||
|
{
|
||||||
|
return (gpu_use_debug_device != old_settings.gpu_use_debug_device ||
|
||||||
|
gpu_disable_shader_cache != old_settings.gpu_disable_shader_cache ||
|
||||||
|
gpu_disable_dual_source_blend != old_settings.gpu_disable_dual_source_blend ||
|
||||||
|
gpu_disable_framebuffer_fetch != old_settings.gpu_disable_framebuffer_fetch ||
|
||||||
|
gpu_disable_texture_buffers != old_settings.gpu_disable_texture_buffers ||
|
||||||
|
gpu_disable_texture_copy_to_self != old_settings.gpu_disable_texture_copy_to_self ||
|
||||||
|
gpu_disable_memory_import != old_settings.gpu_disable_memory_import ||
|
||||||
|
gpu_disable_raster_order_views != old_settings.gpu_disable_raster_order_views ||
|
||||||
|
gpu_disable_compute_shaders != old_settings.gpu_disable_compute_shaders ||
|
||||||
|
gpu_disable_compressed_textures != old_settings.gpu_disable_compressed_textures ||
|
||||||
|
display_exclusive_fullscreen_control != old_settings.display_exclusive_fullscreen_control);
|
||||||
|
}
|
||||||
|
|
||||||
void Settings::SetDefaultLogConfig(SettingsInterface& si)
|
void Settings::SetDefaultLogConfig(SettingsInterface& si)
|
||||||
{
|
{
|
||||||
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL));
|
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL));
|
||||||
|
|
|
@ -98,6 +98,7 @@ struct Settings
|
||||||
std::string gpu_adapter;
|
std::string gpu_adapter;
|
||||||
u8 gpu_resolution_scale = 1;
|
u8 gpu_resolution_scale = 1;
|
||||||
u8 gpu_multisamples = 1;
|
u8 gpu_multisamples = 1;
|
||||||
|
u8 gpu_max_queued_frames = DEFAULT_GPU_MAX_QUEUED_FRAMES;
|
||||||
bool gpu_use_thread : 1 = true;
|
bool gpu_use_thread : 1 = true;
|
||||||
bool gpu_use_software_renderer_for_readbacks : 1 = false;
|
bool gpu_use_software_renderer_for_readbacks : 1 = false;
|
||||||
bool gpu_use_debug_device : 1 = false;
|
bool gpu_use_debug_device : 1 = false;
|
||||||
|
@ -378,7 +379,9 @@ struct Settings
|
||||||
void Save(SettingsInterface& si, bool ignore_base) const;
|
void Save(SettingsInterface& si, bool ignore_base) const;
|
||||||
static void Clear(SettingsInterface& si);
|
static void Clear(SettingsInterface& si);
|
||||||
|
|
||||||
void FixIncompatibleSettings(bool display_osd_messages);
|
void FixIncompatibleSettings(const SettingsInterface& si, bool display_osd_messages);
|
||||||
|
|
||||||
|
bool AreGPUDeviceSettingsChanged(const Settings& old_settings) const;
|
||||||
|
|
||||||
/// Initializes configuration.
|
/// Initializes configuration.
|
||||||
static void SetDefaultLogConfig(SettingsInterface& si);
|
static void SetDefaultLogConfig(SettingsInterface& si);
|
||||||
|
@ -565,13 +568,19 @@ struct Settings
|
||||||
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = true;
|
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = true;
|
||||||
static constexpr bool DEFAULT_FAST_BOOT_VALUE = false;
|
static constexpr bool DEFAULT_FAST_BOOT_VALUE = false;
|
||||||
static constexpr u16 DEFAULT_GDB_SERVER_PORT = 2345;
|
static constexpr u16 DEFAULT_GDB_SERVER_PORT = 2345;
|
||||||
|
|
||||||
|
// TODO: Maybe lower? But that means fast CPU threads would always stall, could be a problem for power management.
|
||||||
|
static constexpr u8 DEFAULT_GPU_MAX_QUEUED_FRAMES = 2;
|
||||||
#else
|
#else
|
||||||
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false;
|
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false;
|
||||||
static constexpr bool DEFAULT_FAST_BOOT_VALUE = true;
|
static constexpr bool DEFAULT_FAST_BOOT_VALUE = true;
|
||||||
|
static constexpr u8 DEFAULT_GPU_MAX_QUEUED_FRAMES = 3;
|
||||||
#endif
|
#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 {
|
namespace EmuFolders {
|
||||||
extern std::string AppRoot;
|
extern std::string AppRoot;
|
||||||
|
|
1126
src/core/system.cpp
1126
src/core/system.cpp
File diff suppressed because it is too large
Load Diff
|
@ -165,7 +165,6 @@ std::unique_ptr<INISettingsInterface> GetGameSettingsInterface(const GameDatabas
|
||||||
std::string GetInputProfilePath(std::string_view name);
|
std::string GetInputProfilePath(std::string_view name);
|
||||||
|
|
||||||
State GetState();
|
State GetState();
|
||||||
void SetState(State new_state);
|
|
||||||
bool IsRunning();
|
bool IsRunning();
|
||||||
bool IsPaused();
|
bool IsPaused();
|
||||||
bool IsShutdown();
|
bool IsShutdown();
|
||||||
|
@ -384,7 +383,7 @@ s32 GetAudioOutputVolume();
|
||||||
void UpdateVolume();
|
void UpdateVolume();
|
||||||
|
|
||||||
/// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically.
|
/// 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,
|
DisplayScreenshotFormat format = g_settings.display_screenshot_format,
|
||||||
u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
|
u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
|
||||||
|
|
||||||
|
@ -400,7 +399,6 @@ MediaCapture* GetMediaCapture();
|
||||||
|
|
||||||
/// Media capture (video and/or audio). If no path is provided, one will be generated automatically.
|
/// Media capture (video and/or audio). If no path is provided, one will be generated automatically.
|
||||||
bool StartMediaCapture(std::string path = {});
|
bool StartMediaCapture(std::string path = {});
|
||||||
bool StartMediaCapture(std::string path, bool capture_video, bool capture_audio);
|
|
||||||
void StopMediaCapture();
|
void StopMediaCapture();
|
||||||
|
|
||||||
/// Toggle Widescreen Hack and Aspect Ratio
|
/// Toggle Widescreen Hack and Aspect Ratio
|
||||||
|
@ -413,15 +411,11 @@ void ToggleSoftwareRendering();
|
||||||
/// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x.
|
/// 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);
|
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)
|
// Memory Save States (Rewind and Runahead)
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage);
|
void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage);
|
||||||
void ClearMemorySaveStates(bool deallocate_resources);
|
void ClearMemorySaveStates(bool reallocate_resources);
|
||||||
void SetRunaheadReplayFlag();
|
void SetRunaheadReplayFlag();
|
||||||
|
|
||||||
/// Shared socket multiplexer, used by PINE/GDB/etc.
|
/// Shared socket multiplexer, used by PINE/GDB/etc.
|
||||||
|
|
|
@ -7,26 +7,32 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
struct GPUBackendFramePresentationParameters;
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
|
|
||||||
/// Memory save states - only for internal use.
|
/// Memory save states - only for internal use.
|
||||||
struct MemorySaveState
|
struct MemorySaveState
|
||||||
{
|
{
|
||||||
std::unique_ptr<GPUTexture> vram_texture;
|
|
||||||
DynamicHeapArray<u8> state_data;
|
DynamicHeapArray<u8> state_data;
|
||||||
size_t state_size;
|
size_t state_size;
|
||||||
|
|
||||||
|
std::unique_ptr<GPUTexture> vram_texture;
|
||||||
|
DynamicHeapArray<u8> gpu_state_data;
|
||||||
|
size_t gpu_state_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
MemorySaveState& AllocateMemoryState();
|
MemorySaveState& AllocateMemoryState();
|
||||||
MemorySaveState& GetFirstMemoryState();
|
MemorySaveState& GetFirstMemoryState();
|
||||||
MemorySaveState& PopMemoryState();
|
MemorySaveState& PopMemoryState();
|
||||||
|
bool AllocateMemoryStates(size_t state_count);
|
||||||
|
void FreeMemoryStateTextures();
|
||||||
void FreeMemoryStateStorage();
|
void FreeMemoryStateStorage();
|
||||||
void LoadMemoryState(MemorySaveState& mss, bool update_display);
|
void LoadMemoryState(MemorySaveState& mss, bool update_display);
|
||||||
bool SaveMemoryState(MemorySaveState& mss);
|
void SaveMemoryState(MemorySaveState& mss);
|
||||||
|
|
||||||
/// Returns the maximum size of a save state, considering the current configuration.
|
|
||||||
size_t GetMaxSaveStateSize();
|
|
||||||
|
|
||||||
|
bool IsRunaheadActive();
|
||||||
void IncrementFrameNumber();
|
void IncrementFrameNumber();
|
||||||
void IncrementInternalFrameNumber();
|
void IncrementInternalFrameNumber();
|
||||||
void FrameDone();
|
void FrameDone();
|
||||||
|
@ -35,6 +41,10 @@ void FrameDone();
|
||||||
GPUVSyncMode GetEffectiveVSyncMode();
|
GPUVSyncMode GetEffectiveVSyncMode();
|
||||||
bool ShouldAllowPresentThrottle();
|
bool ShouldAllowPresentThrottle();
|
||||||
|
|
||||||
|
/// Retrieves timing information for frame presentation on the GPU thread.
|
||||||
|
/// Returns false if this frame should not be presented or the command buffer flushed.
|
||||||
|
bool GetFramePresentationParameters(GPUBackendFramePresentationParameters* frame);
|
||||||
|
|
||||||
/// Call when host display size changes.
|
/// Call when host display size changes.
|
||||||
void DisplayWindowResized();
|
void DisplayWindowResized();
|
||||||
|
|
||||||
|
@ -65,6 +75,7 @@ void IdlePollUpdate();
|
||||||
/// Task threads, asynchronous work which will block system shutdown.
|
/// Task threads, asynchronous work which will block system shutdown.
|
||||||
void QueueTaskOnThread(std::function<void()> task);
|
void QueueTaskOnThread(std::function<void()> task);
|
||||||
void RemoveSelfFromTaskThreads();
|
void RemoveSelfFromTaskThreads();
|
||||||
|
void JoinTaskThreads();
|
||||||
|
|
||||||
} // namespace System
|
} // namespace System
|
||||||
|
|
||||||
|
@ -91,11 +102,8 @@ void OnSystemPaused();
|
||||||
/// Called when the VM is resumed after being paused.
|
/// Called when the VM is resumed after being paused.
|
||||||
void OnSystemResumed();
|
void OnSystemResumed();
|
||||||
|
|
||||||
/// Called when the pause state changes, or fullscreen UI opens.
|
|
||||||
void OnIdleStateChanged();
|
|
||||||
|
|
||||||
/// Called when performance metrics are updated, approximately once a second.
|
/// 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.
|
/// 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);
|
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);
|
||||||
|
|
|
@ -241,14 +241,14 @@ void DebuggerWindow::onBreakpointListItemChanged(QTreeWidgetItem* item, int colu
|
||||||
|
|
||||||
void DebuggerWindow::onStepIntoActionTriggered()
|
void DebuggerWindow::onStepIntoActionTriggered()
|
||||||
{
|
{
|
||||||
Assert(System::IsPaused());
|
Assert(QtHost::IsSystemPaused());
|
||||||
saveCurrentState();
|
saveCurrentState();
|
||||||
g_emu_thread->singleStepCPU();
|
g_emu_thread->singleStepCPU();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebuggerWindow::onStepOverActionTriggered()
|
void DebuggerWindow::onStepOverActionTriggered()
|
||||||
{
|
{
|
||||||
Assert(System::IsPaused());
|
Assert(QtHost::IsSystemPaused());
|
||||||
if (!CPU::AddStepOverBreakpoint())
|
if (!CPU::AddStepOverBreakpoint())
|
||||||
{
|
{
|
||||||
onStepIntoActionTriggered();
|
onStepIntoActionTriggered();
|
||||||
|
@ -262,7 +262,7 @@ void DebuggerWindow::onStepOverActionTriggered()
|
||||||
|
|
||||||
void DebuggerWindow::onStepOutActionTriggered()
|
void DebuggerWindow::onStepOutActionTriggered()
|
||||||
{
|
{
|
||||||
Assert(System::IsPaused());
|
Assert(QtHost::IsSystemPaused());
|
||||||
if (!CPU::AddStepOutBreakpoint())
|
if (!CPU::AddStepOutBreakpoint())
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?"));
|
QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?"));
|
||||||
|
|
|
@ -293,6 +293,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
||||||
// Debugging Tab
|
// Debugging Tab
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
|
||||||
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.maxQueuedFrames, "GPU", "MaxQueuedFrames",
|
||||||
|
Settings::DEFAULT_GPU_MAX_QUEUED_FRAMES);
|
||||||
|
connect(m_ui.gpuThread, &QCheckBox::checkStateChanged, this, &GraphicsSettingsWidget::onGPUThreadChanged);
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToEnumSetting(
|
SettingWidgetBinder::BindWidgetToEnumSetting(
|
||||||
sif, m_ui.gpuDumpCompressionMode, "GPU", "DumpCompressionMode", &Settings::ParseGPUDumpCompressionMode,
|
sif, m_ui.gpuDumpCompressionMode, "GPU", "DumpCompressionMode", &Settings::ParseGPUDumpCompressionMode,
|
||||||
|
@ -325,6 +328,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
||||||
onMediaCaptureVideoEnabledChanged();
|
onMediaCaptureVideoEnabledChanged();
|
||||||
onEnableTextureCacheChanged();
|
onEnableTextureCacheChanged();
|
||||||
onEnableAnyTextureReplacementsChanged();
|
onEnableAnyTextureReplacementsChanged();
|
||||||
|
onGPUThreadChanged();
|
||||||
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
|
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
|
||||||
|
|
||||||
// Rendering Tab
|
// Rendering Tab
|
||||||
|
@ -610,8 +614,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
||||||
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
|
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
|
||||||
"replacement or an overlay."));
|
"replacement or an overlay."));
|
||||||
dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
|
dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
|
||||||
tr("Uses a second thread for drawing graphics. Currently only available for the software "
|
tr("Uses a second thread for drawing graphics. Provides a significant speed improvement "
|
||||||
"renderer, but can provide a significant speed improvement, and is safe to use."));
|
"particularly with the software renderer, and is safe to use."));
|
||||||
|
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(
|
||||||
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
|
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
|
||||||
|
@ -819,8 +823,6 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
|
||||||
m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11);
|
m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_ui.gpuThread->setEnabled(!is_hardware);
|
|
||||||
|
|
||||||
m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 ||
|
m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 ||
|
||||||
render_api == RenderAPI::Vulkan);
|
render_api == RenderAPI::Vulkan);
|
||||||
m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan);
|
m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan);
|
||||||
|
@ -1181,6 +1183,13 @@ void GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged()
|
||||||
m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled);
|
m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphicsSettingsWidget::onGPUThreadChanged()
|
||||||
|
{
|
||||||
|
const bool enabled = m_dialog->getEffectiveBoolValue("GPU", "UseThread", true);
|
||||||
|
m_ui.maxQueuedFrames->setEnabled(enabled);
|
||||||
|
m_ui.maxQueuedFramesLabel->setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
void GraphicsSettingsWidget::onTextureReplacementOptionsClicked()
|
void GraphicsSettingsWidget::onTextureReplacementOptionsClicked()
|
||||||
{
|
{
|
||||||
QDialog dlg(QtUtils::GetRootWidget(this));
|
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||||
|
|
|
@ -44,6 +44,8 @@ private Q_SLOTS:
|
||||||
void onEnableAnyTextureReplacementsChanged();
|
void onEnableAnyTextureReplacementsChanged();
|
||||||
void onTextureReplacementOptionsClicked();
|
void onTextureReplacementOptionsClicked();
|
||||||
|
|
||||||
|
void onGPUThreadChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr int TAB_INDEX_RENDERING = 0;
|
static constexpr int TAB_INDEX_RENDERING = 0;
|
||||||
static constexpr int TAB_INDEX_ADVANCED = 1;
|
static constexpr int TAB_INDEX_ADVANCED = 1;
|
||||||
|
|
|
@ -1286,11 +1286,29 @@
|
||||||
<item row="1" column="0" colspan="2">
|
<item row="1" column="0" colspan="2">
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QCheckBox" name="gpuThread">
|
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0,0">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Threaded Rendering</string>
|
<widget class="QCheckBox" name="gpuThread">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>Threaded Rendering</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="maxQueuedFramesLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max Queued Frames:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="maxQueuedFrames">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -85,6 +85,7 @@ static bool s_use_central_widget = false;
|
||||||
// UI thread VM validity.
|
// UI thread VM validity.
|
||||||
static bool s_system_valid = false;
|
static bool s_system_valid = false;
|
||||||
static bool s_system_paused = false;
|
static bool s_system_paused = false;
|
||||||
|
static bool s_fullscreen_ui_started = false;
|
||||||
static std::atomic_uint32_t s_system_locked{false};
|
static std::atomic_uint32_t s_system_locked{false};
|
||||||
static QString s_current_game_title;
|
static QString s_current_game_title;
|
||||||
static QString s_current_game_serial;
|
static QString s_current_game_serial;
|
||||||
|
@ -764,7 +765,7 @@ void MainWindow::recreate()
|
||||||
{
|
{
|
||||||
g_emu_thread->setSurfaceless(false);
|
g_emu_thread->setSurfaceless(false);
|
||||||
g_main_window->updateEmulationActions(false, System::IsValid(), Achievements::IsHardcoreModeActive());
|
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())
|
if (controller_settings_window_pos.has_value())
|
||||||
|
@ -1258,8 +1259,9 @@ void MainWindow::onStartFullscreenUITriggered()
|
||||||
g_emu_thread->startFullscreenUI();
|
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.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"));
|
m_ui.actionStartFullscreenUI2->setText(running ? tr("Exit Big Picture") : tr("Big Picture"));
|
||||||
}
|
}
|
||||||
|
@ -2046,7 +2048,7 @@ void MainWindow::connectSignals()
|
||||||
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
|
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
|
||||||
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
|
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
|
||||||
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);
|
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::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
|
||||||
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
|
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
|
||||||
&MainWindow::onAchievementsChallengeModeChanged);
|
&MainWindow::onAchievementsChallengeModeChanged);
|
||||||
|
@ -2503,7 +2505,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
|
// 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
|
// 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.
|
// 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);
|
updateWindowState(true);
|
||||||
|
|
||||||
// Now we can actually shut down the VM.
|
// Now we can actually shut down the VM.
|
||||||
|
|
|
@ -168,7 +168,7 @@ private Q_SLOTS:
|
||||||
void onCheatsActionTriggered();
|
void onCheatsActionTriggered();
|
||||||
void onCheatsMenuAboutToShow();
|
void onCheatsMenuAboutToShow();
|
||||||
void onStartFullscreenUITriggered();
|
void onStartFullscreenUITriggered();
|
||||||
void onFullscreenUIStateChange(bool running);
|
void onFullscreenUIStartedOrStopped(bool running);
|
||||||
void onRemoveDiscActionTriggered();
|
void onRemoveDiscActionTriggered();
|
||||||
void onScanForNewGamesTriggered();
|
void onScanForNewGamesTriggered();
|
||||||
void onViewToolbarActionToggled(bool checked);
|
void onViewToolbarActionToggled(bool checked);
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
#include "core/game_list.h"
|
#include "core/game_list.h"
|
||||||
#include "core/gdb_server.h"
|
#include "core/gdb_server.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu.h"
|
||||||
|
#include "core/gpu_backend.h"
|
||||||
#include "core/gpu_hw_texture_cache.h"
|
#include "core/gpu_hw_texture_cache.h"
|
||||||
|
#include "core/gpu_thread.h"
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
#include "core/imgui_overlays.h"
|
#include "core/imgui_overlays.h"
|
||||||
#include "core/memory_card.h"
|
#include "core/memory_card.h"
|
||||||
|
@ -223,7 +225,6 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
|
||||||
INISettingsInterface* ini = static_cast<INISettingsInterface*>(sif);
|
INISettingsInterface* ini = static_cast<INISettingsInterface*>(sif);
|
||||||
Error error;
|
Error error;
|
||||||
|
|
||||||
|
|
||||||
// if there's no keys, just toss the whole thing out
|
// if there's no keys, just toss the whole thing out
|
||||||
if (delete_if_empty && ini->IsEmpty())
|
if (delete_if_empty && ini->IsEmpty())
|
||||||
{
|
{
|
||||||
|
@ -576,13 +577,8 @@ void Host::LoadSettings(const SettingsInterface& si, std::unique_lock<std::mutex
|
||||||
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
|
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
|
||||||
{
|
{
|
||||||
if (g_main_window)
|
if (g_main_window)
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
|
||||||
|
|
||||||
if (System::IsValid())
|
|
||||||
updatePerformanceCounters();
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't mess with fullscreen while locked
|
// don't mess with fullscreen while locked
|
||||||
if (!QtHost::IsSystemLocked())
|
if (!QtHost::IsSystemLocked())
|
||||||
{
|
{
|
||||||
|
@ -591,7 +587,7 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
|
||||||
{
|
{
|
||||||
m_is_rendering_to_main = render_to_main;
|
m_is_rendering_to_main = render_to_main;
|
||||||
if (g_gpu_device)
|
if (g_gpu_device)
|
||||||
Host::UpdateDisplayWindow(m_is_fullscreen);
|
GPUThread::UpdateDisplayWindow(m_is_fullscreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -730,33 +726,24 @@ void EmuThread::startFullscreenUI()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System::IsValid())
|
if (System::IsValid() || m_is_fullscreen_ui_started)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// we want settings loaded so we choose the correct renderer
|
// we want settings loaded so we choose the correct renderer
|
||||||
// this also sorts out input sources.
|
// this also sorts out input sources.
|
||||||
System::LoadSettings(false);
|
System::LoadSettings(false);
|
||||||
m_is_rendering_to_main = shouldRenderToMain();
|
m_is_rendering_to_main = shouldRenderToMain();
|
||||||
m_run_fullscreen_ui = true;
|
|
||||||
|
|
||||||
// borrow the game start fullscreen flag
|
// borrow the game start fullscreen flag
|
||||||
const bool start_fullscreen =
|
const bool start_fullscreen =
|
||||||
(s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
|
(s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), start_fullscreen, &error) ||
|
if (!GPUThread::StartFullscreenUI(start_fullscreen, &error))
|
||||||
!FullscreenUI::Initialize())
|
|
||||||
{
|
{
|
||||||
Host::ReportErrorAsync("Error", error.GetDescription());
|
Host::ReportErrorAsync("Error", error.GetDescription());
|
||||||
m_run_fullscreen_ui = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit fullscreenUIStateChange(true);
|
|
||||||
|
|
||||||
// poll more frequently so we don't lose events
|
|
||||||
stopBackgroundControllerPollTimer();
|
|
||||||
startBackgroundControllerPollTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::stopFullscreenUI()
|
void EmuThread::stopFullscreenUI()
|
||||||
|
@ -771,18 +758,8 @@ void EmuThread::stopFullscreenUI()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullscreen(false, true);
|
if (m_is_fullscreen_ui_started)
|
||||||
|
GPUThread::StopFullscreenUI();
|
||||||
if (m_run_fullscreen_ui)
|
|
||||||
{
|
|
||||||
m_run_fullscreen_ui = false;
|
|
||||||
emit fullscreenUIStateChange(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_gpu_device)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Host::ReleaseGPUDevice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
|
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
|
||||||
|
@ -889,7 +866,7 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
|
||||||
|
|
||||||
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
|
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
|
||||||
{
|
{
|
||||||
Host::ResizeDisplayWindow(width, height, scale);
|
GPUThread::ResizeDisplayWindow(width, height, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::redrawDisplayWindow()
|
void EmuThread::redrawDisplayWindow()
|
||||||
|
@ -900,10 +877,10 @@ void EmuThread::redrawDisplayWindow()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_gpu_device || System::IsShutdown())
|
if (System::IsShutdown())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
System::InvalidateDisplay();
|
GPUThread::PresentCurrentFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::toggleFullscreen()
|
void EmuThread::toggleFullscreen()
|
||||||
|
@ -931,7 +908,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||||
|
|
||||||
m_is_fullscreen = fullscreen;
|
m_is_fullscreen = fullscreen;
|
||||||
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
|
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
|
||||||
Host::UpdateDisplayWindow(fullscreen);
|
GPUThread::UpdateDisplayWindow(fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Host::IsFullscreen()
|
bool Host::IsFullscreen()
|
||||||
|
@ -960,7 +937,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_is_surfaceless = surfaceless;
|
m_is_surfaceless = surfaceless;
|
||||||
Host::UpdateDisplayWindow(false);
|
GPUThread::UpdateDisplayWindow(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::requestDisplaySize(float scale)
|
void EmuThread::requestDisplaySize(float scale)
|
||||||
|
@ -1017,6 +994,7 @@ void Host::OnSystemStarting()
|
||||||
void Host::OnSystemStarted()
|
void Host::OnSystemStarted()
|
||||||
{
|
{
|
||||||
g_emu_thread->stopBackgroundControllerPollTimer();
|
g_emu_thread->stopBackgroundControllerPollTimer();
|
||||||
|
g_emu_thread->wakeThread();
|
||||||
|
|
||||||
emit g_emu_thread->systemStarted();
|
emit g_emu_thread->systemStarted();
|
||||||
}
|
}
|
||||||
|
@ -1034,6 +1012,7 @@ void Host::OnSystemResumed()
|
||||||
g_emu_thread->setSurfaceless(false);
|
g_emu_thread->setSurfaceless(false);
|
||||||
|
|
||||||
emit g_emu_thread->systemResumed();
|
emit g_emu_thread->systemResumed();
|
||||||
|
g_emu_thread->wakeThread();
|
||||||
|
|
||||||
g_emu_thread->stopBackgroundControllerPollTimer();
|
g_emu_thread->stopBackgroundControllerPollTimer();
|
||||||
}
|
}
|
||||||
|
@ -1045,9 +1024,14 @@ void Host::OnSystemDestroyed()
|
||||||
emit g_emu_thread->systemDestroyed();
|
emit g_emu_thread->systemDestroyed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::OnIdleStateChanged()
|
void Host::OnFullscreenUIStartedOrStopped(bool started)
|
||||||
{
|
{
|
||||||
g_emu_thread->wakeThread();
|
g_emu_thread->setFullscreenUIStarted(started);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::OnGPUThreadRunIdleChanged(bool is_active)
|
||||||
|
{
|
||||||
|
g_emu_thread->setGPUThreadRunIdle(is_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::reloadInputSources()
|
void EmuThread::reloadInputSources()
|
||||||
|
@ -1291,7 +1275,12 @@ void EmuThread::reloadPostProcessingShaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System::IsValid())
|
if (System::IsValid())
|
||||||
PostProcessing::ReloadShaders();
|
{
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
if (GPUThread::HasGPUBackend())
|
||||||
|
PostProcessing::ReloadShaders();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::updatePostProcessingSettings()
|
void EmuThread::updatePostProcessingSettings()
|
||||||
|
@ -1303,7 +1292,12 @@ void EmuThread::updatePostProcessingSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System::IsValid())
|
if (System::IsValid())
|
||||||
PostProcessing::UpdateSettings();
|
{
|
||||||
|
GPUThread::RunOnThread([]() {
|
||||||
|
if (GPUThread::HasGPUBackend())
|
||||||
|
PostProcessing::UpdateSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::clearInputBindStateFromSource(InputBindingKey key)
|
void EmuThread::clearInputBindStateFromSource(InputBindingKey key)
|
||||||
|
@ -1326,7 +1320,7 @@ void EmuThread::reloadTextureReplacements()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System::IsValid())
|
if (System::IsValid())
|
||||||
GPUTextureCache::ReloadTextureReplacements(true);
|
GPUThread::RunOnThread([]() { GPUTextureCache::ReloadTextureReplacements(true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::captureGPUFrameDump()
|
void EmuThread::captureGPUFrameDump()
|
||||||
|
@ -1679,7 +1673,8 @@ void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32*
|
||||||
*height = size.height();
|
*height = size.height();
|
||||||
|
|
||||||
// eat all pending events, to make sure we're not going to write input events back to a dead pointer
|
// 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,
|
void EmuThread::queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
|
||||||
|
@ -1699,10 +1694,12 @@ void EmuThread::processAuxiliaryRenderWindowInputEvent(void* userdata, quint32 e
|
||||||
quint32 param3)
|
quint32 param3)
|
||||||
{
|
{
|
||||||
DebugAssert(isCurrentThread());
|
DebugAssert(isCurrentThread());
|
||||||
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
|
GPUThread::RunOnThread([userdata, event, param1, param2, param3]() {
|
||||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
|
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
|
||||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
|
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
|
||||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
|
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
|
||||||
|
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::doBackgroundControllerPoll()
|
void EmuThread::doBackgroundControllerPoll()
|
||||||
|
@ -1731,7 +1728,7 @@ void EmuThread::startBackgroundControllerPollTimer()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
u32 poll_interval = BACKGROUND_CONTROLLER_POLLING_INTERVAL;
|
u32 poll_interval = BACKGROUND_CONTROLLER_POLLING_INTERVAL;
|
||||||
if (FullscreenUI::IsInitialized())
|
if (m_gpu_thread_run_idle)
|
||||||
poll_interval = FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL;
|
poll_interval = FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL;
|
||||||
if (GDBServer::HasAnyClients())
|
if (GDBServer::HasAnyClients())
|
||||||
poll_interval = GDB_SERVER_POLLING_INTERVAL;
|
poll_interval = GDB_SERVER_POLLING_INTERVAL;
|
||||||
|
@ -1747,6 +1744,37 @@ void EmuThread::stopBackgroundControllerPollTimer()
|
||||||
m_background_controller_polling_timer->stop();
|
m_background_controller_polling_timer->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuThread::setGPUThreadRunIdle(bool active)
|
||||||
|
{
|
||||||
|
if (!isCurrentThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "setGPUThreadRunIdle", Qt::QueuedConnection, Q_ARG(bool, active));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_gpu_thread_run_idle = active;
|
||||||
|
|
||||||
|
// break out of the event loop if we're not executing a system
|
||||||
|
if (active && !g_settings.gpu_use_thread && !System::IsRunning())
|
||||||
|
m_event_loop->quit();
|
||||||
|
|
||||||
|
// 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()
|
void EmuThread::start()
|
||||||
{
|
{
|
||||||
AssertMsg(!g_emu_thread, "Emu thread does not exist");
|
AssertMsg(!g_emu_thread, "Emu thread does not exist");
|
||||||
|
@ -1776,8 +1804,6 @@ void EmuThread::stopInThread()
|
||||||
|
|
||||||
void EmuThread::run()
|
void EmuThread::run()
|
||||||
{
|
{
|
||||||
Threading::SetNameOfCurrentThread("CPU Thread");
|
|
||||||
|
|
||||||
m_event_loop = new QEventLoop();
|
m_event_loop = new QEventLoop();
|
||||||
m_started_semaphore.release();
|
m_started_semaphore.release();
|
||||||
|
|
||||||
|
@ -1796,6 +1822,9 @@ void EmuThread::run()
|
||||||
createBackgroundControllerPollTimer();
|
createBackgroundControllerPollTimer();
|
||||||
startBackgroundControllerPollTimer();
|
startBackgroundControllerPollTimer();
|
||||||
|
|
||||||
|
// kick off GPU thread
|
||||||
|
Threading::Thread gpu_thread(&EmuThread::gpuThreadEntryPoint);
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
while (!m_shutdown_flag)
|
while (!m_shutdown_flag)
|
||||||
{
|
{
|
||||||
|
@ -1803,24 +1832,17 @@ void EmuThread::run()
|
||||||
{
|
{
|
||||||
System::Execute();
|
System::Execute();
|
||||||
}
|
}
|
||||||
|
else if (!GPUThread::IsUsingThread() && GPUThread::IsRunningIdle())
|
||||||
|
{
|
||||||
|
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
|
||||||
|
|
||||||
|
// have to double-check the condition after processing events, because the events could shut us down
|
||||||
|
if (!GPUThread::IsUsingThread() && GPUThread::IsRunningIdle())
|
||||||
|
GPUThread::Internal::DoRunIdle();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// we want to keep rendering the UI when paused and fullscreen UI is enabled
|
m_event_loop->exec();
|
||||||
if (!FullscreenUI::HasActiveWindow() && !System::IsRunning())
|
|
||||||
{
|
|
||||||
// wait until we have a system before running
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1828,13 +1850,25 @@ void EmuThread::run()
|
||||||
System::ShutdownSystem(false);
|
System::ShutdownSystem(false);
|
||||||
|
|
||||||
destroyBackgroundControllerPollTimer();
|
destroyBackgroundControllerPollTimer();
|
||||||
|
|
||||||
|
// tell GPU thread to exit
|
||||||
|
GPUThread::Internal::RequestShutdown();
|
||||||
|
gpu_thread.Join();
|
||||||
|
|
||||||
|
// and tidy up everything left
|
||||||
System::CPUThreadShutdown();
|
System::CPUThreadShutdown();
|
||||||
|
|
||||||
// move back to UI thread
|
// move back to UI thread
|
||||||
moveToThread(m_ui_thread);
|
moveToThread(m_ui_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::FrameDone()
|
void EmuThread::gpuThreadEntryPoint()
|
||||||
|
{
|
||||||
|
Threading::SetNameOfCurrentThread("GPU Thread");
|
||||||
|
GPUThread::Internal::GPUThreadEntryPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1949,7 +1983,7 @@ void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view
|
||||||
{
|
{
|
||||||
emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name));
|
emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name));
|
||||||
|
|
||||||
if (System::IsValid() || g_emu_thread->isRunningFullscreenUI())
|
if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
|
||||||
{
|
{
|
||||||
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
|
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
|
||||||
fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier),
|
fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier),
|
||||||
|
@ -1975,7 +2009,7 @@ void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view ident
|
||||||
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, std::move(message),
|
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, std::move(message),
|
||||||
Host::OSD_WARNING_DURATION);
|
Host::OSD_WARNING_DURATION);
|
||||||
}
|
}
|
||||||
else if (System::IsValid() || g_emu_thread->isRunningFullscreenUI())
|
else if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
|
||||||
{
|
{
|
||||||
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
|
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
|
||||||
fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier),
|
fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier),
|
||||||
|
@ -2037,16 +2071,16 @@ void Host::ReleaseRenderWindow()
|
||||||
g_emu_thread->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 RenderAPI render_api = g_gpu_device->GetRenderAPI();
|
||||||
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer();
|
const bool hardware_renderer = GPUBackend::IsUsingHardwareBackend();
|
||||||
u32 render_width = 0;
|
u32 render_width = 0;
|
||||||
u32 render_height = 0;
|
u32 render_height = 0;
|
||||||
|
|
||||||
if (g_gpu)
|
if (gpu_backend)
|
||||||
{
|
{
|
||||||
const u32 render_scale = g_gpu->GetResolutionScale();
|
const u32 render_scale = gpu_backend->GetResolutionScale();
|
||||||
std::tie(render_width, render_height) = g_gpu->GetFullDisplayResolution();
|
std::tie(render_width, render_height) = g_gpu->GetFullDisplayResolution();
|
||||||
render_width *= render_scale;
|
render_width *= render_scale;
|
||||||
render_height *= render_scale;
|
render_height *= render_scale;
|
||||||
|
@ -2110,9 +2144,9 @@ void EmuThread::resetPerformanceCounters()
|
||||||
Q_ARG(const QString&, blank));
|
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)
|
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name)
|
||||||
|
@ -2209,8 +2243,8 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
|
||||||
|
|
||||||
EmuThread::SystemLock EmuThread::pauseAndLockSystem()
|
EmuThread::SystemLock EmuThread::pauseAndLockSystem()
|
||||||
{
|
{
|
||||||
const bool was_fullscreen = System::IsValid() && isFullscreen();
|
const bool was_fullscreen = QtHost::IsSystemValid() && isFullscreen();
|
||||||
const bool was_paused = System::IsPaused();
|
const bool was_paused = QtHost::IsSystemPaused();
|
||||||
|
|
||||||
// We use surfaceless rather than switching out of fullscreen, because
|
// We use surfaceless rather than switching out of fullscreen, because
|
||||||
// we're paused, so we're not going to be rendering anyway.
|
// we're paused, so we're not going to be rendering anyway.
|
||||||
|
|
|
@ -44,6 +44,8 @@ class INISettingsInterface;
|
||||||
enum class RenderAPI : u8;
|
enum class RenderAPI : u8;
|
||||||
class GPUDevice;
|
class GPUDevice;
|
||||||
|
|
||||||
|
class GPUBackend;
|
||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
class DisplayWidget;
|
class DisplayWidget;
|
||||||
|
|
||||||
|
@ -93,7 +95,6 @@ public:
|
||||||
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
|
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
|
||||||
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
||||||
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
|
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,
|
std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
|
||||||
Error* error);
|
Error* error);
|
||||||
|
@ -102,6 +103,7 @@ public:
|
||||||
|
|
||||||
void startBackgroundControllerPollTimer();
|
void startBackgroundControllerPollTimer();
|
||||||
void stopBackgroundControllerPollTimer();
|
void stopBackgroundControllerPollTimer();
|
||||||
|
void setFullscreenUIStarted(bool started);
|
||||||
void wakeThread();
|
void wakeThread();
|
||||||
|
|
||||||
bool shouldRenderToMain() const;
|
bool shouldRenderToMain() const;
|
||||||
|
@ -109,7 +111,7 @@ public:
|
||||||
|
|
||||||
void bootOrLoadState(std::string path);
|
void bootOrLoadState(std::string path);
|
||||||
|
|
||||||
void updatePerformanceCounters();
|
void updatePerformanceCounters(const GPUBackend* gpu_backend);
|
||||||
void resetPerformanceCounters();
|
void resetPerformanceCounters();
|
||||||
|
|
||||||
/// Locks the system by pausing it, while a popup dialog is displayed.
|
/// Locks the system by pausing it, while a popup dialog is displayed.
|
||||||
|
@ -147,7 +149,7 @@ Q_SIGNALS:
|
||||||
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
|
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
|
||||||
void inputProfileLoaded();
|
void inputProfileLoaded();
|
||||||
void mouseModeRequested(bool relative, bool hide_cursor);
|
void mouseModeRequested(bool relative, bool hide_cursor);
|
||||||
void fullscreenUIStateChange(bool running);
|
void fullscreenUIStartedOrStopped(bool running);
|
||||||
void achievementsLoginRequested(Achievements::LoginRequestReason reason);
|
void achievementsLoginRequested(Achievements::LoginRequestReason reason);
|
||||||
void achievementsRefreshed(quint32 id, const QString& game_info_string);
|
void achievementsRefreshed(quint32 id, const QString& game_info_string);
|
||||||
void achievementsChallengeModeChanged(bool enabled);
|
void achievementsChallengeModeChanged(bool enabled);
|
||||||
|
@ -210,6 +212,7 @@ public Q_SLOTS:
|
||||||
void clearInputBindStateFromSource(InputBindingKey key);
|
void clearInputBindStateFromSource(InputBindingKey key);
|
||||||
void reloadTextureReplacements();
|
void reloadTextureReplacements();
|
||||||
void captureGPUFrameDump();
|
void captureGPUFrameDump();
|
||||||
|
void setGPUThreadRunIdle(bool active);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void stopInThread();
|
void stopInThread();
|
||||||
|
@ -227,23 +230,23 @@ protected:
|
||||||
void run() override;
|
void run() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using InputButtonHandler = std::function<void(bool)>;
|
|
||||||
using InputAxisHandler = std::function<void(float)>;
|
|
||||||
|
|
||||||
void createBackgroundControllerPollTimer();
|
void createBackgroundControllerPollTimer();
|
||||||
void destroyBackgroundControllerPollTimer();
|
void destroyBackgroundControllerPollTimer();
|
||||||
void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
|
void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
|
||||||
std::function<void(bool)> callback) const;
|
std::function<void(bool)> callback) const;
|
||||||
|
|
||||||
|
static void gpuThreadEntryPoint();
|
||||||
|
|
||||||
QThread* m_ui_thread;
|
QThread* m_ui_thread;
|
||||||
QSemaphore m_started_semaphore;
|
QSemaphore m_started_semaphore;
|
||||||
QEventLoop* m_event_loop = nullptr;
|
QEventLoop* m_event_loop = nullptr;
|
||||||
QTimer* m_background_controller_polling_timer = nullptr;
|
QTimer* m_background_controller_polling_timer = nullptr;
|
||||||
|
|
||||||
bool m_shutdown_flag = false;
|
bool m_shutdown_flag = false;
|
||||||
bool m_run_fullscreen_ui = false;
|
|
||||||
bool m_is_rendering_to_main = false;
|
bool m_is_rendering_to_main = false;
|
||||||
bool m_is_fullscreen = false;
|
bool m_is_fullscreen = false;
|
||||||
|
bool m_is_fullscreen_ui_started = false;
|
||||||
|
bool m_gpu_thread_run_idle = false;
|
||||||
bool m_is_surfaceless = false;
|
bool m_is_surfaceless = false;
|
||||||
bool m_save_state_on_shutdown = false;
|
bool m_save_state_on_shutdown = false;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
#include "core/controller.h"
|
#include "core/controller.h"
|
||||||
#include "core/fullscreen_ui.h"
|
#include "core/fullscreen_ui.h"
|
||||||
#include "core/game_list.h"
|
#include "core/game_list.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu_backend.h"
|
||||||
|
#include "core/gpu_thread.h"
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
#include "core/system_private.h"
|
#include "core/system_private.h"
|
||||||
|
@ -46,9 +47,11 @@ static void HookSignals();
|
||||||
static bool SetFolders();
|
static bool SetFolders();
|
||||||
static bool SetNewDataRoot(const std::string& filename);
|
static bool SetNewDataRoot(const std::string& filename);
|
||||||
static std::string GetFrameDumpFilename(u32 frame);
|
static std::string GetFrameDumpFilename(u32 frame);
|
||||||
|
static void GPUThreadEntryPoint();
|
||||||
} // namespace RegTestHost
|
} // namespace RegTestHost
|
||||||
|
|
||||||
static std::unique_ptr<MemorySettingsInterface> s_base_settings_interface;
|
static std::unique_ptr<MemorySettingsInterface> s_base_settings_interface;
|
||||||
|
static Threading::Thread s_gpu_thread;
|
||||||
|
|
||||||
static u32 s_frames_to_run = 60 * 60;
|
static u32 s_frames_to_run = 60 * 60;
|
||||||
static u32 s_frames_remaining = 0;
|
static u32 s_frames_remaining = 0;
|
||||||
|
@ -281,12 +284,17 @@ void Host::OnSystemResumed()
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::OnIdleStateChanged()
|
void Host::OnGPUThreadRunIdleChanged(bool is_active)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::OnPerformanceCountersUpdated()
|
void Host::OnFullscreenUIStartedOrStopped(bool started)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -375,14 +383,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_number % s_frame_dump_interval) == 0))
|
||||||
if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame % s_frame_dump_interval) == 0))
|
gpu_backend->WriteDisplayTextureToFile(RegTestHost::GetFrameDumpFilename(frame_number));
|
||||||
{
|
|
||||||
std::string dump_filename(RegTestHost::GetFrameDumpFilename(frame));
|
|
||||||
g_gpu->WriteDisplayTextureToFile(std::move(dump_filename));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::OpenURL(std::string_view url)
|
void Host::OpenURL(std::string_view url)
|
||||||
|
@ -508,6 +512,12 @@ void RegTestHost::HookSignals()
|
||||||
std::signal(SIGTERM, SignalHandler);
|
std::signal(SIGTERM, SignalHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RegTestHost::GPUThreadEntryPoint()
|
||||||
|
{
|
||||||
|
Threading::SetNameOfCurrentThread("CPU Thread");
|
||||||
|
GPUThread::Internal::GPUThreadEntryPoint();
|
||||||
|
}
|
||||||
|
|
||||||
void RegTestHost::InitializeEarlyConsole()
|
void RegTestHost::InitializeEarlyConsole()
|
||||||
{
|
{
|
||||||
const bool was_console_enabled = Log::IsConsoleOutputEnabled();
|
const bool was_console_enabled = Log::IsConsoleOutputEnabled();
|
||||||
|
@ -773,6 +783,7 @@ int main(int argc, char* argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
RegTestHost::HookSignals();
|
RegTestHost::HookSignals();
|
||||||
|
s_gpu_thread.Start(&RegTestHost::GPUThreadEntryPoint);
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
int result = -1;
|
int result = -1;
|
||||||
|
@ -813,6 +824,12 @@ int main(int argc, char* argv[])
|
||||||
result = 0;
|
result = 0;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
if (s_gpu_thread.Joinable())
|
||||||
|
{
|
||||||
|
GPUThread::Internal::RequestShutdown();
|
||||||
|
s_gpu_thread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
System::CPUThreadShutdown();
|
System::CPUThreadShutdown();
|
||||||
System::ProcessShutdown();
|
System::ProcessShutdown();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -385,6 +385,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)
|
bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
|
||||||
{
|
{
|
||||||
return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) &&
|
return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) &&
|
||||||
|
|
|
@ -657,6 +657,9 @@ public:
|
||||||
/// Returns a string representing the specified language.
|
/// Returns a string representing the specified language.
|
||||||
static const char* ShaderLanguageToString(GPUShaderLanguage 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.
|
/// Returns a new device for the specified API.
|
||||||
static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api);
|
static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api);
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@ static void DrawChoiceDialog();
|
||||||
static void DrawInputDialog();
|
static void DrawInputDialog();
|
||||||
static void DrawMessageDialog();
|
static void DrawMessageDialog();
|
||||||
static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing);
|
static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing);
|
||||||
|
static void DrawLoadingScreen(std::string_view image, std::string_view message, s32 progress_min, s32 progress_max,
|
||||||
|
s32 progress_value, bool is_persistent);
|
||||||
static void DrawNotifications(ImVec2& position, float spacing);
|
static void DrawNotifications(ImVec2& position, float spacing);
|
||||||
static void DrawToast();
|
static void DrawToast();
|
||||||
static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb,
|
static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb,
|
||||||
|
@ -171,6 +173,13 @@ struct ALIGN_TO_CACHE_LINE UIState
|
||||||
|
|
||||||
std::vector<BackgroundProgressDialogData> background_progress_dialogs;
|
std::vector<BackgroundProgressDialogData> background_progress_dialogs;
|
||||||
std::mutex background_progress_lock;
|
std::mutex background_progress_lock;
|
||||||
|
|
||||||
|
std::string loading_screen_image;
|
||||||
|
std::string loading_screen_message;
|
||||||
|
s32 loading_screen_min = 0;
|
||||||
|
s32 loading_screen_max = 0;
|
||||||
|
s32 loading_screen_value = 0;
|
||||||
|
bool loading_screen_open = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -2916,6 +2925,145 @@ void ImGuiFullscreen::DrawBackgroundProgressDialogs(ImVec2& position, float spac
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::PopStyleColor(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImGuiFullscreen::RenderLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
|
||||||
|
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
|
||||||
|
{
|
||||||
|
if (progress_min < progress_max)
|
||||||
|
INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
|
||||||
|
|
||||||
|
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// eat the last imgui frame, it might've been partially rendered by the caller.
|
||||||
|
ImGui::EndFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
DrawLoadingScreen(image, message, progress_min, progress_max, progress_value, false);
|
||||||
|
|
||||||
|
ImGui::EndFrame();
|
||||||
|
|
||||||
|
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
|
||||||
|
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
|
||||||
|
{
|
||||||
|
g_gpu_device->RenderImGui(swap_chain);
|
||||||
|
g_gpu_device->EndPresent(swap_chain, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::NewFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiFullscreen::OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message,
|
||||||
|
s32 progress_min /*= -1*/, s32 progress_max /*= -1*/,
|
||||||
|
s32 progress_value /*= -1*/)
|
||||||
|
{
|
||||||
|
if (s_state.loading_screen_image != image)
|
||||||
|
s_state.loading_screen_image = image;
|
||||||
|
if (s_state.loading_screen_message != message)
|
||||||
|
s_state.loading_screen_message = message;
|
||||||
|
s_state.loading_screen_min = progress_min;
|
||||||
|
s_state.loading_screen_max = progress_max;
|
||||||
|
s_state.loading_screen_value = progress_value;
|
||||||
|
s_state.loading_screen_open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiFullscreen::IsLoadingScreenOpen()
|
||||||
|
{
|
||||||
|
return s_state.loading_screen_open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiFullscreen::RenderLoadingScreen()
|
||||||
|
{
|
||||||
|
if (!s_state.loading_screen_open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawLoadingScreen(s_state.loading_screen_image, s_state.loading_screen_message, s_state.loading_screen_min,
|
||||||
|
s_state.loading_screen_max, s_state.loading_screen_value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiFullscreen::CloseLoadingScreen()
|
||||||
|
{
|
||||||
|
s_state.loading_screen_image = {};
|
||||||
|
s_state.loading_screen_message = {};
|
||||||
|
s_state.loading_screen_min = 0;
|
||||||
|
s_state.loading_screen_max = 0;
|
||||||
|
s_state.loading_screen_value = 0;
|
||||||
|
s_state.loading_screen_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiFullscreen::DrawLoadingScreen(std::string_view image, std::string_view message, s32 progress_min,
|
||||||
|
s32 progress_max, s32 progress_value, bool is_persistent)
|
||||||
|
{
|
||||||
|
const auto& io = ImGui::GetIO();
|
||||||
|
const float scale = ImGuiManager::GetGlobalScale();
|
||||||
|
const float width = (400.0f * scale);
|
||||||
|
const bool has_progress = (progress_min < progress_max);
|
||||||
|
|
||||||
|
const float logo_width = 260.0f * scale;
|
||||||
|
const float logo_height = 260.0f * scale;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(logo_width, logo_height), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) - (50.0f * scale)),
|
||||||
|
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
if (ImGui::Begin("LoadingScreenLogo", nullptr,
|
||||||
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||||
|
ImGuiWindowFlags_NoBackground))
|
||||||
|
{
|
||||||
|
GPUTexture* tex = GetCachedTexture(image);
|
||||||
|
if (tex)
|
||||||
|
ImGui::Image(tex, ImVec2(logo_width, logo_height));
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
const float padding_and_rounding = 18.0f * scale;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, padding_and_rounding);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding_and_rounding, padding_and_rounding));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(width, ((has_progress || is_persistent) ? 90.0f : 55.0f) * scale), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) + (100.0f * scale)),
|
||||||
|
ImGuiCond_Always, ImVec2(0.5f, 0.0f));
|
||||||
|
if (ImGui::Begin("LoadingScreen", nullptr,
|
||||||
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
|
||||||
|
{
|
||||||
|
if (has_progress || is_persistent)
|
||||||
|
{
|
||||||
|
if (!message.empty())
|
||||||
|
ImGui::TextUnformatted(message.data(), message.data() + message.size());
|
||||||
|
|
||||||
|
if (has_progress)
|
||||||
|
{
|
||||||
|
TinyString buf;
|
||||||
|
buf.format("{}/{}", progress_value, progress_max);
|
||||||
|
|
||||||
|
const ImVec2 prog_size = ImGui::CalcTextSize(buf.c_str(), buf.end_ptr());
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(width - padding_and_rounding - prog_size.x);
|
||||||
|
ImGui::TextUnformatted(buf.c_str(), buf.end_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f);
|
||||||
|
|
||||||
|
ImGui::ProgressBar(has_progress ?
|
||||||
|
(static_cast<float>(progress_value) / static_cast<float>(progress_max - progress_min)) :
|
||||||
|
static_cast<float>(-ImGui::GetTime()),
|
||||||
|
ImVec2(-1.0f, 0.0f), "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!message.empty())
|
||||||
|
{
|
||||||
|
const ImVec2 text_size(ImGui::CalcTextSize(message.data(), message.data() + message.size()));
|
||||||
|
ImGui::SetCursorPosX((width - text_size.x) / 2.0f);
|
||||||
|
ImGui::TextUnformatted(message.data(), message.data() + message.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Notifications
|
// Notifications
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -327,6 +327,18 @@ void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32
|
||||||
void CloseBackgroundProgressDialog(const char* str_id);
|
void CloseBackgroundProgressDialog(const char* str_id);
|
||||||
bool IsBackgroundProgressDialogOpen(const char* str_id);
|
bool IsBackgroundProgressDialogOpen(const char* str_id);
|
||||||
|
|
||||||
|
/// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks
|
||||||
|
/// such as compiling shaders when starting up.
|
||||||
|
void RenderLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
|
||||||
|
s32 progress_value = -1);
|
||||||
|
void OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1,
|
||||||
|
s32 progress_max = -1, s32 progress_value = -1);
|
||||||
|
bool IsLoadingScreenOpen();
|
||||||
|
void CloseLoadingScreen();
|
||||||
|
|
||||||
|
/// Renders a previously-configured loading screen.
|
||||||
|
void RenderLoadingScreen();
|
||||||
|
|
||||||
void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path);
|
void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path);
|
||||||
void ClearNotifications();
|
void ClearNotifications();
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
// TODO: Remove me
|
// TODO: Remove me
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
#include "core/host_interface_progress_callback.h"
|
#include "core/fullscreen_ui.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
#include "IconsFontAwesome5.h"
|
#include "IconsFontAwesome5.h"
|
||||||
|
@ -406,7 +406,7 @@ void PostProcessing::Chain::LoadStages()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
HostInterfaceProgressCallback progress;
|
LoadingScreenProgressCallback progress;
|
||||||
progress.SetProgressRange(stage_count);
|
progress.SetProgressRange(stage_count);
|
||||||
|
|
||||||
for (u32 i = 0; i < stage_count; i++)
|
for (u32 i = 0; i < stage_count; i++)
|
||||||
|
@ -476,7 +476,7 @@ void PostProcessing::Chain::UpdateSettings(std::unique_lock<std::mutex>& setting
|
||||||
|
|
||||||
m_stages.resize(stage_count);
|
m_stages.resize(stage_count);
|
||||||
|
|
||||||
HostInterfaceProgressCallback progress;
|
LoadingScreenProgressCallback progress;
|
||||||
progress.SetProgressRange(stage_count);
|
progress.SetProgressRange(stage_count);
|
||||||
|
|
||||||
const GPUTexture::Format prev_format = m_target_format;
|
const GPUTexture::Format prev_format = m_target_format;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "shadergen.h"
|
#include "shadergen.h"
|
||||||
|
|
||||||
// TODO: Remove me
|
// TODO: Remove me
|
||||||
|
#include "core/gpu_thread.h"
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ static std::tuple<std::unique_ptr<reshadefx::codegen>, GPUShaderLanguage> Create
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should have a GPU device and be on the GPU thread.
|
// Should have a GPU device and be on the GPU thread.
|
||||||
Assert(g_gpu_device);
|
Assert(GPUThread::IsOnThread() && g_gpu_device);
|
||||||
|
|
||||||
const bool debug_info = g_gpu_device->IsDebugDevice();
|
const bool debug_info = g_gpu_device->IsDebugDevice();
|
||||||
const RenderAPI rapi = g_gpu_device->GetRenderAPI();
|
const RenderAPI rapi = g_gpu_device->GetRenderAPI();
|
||||||
|
|
|
@ -34,6 +34,8 @@ public:
|
||||||
ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); }
|
ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); }
|
||||||
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
|
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
|
||||||
ALWAYS_INLINE u32 GetVersion() const { return m_version; }
|
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; }
|
ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
|
||||||
ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; }
|
ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue