GPU: Move backend work off CPU thread

This commit is contained in:
Stenzek 2023-12-09 01:28:17 +10:00
parent 724b1a7cc4
commit 69fc53ac8a
No known key found for this signature in database
36 changed files with 5073 additions and 4023 deletions

View File

@ -1,10 +1,17 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>, 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0
#include "threading.h"
#include "assert.h"
#include "log.h"
#include "timer.h"
#include <memory>
#if defined(CPU_ARCH_X86) || defined(CPU_ARCH_X64)
#include <emmintrin.h>
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
@ -14,6 +21,10 @@
#if defined(_WIN32)
#include "windows_headers.h"
#include <process.h>
#if defined(CPU_ARCH_ARM64) && defined(_MSC_VER)
#include <arm64intr.h>
#endif
#else
#include <pthread.h>
#include <unistd.h>
@ -38,6 +49,8 @@
#endif
#endif
Log_SetChannel(Threading);
#ifdef _WIN32
union FileTimeU64Union
{
@ -101,6 +114,138 @@ void Threading::Timeslice()
#endif
}
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
}
// Apple uses a lower tick frequency, so we can't use the dynamic loop below.
#if !defined(_M_ARM64) || defined(__APPLE__) || defined(_WIN32)
static u32 PAUSE_TIME = 0;
static u32 MeasurePauseTime()
{
// GetCPUTicks may have resolution as low as 1us
// One call to MultiPause could take anywhere from 20ns (fast Haswell) to 400ns (slow Skylake)
// We want a measurement of reasonable resolution, but don't want to take too long
// So start at a fairly small number and increase it if it's too fast
for (int testcnt = 64; true; testcnt *= 2)
{
Common::Timer::Value start = Common::Timer::GetCurrentValue();
for (int i = 0; i < testcnt; i++)
{
MultiPause();
}
Common::Timer::Value time = Common::Timer::GetCurrentValue() - start;
if (time > 100)
{
const double nanos = Common::Timer::ConvertValueToNanoseconds(time);
return static_cast<u32>((nanos / testcnt) + 1);
}
}
}
NEVER_INLINE static void UpdatePauseTime()
{
Common::Timer::BusyWait(10000000);
u32 pause = MeasurePauseTime();
// Take a few measurements in case something weird happens during one
// (e.g. OS interrupt)
for (int i = 0; i < 4; i++)
pause = std::min(pause, MeasurePauseTime());
PAUSE_TIME = pause;
VERBOSE_LOG("MultiPause time: {}ns", pause);
}
u32 Threading::ShortSpin()
{
u32 inc = PAUSE_TIME;
if (inc == 0) [[unlikely]]
{
UpdatePauseTime();
inc = PAUSE_TIME;
}
u32 time = 0;
// Sleep for approximately 500ns
for (; time < 500; time += inc)
MultiPause();
return time;
}
#else
// On ARM, we have big/little cores, and who knows which one we'll measure/run on..
// TODO: Actually verify this code.
const u32 SHORT_SPIN_TIME_TICKS = static_cast<u32>((Common::Timer::GetFrequency() * 500) / 1000000000);
u32 Threading::ShortSpin()
{
const Common::Timer::Value start = Common::Timer::GetCurrentValue();
Common::Timer::Value now = start;
while ((now - start) < SHORT_SPIN_TIME_TICKS)
{
MultiPause();
now = Common::Timer::GetCurrentValue();
}
return static_cast<u32>((Common::Timer::GetCurrentValue() * (now - start)) / 1000000000);
}
#endif
static u32 GetSpinTime()
{
if (char* req = std::getenv("WAIT_SPIN_MICROSECONDS"))
{
return 1000 * atoi(req);
}
else
{
#ifndef _M_ARM64
return 50 * 1000; // 50us
#else
return 200 * 1000; // 200us
#endif
}
}
const u32 Threading::SPIN_TIME_NS = GetSpinTime();
Threading::ThreadHandle::ThreadHandle() = default;
#ifdef _WIN32
@ -617,3 +762,130 @@ bool Threading::KernelSemaphore::TryWait()
return sem_trywait(&m_sema) == 0;
#endif
}
bool Threading::WorkSema::CheckForWork()
{
s32 value = m_state.load(std::memory_order_relaxed);
DebugAssert(!IsDead(value));
// we want to switch to the running state, but preserve the waiting empty bit for RUNNING_N -> RUNNING_0
// otherwise, we clear the waiting flag (since we're notifying the waiter that we're empty below)
while (!m_state.compare_exchange_weak(value,
IsReadyForSleep(value) ? STATE_RUNNING_0 : (value & STATE_FLAG_WAITING_EMPTY),
std::memory_order_acq_rel, std::memory_order_relaxed))
{
}
// if we're not empty, we have work to do
if (!IsReadyForSleep(value))
return true;
// this means we're empty, so notify any waiters
if (value & STATE_FLAG_WAITING_EMPTY)
m_empty_sema.Post();
// no work to do
return false;
}
void Threading::WorkSema::WaitForWork()
{
// State change:
// SLEEPING, SPINNING: This is the worker thread and it's clearly not asleep or spinning, so these states should be
// impossible RUNNING_0: Change state to SLEEPING, wake up thread if WAITING_EMPTY RUNNING_N: Change state to
// RUNNING_0 (and preserve WAITING_EMPTY flag)
s32 value = m_state.load(std::memory_order_relaxed);
DebugAssert(!IsDead(value));
while (!m_state.compare_exchange_weak(value, NextStateWaitForWork(value), std::memory_order_acq_rel,
std::memory_order_relaxed))
;
if (IsReadyForSleep(value))
{
if (value & STATE_FLAG_WAITING_EMPTY)
m_empty_sema.Post();
m_sema.Wait();
// Acknowledge any additional work added between wake up request and getting here
m_state.fetch_and(STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire);
}
}
void Threading::WorkSema::WaitForWorkWithSpin()
{
s32 value = m_state.load(std::memory_order_relaxed);
DebugAssert(!IsDead(value));
while (IsReadyForSleep(value))
{
if (m_state.compare_exchange_weak(value, STATE_SPINNING, std::memory_order_release, std::memory_order_relaxed))
{
if (value & STATE_FLAG_WAITING_EMPTY)
m_empty_sema.Post();
value = STATE_SPINNING;
break;
}
}
u32 waited = 0;
while (value < 0)
{
if (waited > SPIN_TIME_NS)
{
if (!m_state.compare_exchange_weak(value, STATE_SLEEPING, std::memory_order_relaxed))
continue;
m_sema.Wait();
break;
}
waited += ShortSpin();
value = m_state.load(std::memory_order_relaxed);
}
// Clear back to STATE_RUNNING_0 (but preserve waiting empty flag)
m_state.fetch_and(STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire);
}
bool Threading::WorkSema::WaitForEmpty()
{
s32 value = m_state.load(std::memory_order_acquire);
while (true)
{
if (value < 0)
return !IsDead(value); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
// Note: We technically only need memory_order_acquire on *failure* (because that's when we could leave without
// sleeping), but libstdc++ still asserts on failure < success
if (m_state.compare_exchange_weak(value, value | STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire))
break;
}
DebugAssertMsg(!(value & STATE_FLAG_WAITING_EMPTY),
"Multiple threads attempted to wait for empty (not currently supported)");
m_empty_sema.Wait();
return !IsDead(m_state.load(std::memory_order_relaxed));
}
bool Threading::WorkSema::WaitForEmptyWithSpin()
{
s32 value = m_state.load(std::memory_order_acquire);
u32 waited = 0;
while (true)
{
if (value < 0)
return !IsDead(value); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
if (waited > SPIN_TIME_NS &&
m_state.compare_exchange_weak(value, value | STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire))
break;
waited += ShortSpin();
value = m_state.load(std::memory_order_acquire);
}
DebugAssertMsg(!(value & STATE_FLAG_WAITING_EMPTY),
"Multiple threads attempted to wait for empty (not currently supported)");
m_empty_sema.Wait();
return !IsDead(m_state.load(std::memory_order_relaxed));
}
void Threading::WorkSema::Kill()
{
s32 value = m_state.exchange(std::numeric_limits<s32>::min(), std::memory_order_release);
if (value & STATE_FLAG_WAITING_EMPTY)
m_empty_sema.Post();
}
void Threading::WorkSema::Reset()
{
m_state = STATE_RUNNING_0;
}

View File

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>, 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0
#pragma once
#include "types.h"
@ -20,9 +20,16 @@ extern u64 GetThreadTicksPerSecond();
/// Set the name of the current thread
extern void SetNameOfCurrentThread(const char* name);
// Releases a timeslice to other threads.
/// Releases a timeslice to other threads.
extern void Timeslice();
/// Spin for a short period of time (call while spinning waiting for a lock)
/// Returns the approximate number of ns that passed
extern u32 ShortSpin();
/// Number of ns to spin for before sleeping a thread
extern const u32 SPIN_TIME_NS;
// --------------------------------------------------------------------------------------
// ThreadHandle
// --------------------------------------------------------------------------------------
@ -121,4 +128,90 @@ public:
bool TryWait();
};
/// A semaphore for notifying a work-processing thread of new work in a (separate) queue
///
/// Usage:
/// - Processing thread loops on `WaitForWork()` followed by processing all work in the queue
/// - Threads adding work first add their work to the queue, then call `NotifyOfWork()`
class WorkSema
{
/// Semaphore for sleeping the worker thread
KernelSemaphore m_sema;
/// Semaphore for sleeping thread waiting on worker queue empty
KernelSemaphore m_empty_sema;
/// Current state (see enum below)
std::atomic<s32> m_state{0};
// Expected call frequency is NotifyOfWork > WaitForWork > WaitForEmpty
// So optimize states for fast NotifyOfWork
enum
{
/* Any <-2 state: STATE_DEAD: Thread has crashed and is awaiting revival */
STATE_SPINNING = -2, ///< Worker thread is spinning waiting for work
STATE_SLEEPING = -1, ///< Worker thread is sleeping on m_sema
STATE_RUNNING_0 =
0, ///< Worker thread is processing work, but no work has been added since it last checked for new work
/* Any >0 state: STATE_RUNNING_N: Worker thread is processing work, and work has been added since it last checked
for new work */
STATE_FLAG_WAITING_EMPTY =
1 << 30, ///< Flag to indicate that a thread is sleeping on m_empty_sema (can be applied to any STATE_RUNNING)
};
bool IsDead(s32 state) { return state < STATE_SPINNING; }
bool IsReadyForSleep(s32 state)
{
s32 waiting_empty_cleared = state & (STATE_FLAG_WAITING_EMPTY - 1);
return waiting_empty_cleared == STATE_RUNNING_0;
}
s32 NextStateWaitForWork(s32 current)
{
s32 new_state = IsReadyForSleep(current) ? STATE_SLEEPING : STATE_RUNNING_0;
return new_state | (current & STATE_FLAG_WAITING_EMPTY); // Preserve waiting empty flag for RUNNING_N -> RUNNING_0
}
public:
/// Notify the worker thread that you've added new work to its queue
void NotifyOfWork()
{
// State change:
// DEAD: Stay in DEAD (starting DEAD state is INT_MIN so we can assume we won't flip over to anything else)
// SPINNING: Change state to RUNNING. Thread will notice and process the new data
// SLEEPING: Change state to RUNNING and wake worker. Thread will wake up and process the new data.
// RUNNING_0: Change state to RUNNING_N.
// RUNNING_N: Stay in RUNNING_N
s32 old = m_state.fetch_add(2, std::memory_order_release);
if (old == STATE_SLEEPING)
m_sema.Post();
}
/// Checks if there's any work in the queue
bool CheckForWork();
/// Wait for work to be added to the queue
void WaitForWork();
/// Wait for work to be added to the queue, spinning for a bit before sleeping the thread
void WaitForWorkWithSpin();
/// Wait for the worker thread to finish processing all entries in the queue or die
/// Returns false if the thread is dead
bool WaitForEmpty();
/// Wait for the worker thread to finish processing all entries in the queue or die, spinning a bit before sleeping
/// the thread Returns false if the thread is dead
bool WaitForEmptyWithSpin();
/// Called by the worker thread to notify others of its death
/// Dead threads don't process work, and WaitForEmpty will return instantly even though there may be work in the queue
void Kill();
/// Reset the semaphore to the initial state
/// Should be called by the worker thread if it restarts after dying
void Reset();
};
} // namespace Threading

View File

@ -26,6 +26,18 @@
#define ALWAYS_INLINE_RELEASE ALWAYS_INLINE
#endif
// Avoid inline helper
#ifndef NEVER_INLINE
#if defined(_MSC_VER)
#define NEVER_INLINE __declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
#define NEVER_INLINE __attribute__((noinline))
#else
#define NEVER_INLINE
#endif
#endif
// unreferenced parameter macro
#ifndef UNREFERENCED_VARIABLE
#if defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__)

View File

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

View File

@ -11,6 +11,7 @@
#include "bus.h"
#include "cpu_core.h"
#include "fullscreen_ui.h"
#include "gpu_thread.h"
#include "host.h"
#include "system.h"
@ -1001,7 +1002,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// ensure fullscreen UI is ready for notifications
if (display_summary)
FullscreenUI::Initialize();
GPUThread::RunOnThread(&FullscreenUI::Initialize);
if (const std::string_view badge_name = info->badge_name; !badge_name.empty())
{
@ -1062,7 +1063,7 @@ void Achievements::ClearGameHash()
void Achievements::DisplayAchievementSummary()
{
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title;
if (IsHardcoreModeActive())
@ -1087,8 +1088,13 @@ void Achievements::DisplayAchievementSummary()
summary = TRANSLATE_STR("Achievements", "This game has no achievements.");
}
GPUThread::RunOnThread([title = std::move(title), summary = std::move(summary)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
std::move(summary), s_game_icon);
});
}
// Technically not going through the resource API, but since we're passing this to something else, we can't.
@ -1098,11 +1104,16 @@ void Achievements::DisplayAchievementSummary()
void Achievements::DisplayHardcoreDeferredMessage()
{
if (g_settings.achievements_hardcore_mode && !s_hardcore_mode && System::IsValid() && FullscreenUI::Initialize())
if (g_settings.achievements_hardcore_mode && !s_hardcore_mode && System::IsValid())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
});
}
}
@ -1124,7 +1135,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_game_id);
UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title;
if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL)
@ -1134,9 +1145,15 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state);
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id),
static_cast<float>(g_settings.achievements_notification_duration),
std::move(title), cheevo->description, std::move(badge_path));
GPUThread::RunOnThread([id = cheevo->id, duration = g_settings.achievements_notification_duration,
title = std::move(title), description = std::string(cheevo->description),
badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", id), static_cast<float>(duration),
std::move(title), std::move(description), std::move(badge_path));
});
}
if (g_settings.achievements_sound_effects)
@ -1148,7 +1165,7 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
INFO_LOG("Game {} complete", s_game_id);
UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_game_title);
std::string message =
@ -1157,8 +1174,13 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
s_game_summary.num_unlocked_achievements),
TRANSLATE_PLURAL_STR("Achievements", "%n points", "Mastery popup", s_game_summary.points_unlocked));
GPUThread::RunOnThread([title = std::move(title), message = std::move(message), icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), s_game_icon);
std::move(message), std::move(icon));
});
}
}
@ -1166,14 +1188,19 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
{
DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
LEADERBOARD_STARTED_NOTIFICATION_TIME, std::move(title), std::move(message),
s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1181,14 +1208,19 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
{
DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
LEADERBOARD_FAILED_NOTIFICATION_TIME, std::move(title), std::move(message),
s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_FAILED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1196,7 +1228,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
{
DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {}{}"),
@ -1212,9 +1244,14 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
}
if (g_settings.achievements_sound_effects)
@ -1226,7 +1263,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_leaderboard_notifications)
{
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"),
@ -1243,9 +1280,15 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), s_game_icon);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
}
}
@ -1375,26 +1418,30 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server disconnected.");
if (FullscreenUI::Initialize())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
TRANSLATE_STR("Achievements",
"An unlock request could not be completed. We will keep retrying to submit this request."),
Host::OSD_ERROR_DURATION);
}
});
}
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server reconnected.");
if (FullscreenUI::Initialize())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
Host::OSD_INFO_DURATION);
}
});
}
void Achievements::ResetClient()
@ -1472,12 +1519,17 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
// new mode
s_hardcore_mode = enabled;
if (System::IsValid() && (HasActiveGame() || force_display_message) && FullscreenUI::Initialize())
if (System::IsValid() && (HasActiveGame() || force_display_message))
{
GPUThread::RunOnThread([enabled]() {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
});
}
rc_client_set_hardcore_enabled(s_client, enabled);
@ -1806,7 +1858,7 @@ void Achievements::ShowLoginNotification()
if (!user)
return;
if (g_settings.achievements_notifications && FullscreenUI::Initialize())
if (g_settings.achievements_notifications)
{
std::string badge_path = GetLoggedInUserBadgePath();
std::string title = user->display_name;
@ -1815,8 +1867,14 @@ void Achievements::ShowLoginNotification()
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
user->score, user->score_softcore, user->num_unread_messages);
GPUThread::RunOnThread(
[title = std::move(title), summary = std::move(summary), badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(badge_path));
});
}
}
@ -1913,14 +1971,6 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
}
#endif
if (!FullscreenUI::Initialize())
{
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);
callback(false);
return;
}
auto real_callback = [callback = std::move(callback)](bool res) mutable {
// don't run the callback in the middle of rendering the UI
Host::RunOnCPUThread([callback = std::move(callback), res]() {
@ -1930,13 +1980,25 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
});
};
GPUThread::RunOnThread([trigger = std::string(trigger), real_callback = std::move(real_callback)]() mutable {
if (!FullscreenUI::Initialize())
{
Host::AddOSDMessage(
fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);
real_callback(false);
return;
}
ImGuiFullscreen::OpenConfirmMessageDialog(
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you "
fmt::format(TRANSLATE_FS("Achievements",
"{0} cannot be performed while hardcore mode is active. Do you "
"want to disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
});
#else
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
Host::OSD_WARNING_DURATION);

View File

@ -49,7 +49,6 @@
<ClCompile Include="gpu_hw_shadergen.cpp" />
<ClCompile Include="gpu_shadergen.cpp" />
<ClCompile Include="gpu_sw.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_sw_rasterizer_avx2.cpp">
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
@ -57,6 +56,7 @@
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="gpu_thread.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" />
@ -134,8 +134,8 @@
<ClInclude Include="gpu_hw_shadergen.h" />
<ClInclude Include="gpu_shadergen.h" />
<ClInclude Include="gpu_sw.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="gpu_sw_rasterizer.h" />
<ClInclude Include="gpu_thread.h" />
<ClInclude Include="gpu_types.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="cpu_types.h" />

View File

@ -46,7 +46,6 @@
<ClCompile Include="analog_joystick.cpp" />
<ClCompile Include="cpu_recompiler_code_generator_aarch32.cpp" />
<ClCompile Include="gpu_backend.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="texture_replacements.cpp" />
<ClCompile Include="multitap.cpp" />
<ClCompile Include="host.cpp" />
@ -70,6 +69,7 @@
<ClCompile Include="gdb_server.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_sw_rasterizer_avx2.cpp" />
<ClCompile Include="gpu_thread.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -119,7 +119,6 @@
<ClInclude Include="analog_joystick.h" />
<ClInclude Include="gpu_types.h" />
<ClInclude Include="gpu_backend.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="texture_replacements.h" />
<ClInclude Include="multitap.h" />
<ClInclude Include="host.h" />
@ -145,6 +144,7 @@
<ClInclude Include="pine_server.h" />
<ClInclude Include="gdb_server.h" />
<ClInclude Include="gpu_sw_rasterizer.h" />
<ClInclude Include="gpu_thread.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

View File

@ -12,6 +12,7 @@
#include "cpu_core.h"
#include "game_list.h"
#include "gpu.h"
#include "gpu_thread.h"
#include "host.h"
#include "resources.h"
#include "settings.h"
@ -594,6 +595,7 @@ bool FullscreenUI::Initialize()
s_was_paused_on_quick_menu_open = false;
s_about_window_open = false;
s_hotkey_list_cache = InputManager::GetHotkeyList();
GPUThread::SetRunIdle(true);
if (!System::IsValid())
SwitchToLanding();
@ -624,6 +626,7 @@ bool FullscreenUI::AreAnyDialogsOpen()
void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
@ -631,54 +634,114 @@ void FullscreenUI::CheckForConfigChanges(const Settings& old_settings)
// That means we're going to be reading achievement state.
if (old_settings.achievements_enabled && !g_settings.achievements_enabled)
{
if (s_current_main_window == MainWindowType::Achievements || s_current_main_window == MainWindowType::Leaderboards)
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (s_current_main_window == MainWindowType::Achievements ||
s_current_main_window == MainWindowType::Leaderboards)
{
ReturnToPreviousWindow();
}
});
}
}
void FullscreenUI::OnSystemStarted()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
GPUThread::SetRunIdle(false);
s_current_main_window = MainWindowType::None;
QueueResetFocus();
});
}
void FullscreenUI::OnSystemPaused()
{
// noop
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
GPUThread::SetRunIdle(true);
});
}
void FullscreenUI::OnSystemResumed()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
GPUThread::SetRunIdle(false);
// get rid of pause menu if we unpaused another way
if (s_current_main_window == MainWindowType::PauseMenu)
ClosePauseMenu();
});
}
void FullscreenUI::OnSystemDestroyed()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
// If we didn't start big picture before the system, shut ourselves down.
if (!GPUThread::WasFullscreenUIRequested())
{
Shutdown();
return;
}
GPUThread::SetRunIdle(true);
s_pause_menu_was_open = false;
s_was_paused_on_quick_menu_open = false;
s_current_pause_submenu = PauseSubMenu::None;
SwitchToLanding();
});
}
void FullscreenUI::OnRunningGameChanged()
{
// NOTE: Called on CPU thread.
if (!IsInitialized())
return;
const std::string& path = System::GetDiscPath();
const std::string& serial = System::GetGameSerial();
std::string subtitle;
if (!serial.empty())
s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
else
s_current_game_subtitle = {};
subtitle = {};
GPUThread::RunOnThread([subtitle = std::move(subtitle)]() mutable {
if (!IsInitialized())
return;
s_current_game_subtitle = std::move(subtitle);
});
}
void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open)
@ -695,6 +758,7 @@ void FullscreenUI::OpenPauseMenu()
if (!System::IsValid())
return;
GPUThread::RunOnThread([]() {
if (!Initialize() || s_current_main_window != MainWindowType::None)
return;
@ -704,6 +768,7 @@ void FullscreenUI::OpenPauseMenu()
QueueResetFocus();
ForceKeyNavEnabled();
FixStateIfPaused();
});
}
void FullscreenUI::FixStateIfPaused()
@ -713,21 +778,15 @@ void FullscreenUI::FixStateIfPaused()
// When we're paused, we won't have trickled the key up event for escape yet. Do it now.
ImGui::UpdateInputEvents(false);
Host::OnIdleStateChanged();
Host::RunOnCPUThread([]() {
if (System::IsValid())
{
// Why twice? To clear the "wants keyboard input" flag.
System::InvalidateDisplay();
System::InvalidateDisplay();
}
});
}
void FullscreenUI::ClosePauseMenu()
{
if (!IsInitialized() || !System::IsValid())
if (!System::IsValid())
return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
if (System::GetState() == System::State::Paused && !s_was_paused_on_quick_menu_open)
@ -738,6 +797,7 @@ void FullscreenUI::ClosePauseMenu()
s_pause_menu_was_open = false;
QueueResetFocus();
FixStateIfPaused();
});
}
void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
@ -749,6 +809,8 @@ void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
void FullscreenUI::Shutdown()
{
GPUThread::SetRunIdle(false);
Achievements::ClearUIState();
CloseSaveStateSelector();
s_cover_image_map.clear();
@ -1135,6 +1197,7 @@ void FullscreenUI::DoChangeDiscFromFile()
void FullscreenUI::DoChangeDisc()
{
Host::RunOnCPUThread([]() {
ImGuiFullscreen::ChoiceDialogOptions options;
if (System::HasMediaSubImages())
@ -1147,6 +1210,7 @@ void FullscreenUI::DoChangeDisc()
for (u32 i = 0; i < count; i++)
options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
GPUThread::RunOnThread([options = std::move(options)]() mutable {
auto callback = [](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
@ -1156,7 +1220,7 @@ void FullscreenUI::DoChangeDisc()
}
else if (index > 0)
{
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
Host::RunOnCPUThread([index = static_cast<u32>(index - 1)]() { System::SwitchMediaSubImage(index); });
}
QueueResetFocus();
@ -1166,6 +1230,7 @@ void FullscreenUI::DoChangeDisc()
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
});
return;
}
@ -1189,6 +1254,7 @@ void FullscreenUI::DoChangeDisc()
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)
{
@ -1198,7 +1264,7 @@ void FullscreenUI::DoChangeDisc()
}
else if (index > 0)
{
System::InsertMedia(paths[index - 1].c_str());
Host::RunOnCPUThread([path = std::move(paths[index - 1])]() { System::InsertMedia(path.c_str()); });
}
QueueResetFocus();
@ -1208,16 +1274,19 @@ void FullscreenUI::DoChangeDisc()
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
});
return;
}
}
DoChangeDiscFromFile();
GPUThread::RunOnThread([]() { DoChangeDiscFromFile(); });
});
}
void FullscreenUI::DoCheatsMenu()
{
Host::RunOnCPUThread([]() {
CheatList* cl = System::GetCheatList();
if (!cl)
{
@ -1238,6 +1307,7 @@ void FullscreenUI::DoCheatsMenu()
options.emplace_back(cc.description.c_str(), cc.enabled);
}
GPUThread::RunOnThread([options = std::move(options)]() mutable {
auto callback = [](s32 index, const std::string& title, bool checked) {
if (index < 0)
{
@ -1245,6 +1315,7 @@ void FullscreenUI::DoCheatsMenu()
return;
}
Host::RunOnCPUThread([index, checked]() {
CheatList* cl = System::GetCheatList();
if (!cl)
return;
@ -1254,13 +1325,17 @@ void FullscreenUI::DoCheatsMenu()
cl->ApplyCode(static_cast<u32>(index));
else
System::SetCheatCodeState(static_cast<u32>(index), checked);
});
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_FROWN, "Cheat List"), true, std::move(options), std::move(callback));
});
});
}
void FullscreenUI::DoToggleAnalogMode()
{
// hacky way to toggle analog mode
Host::RunOnCPUThread([]() {
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
{
Controller* ctrl = System::GetController(i);
@ -1281,6 +1356,7 @@ void FullscreenUI::DoToggleAnalogMode()
}
}
}
});
}
void FullscreenUI::DoRequestExit()
@ -3720,12 +3796,9 @@ void FullscreenUI::DrawControllerSettingsPage()
&Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count);
// load mtap settings
MultitapMode mtap_mode = g_settings.multitap_mode;
if (IsEditingGameSettings(bsi))
{
mtap_mode = Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
.value_or(g_settings.multitap_mode);
}
const MultitapMode mtap_mode =
Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
.value_or(Settings::DEFAULT_MULTITAP_MODE);
const std::array<bool, 2> mtap_enabled = {
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
@ -7076,22 +7149,25 @@ void FullscreenUI::DrawAboutWindow()
void FullscreenUI::OpenAchievementsWindow()
{
if (!System::IsValid())
return;
if (!Achievements::IsActive())
{
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."),
Host::OSD_INFO_DURATION);
return;
}
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasAchievements() || !Achievements::PrepareAchievementsWindow())
else if (!Achievements::HasAchievements())
{
ShowToast(std::string(), FSUI_STR("This game has no achievements."));
return;
}
GPUThread::RunOnThread([]() {
if (!Initialize() || !Achievements::PrepareAchievementsWindow())
return;
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
@ -7101,6 +7177,7 @@ void FullscreenUI::OpenAchievementsWindow()
s_current_main_window = MainWindowType::Achievements;
QueueResetFocus();
FixStateIfPaused();
});
}
bool FullscreenUI::IsAchievementsWindowOpen()
@ -7110,22 +7187,25 @@ bool FullscreenUI::IsAchievementsWindowOpen()
void FullscreenUI::OpenLeaderboardsWindow()
{
if (!System::IsValid())
return;
if (!Achievements::IsActive())
{
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."),
Host::OSD_INFO_DURATION);
return;
}
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasLeaderboards() || !Achievements::PrepareLeaderboardsWindow())
else if (!Achievements::HasLeaderboards())
{
ShowToast(std::string(), FSUI_STR("This game has no leaderboards."));
return;
}
GPUThread::RunOnThread([]() {
if (!Initialize() || !Achievements::PrepareLeaderboardsWindow())
return;
if (s_current_main_window != MainWindowType::PauseMenu)
{
PauseForMenuOpen(false);
@ -7135,6 +7215,7 @@ void FullscreenUI::OpenLeaderboardsWindow()
s_current_main_window = MainWindowType::Leaderboards;
QueueResetFocus();
FixStateIfPaused();
});
}
bool FullscreenUI::IsLeaderboardsWindowOpen()

File diff suppressed because it is too large Load Diff

View File

@ -28,14 +28,11 @@ class GPUDevice;
class GPUTexture;
class GPUPipeline;
class GPUBackend;
struct Settings;
class TimingEvent;
namespace Threading {
class Thread;
}
class GPU
class GPU final
{
public:
enum class BlitterState : u8
@ -60,7 +57,6 @@ public:
DOT_TIMER_INDEX = 0,
HBLANK_TIMER_INDEX = 1,
MAX_RESOLUTION_SCALE = 32,
DEINTERLACE_BUFFER_COUNT = 4,
DRAWING_AREA_COORD_MASK = 1023,
};
@ -86,25 +82,14 @@ public:
// Base class constructor.
GPU();
virtual ~GPU();
~GPU();
virtual const Threading::Thread* GetSWThread() const = 0;
virtual bool IsHardwareRenderer() const = 0;
virtual bool Initialize();
virtual void Reset(bool clear_vram);
virtual bool DoState(StateWrapper& sw, GPUTexture** save_to_texture, bool update_display);
// Graphics API state reset/restore - call when drawing the UI etc.
// TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext();
void Initialize();
void Reset(bool clear_vram);
bool DoState(StateWrapper& sw, GPUTexture** save_to_texture, bool update_display);
// Render statistics debug window.
void DrawDebugStateWindow();
void GetStatsString(SmallStringBase& str);
void GetMemoryStatsString(SmallStringBase& str);
void ResetStatistics();
void UpdateStatistics(u32 frame_count);
void CPUClockChanged();
@ -160,24 +145,12 @@ public:
void SynchronizeCRTC();
/// Recompile shaders/recreate framebuffers when needed.
virtual void UpdateSettings(const Settings& old_settings);
/// Updates the resolution scale when it's set to automatic.
virtual void UpdateResolutionScale();
/// Returns the effective display resolution of the GPU.
virtual std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true);
/// Returns the full display resolution of the GPU, including padding.
virtual std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true);
void UpdateSettings(const Settings& old_settings);
float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const;
float ComputeDisplayAspectRatio() const;
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.
void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
float* display_y) const;
@ -203,30 +176,7 @@ public:
// Dumps raw VRAM to a file.
bool DumpVRAMToFile(const char* filename);
// Ensures all buffered vertices are drawn.
virtual void FlushRender() = 0;
/// Helper function for computing the draw rectangle in a larger window.
GSVector4i CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio = true) const;
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i draw_rect, bool postfx,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message);
/// Draws the current display texture, with any post-processing.
bool PresentDisplay();
/// Reads the CLUT from the specified coordinates, accounting for wrap-around.
static void ReadCLUT(u16* dest, GPUTexturePaletteReg reg, bool clut_is_8bit);
protected:
private:
TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const;
TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
@ -237,16 +187,6 @@ protected:
}
ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; }
static constexpr std::tuple<u8, u8> UnpackTexcoord(u16 texcoord)
{
return std::make_tuple(static_cast<u8>(texcoord), static_cast<u8>(texcoord >> 8));
}
static constexpr std::tuple<u8, u8, u8> UnpackColorRGB24(u32 rgb24)
{
return std::make_tuple(static_cast<u8>(rgb24), static_cast<u8>(rgb24 >> 8), static_cast<u8>(rgb24 >> 16));
}
static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer,
bool remove_alpha);
@ -270,10 +210,10 @@ protected:
void CommandTickEvent(TickCount ticks);
/// Returns 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...).
ALWAYS_INLINE u32 GetInterlacedDisplayField() const { return ZeroExtend32(m_crtc_state.interlaced_field); }
ALWAYS_INLINE u8 GetInterlacedDisplayField() const { return m_crtc_state.interlaced_field; }
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
ALWAYS_INLINE u32 GetActiveLineLSB() const { return ZeroExtend32(m_crtc_state.active_line_lsb); }
ALWAYS_INLINE u8 GetActiveLineLSB() const { return m_crtc_state.active_line_lsb; }
/// Updates drawing area that's suitablef or clamping.
void SetClampedDrawingArea();
@ -308,16 +248,15 @@ protected:
void InvalidateCLUT();
bool IsCLUTValid() const;
// Rendering in the backend
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height);
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color);
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask);
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height);
virtual void DispatchRenderCommand() = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
virtual void UpdateDisplay() = 0;
virtual void DrawRendererStats();
virtual void OnBufferSwapped();
void ReadVRAM(u16 x, u16 y, u16 width, u16 height);
void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask);
void UpdateDisplay(bool present_frame);
void PrepareForDraw();
void FinishPolyline();
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
bool textured, bool semitransparent)
@ -443,19 +382,10 @@ protected:
u32 texture_window_value;
// decoded values
// TODO: Make this a command
GPUTextureWindow texture_window;
bool texture_x_flip;
bool texture_y_flip;
bool texture_page_changed;
bool texture_window_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; }
ALWAYS_INLINE bool IsTextureWindowChanged() const { return texture_window_changed; }
ALWAYS_INLINE void SetTextureWindowChanged() { texture_window_changed = true; }
ALWAYS_INLINE void ClearTextureWindowChangedFlag() { texture_window_changed = false; }
} m_draw_mode = {};
GPUDrawingArea m_drawing_area = {};
@ -587,64 +517,7 @@ protected:
TickCount m_max_run_ahead = 128;
u32 m_fifo_size = 128;
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
bool RenderDisplay(GPUTexture* target, const GSVector4i draw_rect, bool postfx);
bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing();
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
GPUTexture* m_display_depth_buffer = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
struct Counters
{
u32 num_reads;
u32 num_writes;
u32 num_copies;
u32 num_vertices;
u32 num_primitives;
// u32 num_read_texture_updates;
// u32 num_ubo_updates;
};
struct Stats : Counters
{
size_t host_buffer_streamed;
u32 host_num_draws;
u32 host_num_barriers;
u32 host_num_render_passes;
u32 host_num_copies;
u32 host_num_downloads;
u32 host_num_uploads;
};
Counters m_counters = {};
Stats m_stats = {};
private:
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing);
using GP0CommandHandler = bool (GPU::*)();
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
static GP0CommandHandlerTable GenerateGP0CommandHandlerTable();

File diff suppressed because it is too large Load Diff

View File

@ -2,91 +2,182 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/heap_array.h"
#include "common/threading.h"
#include "gpu_types.h"
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4324) // warning C4324: 'GPUBackend': structure was padded due to alignment specifier
#endif
#include "gpu_types.h"
#include "util/gpu_texture.h"
#include <tuple>
class Error;
class SmallStringBase;
class GPUFramebuffer;
class GPUPipeline;
struct Settings;
class StateWrapper;
// DESIGN NOTE: Only static methods should be called on the CPU thread.
// You specifically don't have a global pointer available for this reason.
class GPUBackend
{
public:
static GPUThreadCommand* NewClearVRAMCommand();
static GPUBackendDoStateCommand* NewDoStateCommand();
static GPUThreadCommand* NewClearDisplayCommand();
static GPUBackendUpdateDisplayCommand* NewUpdateDisplayCommand();
static GPUThreadCommand* NewClearCacheCommand();
static GPUThreadCommand* NewBufferSwappedCommand();
static GPUThreadCommand* NewFlushRenderCommand();
static GPUThreadCommand* NewUpdateResolutionScaleCommand();
static GPUBackendReadVRAMCommand* NewReadVRAMCommand();
static GPUBackendFillVRAMCommand* NewFillVRAMCommand();
static GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
static GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
static GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
static GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
static GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
static GPUBackendDrawPrecisePolygonCommand* NewDrawPrecisePolygonCommand(u32 num_vertices);
static GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
static GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
static void PushCommand(GPUThreadCommand* cmd);
static void PushCommandAndWakeThread(GPUThreadCommand* cmd);
static void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
static bool IsUsingHardwareBackend();
static std::unique_ptr<GPUBackend> CreateHardwareBackend();
static std::unique_ptr<GPUBackend> CreateSoftwareBackend();
static bool BeginQueueFrame();
static void WaitForOneQueuedFrame();
static bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i draw_rect, bool postfx,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
static std::tuple<u32, u32> GetLastDisplaySourceSize();
static void GetStatsString(SmallStringBase& str);
static void GetMemoryStatsString(SmallStringBase& str);
static void ResetStatistics();
static void UpdateStatistics(u32 frame_count);
public:
GPUBackend();
virtual ~GPUBackend();
ALWAYS_INLINE const Threading::Thread* GetThread() const { return m_use_gpu_thread ? &m_gpu_thread : nullptr; }
ALWAYS_INLINE const void* GetDisplayTextureHandle() const { return m_display_texture; }
ALWAYS_INLINE s32 GetDisplayWidth() const { return m_display_width; }
ALWAYS_INLINE s32 GetDisplayHeight() const { return m_display_height; }
ALWAYS_INLINE s32 GetDisplayViewWidth() const { return m_display_texture_view_width; }
ALWAYS_INLINE s32 GetDisplayViewHeight() const { return m_display_texture_view_height; }
ALWAYS_INLINE float GetDisplayAspectRatio() const { return m_display_aspect_ratio; }
ALWAYS_INLINE bool HasDisplayTexture() const { return static_cast<bool>(m_display_texture); }
virtual bool Initialize(bool force_thread);
virtual void UpdateSettings();
virtual void Reset();
virtual void Shutdown();
virtual bool Initialize(bool clear_vram, Error* error);
GPUBackendFillVRAMCommand* NewFillVRAMCommand();
GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
virtual void ClearVRAM() = 0;
virtual bool DoState(GPUTexture** host_texture, bool is_reading, bool update_display) = 0;
void PushCommand(GPUBackendCommand* cmd);
void Sync(bool allow_sleep);
/// Processes all pending GPU commands.
void RunGPULoop();
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 ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params);
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params);
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) = 0;
GPUBackendCommandParameters params);
virtual void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) = 0;
virtual void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) = 0;
virtual void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawLine(const GPUBackendDrawLineCommand* cmd) = 0;
virtual void FlushRender() = 0;
virtual void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
virtual void ClearCache() = 0;
virtual void OnBufferSwapped() = 0;
void HandleCommand(const GPUBackendCommand* cmd);
virtual void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) = 0;
Threading::KernelSemaphore m_sync_semaphore;
std::atomic_bool m_gpu_thread_sleeping{false};
std::atomic_bool m_gpu_loop_done{false};
Threading::Thread m_gpu_thread;
bool m_use_gpu_thread = false;
virtual void UpdateSettings(const Settings& old_settings);
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;
/// Returns the effective display resolution of the GPU.
virtual std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) const = 0;
/// Returns the full display resolution of the GPU, including padding.
virtual std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) const = 0;
/// TODO: Updates the resolution scale when it's set to automatic.
virtual void UpdateResolutionScale() = 0;
/// Ensures all pending draws are flushed to the host GPU.
virtual void FlushRender() = 0;
// Graphics API state reset/restore - call when drawing the UI etc.
// TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext() = 0;
void HandleCommand(const GPUThreadCommand* cmd);
/// Draws the current display texture, with any post-processing.
bool PresentDisplay();
protected:
enum : u32
{
COMMAND_QUEUE_SIZE = 4 * 1024 * 1024,
THRESHOLD_TO_WAKE_GPU = 256
DEINTERLACE_BUFFER_COUNT = 4,
};
FixedHeapArray<u8, COMMAND_QUEUE_SIZE> m_command_fifo_data;
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_read_ptr{0};
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_write_ptr{0};
};
/// Helper function for computing the draw rectangle in a larger window.
GSVector4i CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio = true) const;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);
/// Renders the display, optionally with postprocessing to the specified image.
void HandleRenderScreenshotToBuffer(const GPUThreadRenderScreenshotToBufferCommand* cmd);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message);
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing);
void ClearDisplay();
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
bool RenderDisplay(GPUTexture* target, 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();
s32 m_display_width = 0;
s32 m_display_height = 0;
s32 m_display_origin_left = 0;
s32 m_display_origin_top = 0;
s32 m_display_vram_width = 0;
s32 m_display_vram_height = 0;
float m_display_aspect_ratio = 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;
};

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
#pragma once
#include "gpu.h"
#include "gpu_backend.h"
#include "texture_replacements.h"
#include "util/gpu_device.h"
@ -21,7 +21,9 @@ class GPU_SW_Backend;
struct GPUBackendCommand;
struct GPUBackendDrawCommand;
class GPU_HW final : public GPU
// TODO: Move to cpp
// TODO: Rename to GPUHWBackend, preserved to avoid conflicts.
class GPU_HW final : public GPUBackend
{
public:
enum class BatchRenderMode : u8
@ -55,21 +57,37 @@ public:
GPU_HW();
~GPU_HW() override;
const Threading::Thread* GetSWThread() const override;
bool IsHardwareRenderer() const override;
bool Initialize(bool clear_vram, Error* error) override;
bool Initialize() override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
void ClearVRAM() override;
bool DoState(GPUTexture** host_texture, bool is_reading, bool update_display) override;
void RestoreDeviceContext() override;
void UpdateSettings(const Settings& old_settings) override;
void UpdateResolutionScale() override final;
std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) override;
std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) override;
void UpdateDisplay() override;
void UpdateResolutionScale() override;
std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) const override;
std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) const override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void FlushRender() override;
void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
private:
enum : u32
@ -78,6 +96,7 @@ private:
MAX_VERTICES_FOR_RECTANGLE = 6 * (((MAX_PRIMITIVE_WIDTH + (TEXTURE_PAGE_WIDTH - 1)) / TEXTURE_PAGE_WIDTH) + 1u) *
(((MAX_PRIMITIVE_HEIGHT + (TEXTURE_PAGE_HEIGHT - 1)) / TEXTURE_PAGE_HEIGHT) + 1u),
NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount),
INVALID_DRAW_MODE_BITS = 0xFFFFFFFFu,
};
enum : u8
{
@ -152,8 +171,6 @@ private:
bool CompilePipelines();
void DestroyPipelines();
void LoadVertices();
void PrintSettingsToLog();
void CheckSettings();
@ -171,7 +188,7 @@ private:
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
bool IsUsingMultisampling() const;
bool IsUsingDownsampling() const;
bool IsUsingDownsampling(const GPUBackendUpdateDisplayCommand* cmd) const;
void SetFullVRAMDirtyRectangle();
void ClearVRAMDirtyRectangle();
@ -181,11 +198,14 @@ private:
void AddUnclampedDrawnRectangle(const GSVector4i rect);
void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
void CheckForTexPageOverlap(GSVector4i uv_rect);
void CheckForTexPageOverlap(const GPUBackendDrawCommand* cmd, GSVector4i uv_rect);
bool IsFlushed() const;
void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices);
void EnsureVertexBufferSpaceForCurrentCommand();
void EnsureVertexBufferSpaceForCommand(const GPUBackendDrawCommand* cmd);
void PrepareDraw(const GPUBackendDrawCommand* cmd);
void FinishPolygonDraw(const GPUBackendDrawCommand* cmd, std::array<BatchVertex, 4>& vertices, u32 num_vertices,
bool is_3d);
void ResetBatchVertexDepth();
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
@ -197,20 +217,8 @@ private:
/// Returns true if the draw is going to use shader blending/framebuffer fetch.
bool NeedsShaderBlending(GPUTransparencyMode transparency, bool check_mask) const;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
void UpdateSoftwareRenderer(bool copy_vram_from_hw);
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void DispatchRenderCommand() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FlushRender() override;
void DrawRendererStats() override;
void OnBufferSwapped() override;
void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask,
bool check_mask, const GSVector4i bounds);
bool BlitVRAMReplacementTexture(const TextureReplacements::ReplacementImage* tex, u32 dst_x, u32 dst_y, u32 width,
@ -220,17 +228,17 @@ private:
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
/// Handles quads with flipped texture coordinate directions.
void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices);
void HandleFlippedQuadTextureCoordinates(const GPUBackendDrawCommand* cmd, BatchVertex* vertices);
bool IsPossibleSpritePolygon(const BatchVertex* vertices) const;
bool ExpandLineTriangles(BatchVertex* vertices);
/// Computes polygon U/V boundaries, and for overlap with the current texture page.
void ComputePolygonUVLimits(BatchVertex* vertices, u32 num_vertices);
void ComputePolygonUVLimits(const GPUBackendDrawCommand* cmd, BatchVertex* vertices, u32 num_vertices);
/// Sets the depth test flag for PGXP depth buffering.
void SetBatchDepthBuffer(bool enabled);
void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices);
void SetBatchSpriteMode(bool enabled);
void SetBatchDepthBuffer(const GPUBackendDrawCommand* cmd, bool enabled);
void CheckForDepthClear(const GPUBackendDrawCommand* cmd, const BatchVertex* vertices, u32 num_vertices);
void SetBatchSpriteMode(const GPUBackendDrawCommand* cmd, bool enabled);
void UpdateDownsamplingLevels();
@ -249,7 +257,7 @@ private:
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
std::unique_ptr<GPUTexture> m_vram_write_texture;
std::unique_ptr<GPU_SW_Backend> m_sw_renderer;
// std::unique_ptr<GPU_SW_Backend> m_sw_renderer;
BatchVertex* m_batch_vertex_ptr = nullptr;
u16* m_batch_index_ptr = nullptr;
@ -287,15 +295,29 @@ private:
BatchConfig m_batch;
// Changed state
bool m_drawing_area_changed = true;
bool m_batch_ubo_dirty = true;
BatchUBOData m_batch_ubo_data = {};
// Bounding box of VRAM area that the GPU has drawn into.
GSVector4i m_clamped_drawing_area = {};
GSVector4i m_vram_dirty_draw_rect = INVALID_RECT;
GSVector4i m_vram_dirty_write_rect = INVALID_RECT;
GSVector4i m_current_uv_rect = INVALID_RECT;
s32 m_current_texture_page_offset[2] = {};
union
{
struct
{
GPUDrawModeReg mode_reg;
GPUTexturePaletteReg palette_reg;
};
u32 bits = INVALID_DRAW_MODE_BITS;
} m_draw_mode = {};
u32 m_last_texture_window_bits = 0;
std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
// [wrapped][interlaced]

View File

@ -2,7 +2,9 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "gpu_hw.h"
#include "util/shadergen.h"
class GPU_HW_ShaderGen : public ShaderGen

View File

@ -2,14 +2,15 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gpu_sw.h"
#include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "system.h"
#include "util/gpu_device.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/gsvector.h"
#include "common/gsvector_formatter.h"
#include "common/intrin.h"
#include "common/log.h"
#include <algorithm>
@ -18,27 +19,141 @@ Log_SetChannel(GPU_SW);
GPU_SW::GPU_SW() = default;
GPU_SW::~GPU_SW()
{
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
m_backend.Shutdown();
}
GPU_SW::~GPU_SW() = default;
const Threading::Thread* GPU_SW::GetSWThread() const
bool GPU_SW::Initialize(bool clear_vram, Error* error)
{
return m_backend.GetThread();
}
bool GPU_SW::IsHardwareRenderer() const
{
return false;
}
bool GPU_SW::Initialize()
{
if (!GPU::Initialize() || !m_backend.Initialize(false))
if (!GPUBackend::Initialize(clear_vram, error))
return false;
GPU_SW_Rasterizer::SelectImplementation();
// if we're using "new" vram, clear it out here
if (clear_vram)
std::memset(g_vram, 0, sizeof(g_vram));
SetDisplayTextureFormat();
return true;
}
bool GPU_SW::DoState(GPUTexture** host_texture, bool is_reading, bool update_display)
{
// TODO: FIXME
// ignore the host texture for software mode, since we want to save vram here
return true;
}
void GPU_SW::ClearVRAM()
{
std::memset(g_vram, 0, sizeof(g_vram));
std::memset(g_gpu_clut, 0, sizeof(g_gpu_clut));
}
std::tuple<u32, u32> GPU_SW::GetEffectiveDisplayResolution(bool scaled /* = true */) const
{
return std::tie(m_display_vram_width, m_display_vram_height);
}
std::tuple<u32, u32> GPU_SW::GetFullDisplayResolution(bool scaled /* = true */) const
{
return std::tie(m_display_width, m_display_height);
}
void GPU_SW::UpdateResolutionScale()
{
}
void GPU_SW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
}
void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const bool dithering_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable, dithering_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::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const bool dithering_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable, dithering_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];
GPUBackendDrawPolygonCommand::Vertex& dst = vertices[i];
dst.x = src.native_x;
dst.y = src.native_y;
dst.color = src.color;
dst.texcoord = src.texcoord;
}
DrawFunction(cmd, &vertices[0], &vertices[1], &vertices[2]);
if (rc.quad_polygon)
DrawFunction(cmd, &vertices[2], &vertices[1], &vertices[3]);
}
void GPU_SW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd);
}
void GPU_SW::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction = GPU_SW_Rasterizer::GetDrawLineFunction(
cmd->rc.shading_enable, cmd->rc.transparency_enable, cmd->IsDitheringEnabled());
for (u16 i = 1; i < cmd->num_vertices; i++)
DrawFunction(cmd, &cmd->vertices[i - 1], &cmd->vertices[i]);
}
void GPU_SW::DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area)
{
GPU_SW_Rasterizer::g_drawing_area = new_drawing_area;
}
void GPU_SW::ClearCache()
{
}
void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU_SW_Rasterizer::UpdateCLUT(reg, clut_is_8bit);
}
void GPU_SW::OnBufferSwapped()
{
}
void GPU_SW::FlushRender()
{
}
void GPU_SW::RestoreDeviceContext()
{
}
void GPU_SW::SetDisplayTextureFormat()
{
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB565, GPUTexture::Format::RGBA5551,
GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8};
static constexpr const std::array formats_for_24bit = {GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8,
@ -59,32 +174,7 @@ bool GPU_SW::Initialize()
break;
}
}
return true;
}
bool GPU_SW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display)
{
// need to ensure the worker thread is done
m_backend.Sync(true);
// ignore the host texture for software mode, since we want to save vram here
return GPU::DoState(sw, nullptr, update_display);
}
void GPU_SW::Reset(bool clear_vram)
{
GPU::Reset(clear_vram);
m_backend.Reset();
}
void GPU_SW::UpdateSettings(const Settings& old_settings)
{
GPU::UpdateSettings(old_settings);
m_backend.UpdateSettings();
}
GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format)
{
if (!m_upload_texture || m_upload_texture->GetWidth() != width || m_upload_texture->GetHeight() != height ||
@ -420,32 +510,28 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3
}
}
void GPU_SW::UpdateDisplay()
void GPU_SW::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
{
// fill display texture
m_backend.Sync(true);
if (!g_settings.debugging.show_vram)
{
if (IsDisplayDisabled())
if (cmd->display_disabled)
{
ClearDisplayTexture();
return;
}
const bool is_24bit = m_GPUSTAT.display_area_color_depth_24;
const bool interlaced = IsInterlacedDisplayEnabled();
const u32 field = GetInterlacedDisplayField();
const u32 vram_offset_x = is_24bit ? m_crtc_state.regs.X : m_crtc_state.display_vram_left;
const u32 vram_offset_y =
m_crtc_state.display_vram_top + ((interlaced && m_GPUSTAT.vertical_resolution) ? field : 0);
const u32 skip_x = is_24bit ? (m_crtc_state.display_vram_left - m_crtc_state.regs.X) : 0;
const u32 read_width = m_crtc_state.display_vram_width;
const u32 read_height = interlaced ? (m_crtc_state.display_vram_height / 2) : m_crtc_state.display_vram_height;
const bool is_24bit = cmd->display_24bit;
const bool interlaced = cmd->interlaced_display_enabled;
const u32 field = cmd->interlaced_display_field;
const u32 vram_offset_x = is_24bit ? cmd->X : cmd->display_vram_left;
const u32 vram_offset_y = cmd->display_vram_top + ((interlaced && cmd->interlaced_display_interleaved) ? field : 0);
const u32 skip_x = is_24bit ? (cmd->display_vram_left - cmd->X) : 0;
const u32 read_width = cmd->display_vram_width;
const u32 read_height = interlaced ? (cmd->display_vram_height / 2) : cmd->display_vram_height;
if (IsInterlacedDisplayEnabled())
if (cmd->interlaced_display_enabled)
{
const u32 line_skip = m_GPUSTAT.vertical_resolution;
const u32 line_skip = cmd->interlaced_display_interleaved;
if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, line_skip, is_24bit))
{
SetDisplayTexture(m_upload_texture.get(), nullptr, 0, 0, read_width, read_height);
@ -477,336 +563,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->palette.bits = m_draw_mode.palette_reg.bits;
cmd->window = m_draw_mode.texture_window;
}
void GPU_SW::DispatchRenderCommand()
{
if (m_drawing_area_changed)
{
GPUBackendSetDrawingAreaCommand* cmd = m_backend.NewSetDrawingAreaCommand();
cmd->new_area = m_drawing_area;
GSVector4i::store<false>(cmd->new_clamped_area, m_clamped_drawing_area);
m_backend.PushCommand(cmd);
m_drawing_area_changed = false;
}
const GPURenderCommand rc{m_render_command.bits};
switch (rc.primitive)
{
case GPUPrimitive::Polygon:
{
const u32 num_vertices = rc.quad_polygon ? 4 : 3;
GPUBackendDrawPolygonCommand* cmd = m_backend.NewDrawPolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
std::array<GSVector2i, 4> positions;
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPolygonCommand::Vertex* vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->x = m_drawing_offset.x + vp.x;
vert->y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
positions[i] = GSVector2i::load(&vert->x);
}
// Cull polygons which are too large.
const GSVector2i min_pos_12 = positions[1].min_i32(positions[2]);
const GSVector2i max_pos_12 = positions[1].max_i32(positions[2]);
const GSVector4i draw_rect_012 = GSVector4i(min_pos_12.min_i32(positions[0]))
.upl64(GSVector4i(max_pos_12.max_i32(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_i32(positions[3]))
.upl64(GSVector4i(max_pos_12.max_i32(positions[3])))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!m_clamped_drawing_area.rintersects(draw_rect_123));
if (second_tri_culled)
{
DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
if (first_tri_culled)
return;
}
else
{
AddDrawTriangleTicks(positions[2], positions[1], positions[3], rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
}
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Rectangle:
{
GPUBackendDrawRectangleCommand* cmd = m_backend.NewDrawRectangleCommand();
FillDrawCommand(cmd, rc);
cmd->color = rc.color_for_first_vertex;
const GPUVertexPosition vp{FifoPop()};
cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
if (rc.texture_enable)
{
const u32 texcoord_and_palette = FifoPop();
cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
cmd->texcoord = Truncate16(texcoord_and_palette);
}
else
{
cmd->palette.bits = 0;
cmd->texcoord = 0;
}
switch (rc.rectangle_size)
{
case GPUDrawRectangleSize::R1x1:
cmd->width = 1;
cmd->height = 1;
break;
case GPUDrawRectangleSize::R8x8:
cmd->width = 8;
cmd->height = 8;
break;
case GPUDrawRectangleSize::R16x16:
cmd->width = 16;
cmd->height = 16;
break;
default:
{
const u32 width_and_height = FifoPop();
cmd->width = static_cast<u16>(width_and_height & VRAM_WIDTH_MASK);
cmd->height = static_cast<u16>((width_and_height >> 16) & VRAM_HEIGHT_MASK);
}
break;
}
const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty()) [[unlikely]]
{
DEBUG_LOG("Culling off-screen rectangle {}", rect);
return;
}
AddDrawRectangleTicks(clamped_rect, rc.texture_enable, rc.transparency_enable);
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Line:
{
if (!rc.polyline)
{
GPUBackendDrawLineCommand* cmd = m_backend.NewDrawLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
if (rc.shading_enable)
{
cmd->vertices[0].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
else
{
cmd->vertices[0].color = rc.color_for_first_vertex;
cmd->vertices[1].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
const GSVector4i v0 = GSVector4i::loadl(&cmd->vertices[0].x);
const GSVector4i v1 = GSVector4i::loadl(&cmd->vertices[1].x);
const GSVector4i rect = v0.min_i32(v1).xyxy(v0.max_i32(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);
FillDrawCommand(cmd, m_render_command);
u32 buffer_pos = 0;
const GPUVertexPosition start_vp{m_blit_buffer[buffer_pos++]};
cmd->vertices[0].x = start_vp.x + m_drawing_offset.x;
cmd->vertices[0].y = start_vp.y + m_drawing_offset.y;
cmd->vertices[0].color = m_render_command.color_for_first_vertex;
const bool shaded = m_render_command.shading_enable;
for (u32 i = 1; i < num_vertices; i++)
{
cmd->vertices[i].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++]};
cmd->vertices[i].x = m_drawing_offset.x + vp.x;
cmd->vertices[i].y = m_drawing_offset.y + vp.y;
const GSVector4i v0 = GSVector4i::loadl(&cmd->vertices[0].x);
const GSVector4i v1 = GSVector4i::loadl(&cmd->vertices[1].x);
const GSVector4i rect = v0.min_i32(v1).xyxy(v0.max_i32(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[i - 1].x,
cmd->vertices[i - 1].y, cmd->vertices[i].x, cmd->vertices[i].y);
return;
}
else
{
AddDrawLineTicks(clamped_rect, rc.shading_enable);
}
}
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()
{
std::unique_ptr<GPU_SW> gpu(std::make_unique<GPU_SW>());
if (!gpu->Initialize())
return nullptr;
return gpu;
return std::make_unique<GPU_SW>();
}

View File

@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "gpu.h"
#include "gpu_sw_backend.h"
#include "gpu_backend.h"
#include "util/gpu_device.h"
@ -13,36 +14,42 @@
#include <memory>
#include <vector>
namespace Threading {
class Thread;
}
class GPUTexture;
class GPU_SW final : public GPU
// TODO: Move to cpp
// TODO: Rename to GPUSWBackend, preserved to avoid conflicts.
class GPU_SW final : public GPUBackend
{
public:
GPU_SW();
~GPU_SW() override;
ALWAYS_INLINE const GPU_SW_Backend& GetBackend() const { return m_backend; }
bool Initialize(bool clear_vram, Error* error) override;
const Threading::Thread* GetSWThread() const override;
bool IsHardwareRenderer() const override;
std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) const override;
std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) const override;
bool Initialize() override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
void Reset(bool clear_vram) override;
void UpdateSettings(const Settings& old_settings) override;
void UpdateResolutionScale() override;
protected:
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void FlushRender() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void RestoreDeviceContext() override;
bool DoState(GPUTexture** host_texture, bool is_reading, bool update_display) override;
void ClearVRAM() override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) 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(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
private:
template<GPUTexture::Format display_format>
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
@ -51,19 +58,11 @@ protected:
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
void UpdateDisplay() override;
void DispatchRenderCommand() override;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
void SetDisplayTextureFormat();
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::RGB565;
GPUTexture::Format m_24bit_display_format = GPUTexture::Format::RGBA8;
std::unique_ptr<GPUTexture> m_upload_texture;
GPU_SW_Backend m_backend;
};

View File

@ -1,227 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR 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 force_thread)
{
GPU_SW_Rasterizer::SelectImplementation();
return GPUBackend::Initialize(force_thread);
}
void GPU_SW_Backend::Reset()
{
GPUBackend::Reset();
}
void GPU_SW_Backend::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const bool dithering_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable, dithering_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, cmd->IsDitheringEnabled());
for (u16 i = 1; i < cmd->num_vertices; i++)
DrawFunction(cmd, &cmd->vertices[i - 1], &cmd->vertices[i]);
}
void GPU_SW_Backend::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params)
{
const u16 color16 = VRAMRGBA8888ToRGBA5551(color);
if ((x + width) <= VRAM_WIDTH && !params.interlaced_rendering)
{
for (u32 yoffs = 0; yoffs < height; yoffs++)
{
const u32 row = (y + yoffs) % VRAM_HEIGHT;
std::fill_n(&g_vram[row * VRAM_WIDTH + x], width, color16);
}
}
else if (params.interlaced_rendering)
{
// Hardware tests show that fills seem to break on the first two lines when the offset matches the displayed field.
const u32 active_field = params.active_line_lsb;
for (u32 yoffs = 0; yoffs < height; yoffs++)
{
const u32 row = (y + yoffs) % VRAM_HEIGHT;
if ((row & u32(1)) == active_field)
continue;
u16* row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++)
{
const u32 col = (x + xoffs) % VRAM_WIDTH;
row_ptr[col] = color16;
}
}
}
else
{
for (u32 yoffs = 0; yoffs < height; yoffs++)
{
const u32 row = (y + yoffs) % VRAM_HEIGHT;
u16* row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++)
{
const u32 col = (x + xoffs) % VRAM_WIDTH;
row_ptr[col] = color16;
}
}
}
}
void GPU_SW_Backend::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data,
GPUBackendCommandParameters params)
{
// Fast path when the copy is not oversized.
if ((x + width) <= VRAM_WIDTH && (y + height) <= VRAM_HEIGHT && !params.IsMaskingEnabled())
{
const u16* src_ptr = static_cast<const u16*>(data);
u16* dst_ptr = &g_vram[y * VRAM_WIDTH + x];
for (u32 yoffs = 0; yoffs < height; yoffs++)
{
std::copy_n(src_ptr, width, dst_ptr);
src_ptr += width;
dst_ptr += VRAM_WIDTH;
}
}
else
{
// Slow path when we need to handle wrap-around.
const u16* src_ptr = static_cast<const u16*>(data);
const u16 mask_and = params.GetMaskAND();
const u16 mask_or = params.GetMaskOR();
for (u32 row = 0; row < height;)
{
u16* dst_row_ptr = &g_vram[((y + row++) % VRAM_HEIGHT) * VRAM_WIDTH];
for (u32 col = 0; col < width;)
{
// TODO: Handle unaligned reads...
u16* pixel_ptr = &dst_row_ptr[(x + col++) % VRAM_WIDTH];
if (((*pixel_ptr) & mask_and) == 0)
*pixel_ptr = *(src_ptr++) | mask_or;
}
}
}
}
void GPU_SW_Backend::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params)
{
// Break up oversized copies. This behavior has not been verified on console.
if ((src_x + width) > VRAM_WIDTH || (dst_x + width) > VRAM_WIDTH)
{
u32 remaining_rows = height;
u32 current_src_y = src_y;
u32 current_dst_y = dst_y;
while (remaining_rows > 0)
{
const u32 rows_to_copy =
std::min<u32>(remaining_rows, std::min<u32>(VRAM_HEIGHT - current_src_y, VRAM_HEIGHT - current_dst_y));
u32 remaining_columns = width;
u32 current_src_x = src_x;
u32 current_dst_x = dst_x;
while (remaining_columns > 0)
{
const u32 columns_to_copy =
std::min<u32>(remaining_columns, std::min<u32>(VRAM_WIDTH - current_src_x, VRAM_WIDTH - current_dst_x));
CopyVRAM(current_src_x, current_src_y, current_dst_x, current_dst_y, columns_to_copy, rows_to_copy, params);
current_src_x = (current_src_x + columns_to_copy) % VRAM_WIDTH;
current_dst_x = (current_dst_x + columns_to_copy) % VRAM_WIDTH;
remaining_columns -= columns_to_copy;
}
current_src_y = (current_src_y + rows_to_copy) % VRAM_HEIGHT;
current_dst_y = (current_dst_y + rows_to_copy) % VRAM_HEIGHT;
remaining_rows -= rows_to_copy;
}
return;
}
// This doesn't have a fast path, but do we really need one? It's not common.
const u16 mask_and = params.GetMaskAND();
const u16 mask_or = params.GetMaskOR();
// Copy in reverse when src_x < dst_x, this is verified on console.
if (src_x < dst_x || ((src_x + width - 1) % VRAM_WIDTH) < ((dst_x + width - 1) % VRAM_WIDTH))
{
for (u32 row = 0; row < height; row++)
{
const u16* src_row_ptr = &g_vram[((src_y + row) % VRAM_HEIGHT) * VRAM_WIDTH];
u16* dst_row_ptr = &g_vram[((dst_y + row) % VRAM_HEIGHT) * VRAM_WIDTH];
for (s32 col = static_cast<s32>(width - 1); col >= 0; col--)
{
const u16 src_pixel = src_row_ptr[(src_x + static_cast<u32>(col)) % VRAM_WIDTH];
u16* dst_pixel_ptr = &dst_row_ptr[(dst_x + static_cast<u32>(col)) % VRAM_WIDTH];
if ((*dst_pixel_ptr & mask_and) == 0)
*dst_pixel_ptr = src_pixel | mask_or;
}
}
}
else
{
for (u32 row = 0; row < height; row++)
{
const u16* src_row_ptr = &g_vram[((src_y + row) % VRAM_HEIGHT) * VRAM_WIDTH];
u16* dst_row_ptr = &g_vram[((dst_y + row) % VRAM_HEIGHT) * VRAM_WIDTH];
for (u32 col = 0; col < width; col++)
{
const u16 src_pixel = src_row_ptr[(src_x + col) % VRAM_WIDTH];
u16* dst_pixel_ptr = &dst_row_ptr[(dst_x + col) % VRAM_WIDTH];
if ((*dst_pixel_ptr & mask_and) == 0)
*dst_pixel_ptr = src_pixel | mask_or;
}
}
}
}
void GPU_SW_Backend::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU::ReadCLUT(g_gpu_clut, reg, clut_is_8bit);
}
void GPU_SW_Backend::DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area)
{
GPU_SW_Rasterizer::g_drawing_area = new_drawing_area;
}
void GPU_SW_Backend::FlushRender()
{
}

View File

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

View File

@ -36,6 +36,32 @@ constinit const DitherLUT g_dither_lut = []() constexpr {
GPUDrawingArea g_drawing_area = {};
} // namespace GPU_SW_Rasterizer
void GPU_SW_Rasterizer::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
u16* const dest = g_gpu_clut;
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(dest, &src_row[start_x], sizeof(u16) * end);
std::memcpy(dest + end, src_row, sizeof(u16) * start);
}
else
{
std::memcpy(dest, &src_row[start_x], sizeof(u16) * 256);
}
}
}
// Default implementation definitions.
namespace GPU_SW_Rasterizer {
#include "gpu_sw_rasterizer.inl"

View File

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

View File

@ -809,7 +809,7 @@ ALWAYS_INLINE_RELEASE static void AddIDeltas_DY(i_group& ig, const i_deltas& idl
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable,
bool dithering_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,
i_group ig, const i_deltas& idl)
{
if (cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (Truncate8(static_cast<u32>(y)) & 1u))
@ -988,7 +988,7 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* cmd, s32
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable,
bool dithering_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)
{
#if 0

862
src/core/gpu_thread.cpp Normal file
View File

@ -0,0 +1,862 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gpu_thread.h"
#include "fullscreen_ui.h"
#include "gpu_backend.h"
#include "host.h"
#include "imgui_overlays.h"
#include "settings.h"
#include "shader_cache_version.h"
#include "system.h"
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/postprocessing.h"
#include "util/state_wrapper.h"
#include "common/align.h"
#include "common/error.h"
#include "common/log.h"
#include "common/threading.h"
#include "common/timer.h"
#include "IconsFontAwesome5.h"
#include "imgui.h"
#include <optional>
Log_SetChannel(GPUThread);
namespace GPUThread {
enum : u32
{
COMMAND_QUEUE_SIZE = 4 * 1024 * 1024,
THRESHOLD_TO_WAKE_GPU = 256
};
/// Starts the thread, if it hasn't already been started.
/// TODO: Persist thread
static bool Start(std::optional<GPURenderer> api, Error* error);
static void RunGPULoop();
static u32 GetPendingCommandSize();
static void WakeGPUThread();
static bool CreateDeviceOnThread(RenderAPI api, Error* error);
static void DestroyDeviceOnThread();
static void CreateGPUBackendOnThread(bool initialize_vram);
static void ChangeGPUBackendOnThread();
static void DestroyGPUBackendOnThread();
static void UpdateSettingsOnThread(const Settings& old_settings);
static void UpdateVSyncOnThread();
static void UpdatePerformanceCountersOnThread();
// TODO: Pack this crap together, don't trust LTO...
static RenderAPI s_render_api = RenderAPI::None;
static std::unique_ptr<GPUBackend> s_gpu_backend;
static std::optional<GPURenderer> s_requested_renderer;
static bool s_start_fullscreen_ui = false;
static GPUVSyncMode s_requested_vsync = GPUVSyncMode::Disabled;
static bool s_requested_allow_present_throttle = false;
static bool s_last_frame_skipped = false;
static Common::Timer::Value s_last_performance_counter_update_time = 0;
static u32 s_presents_since_last_update = 0;
static float s_accumulated_gpu_time = 0.0f;
static float s_average_gpu_time = 0.0f;
static float s_gpu_usage = 0.0f;
static Threading::KernelSemaphore m_sync_semaphore;
static Threading::Thread m_gpu_thread;
static Error s_open_error;
static std::atomic_bool s_open_flag{false};
static std::atomic_bool s_shutdown_flag{false};
static std::atomic_bool s_run_idle_flag{false};
static std::atomic_flag s_performance_counters_updated = ATOMIC_FLAG_INIT;
static Threading::WorkSema s_work_sema;
static FixedHeapArray<u8, COMMAND_QUEUE_SIZE> m_command_fifo_data;
alignas(HOST_CACHE_LINE_SIZE) static std::atomic<u32> m_command_fifo_read_ptr{0};
alignas(HOST_CACHE_LINE_SIZE) static std::atomic<u32> m_command_fifo_write_ptr{0};
} // namespace GPUThread
const Threading::ThreadHandle& GPUThread::GetThreadHandle()
{
return m_gpu_thread;
}
RenderAPI GPUThread::GetRenderAPI()
{
std::atomic_thread_fence(std::memory_order_acquire);
return s_render_api;
}
bool GPUThread::IsStarted()
{
return m_gpu_thread.Joinable();
}
bool GPUThread::WasFullscreenUIRequested()
{
return s_start_fullscreen_ui;
}
bool GPUThread::Start(std::optional<GPURenderer> renderer, Error* error)
{
Assert(!IsStarted());
INFO_LOG("Starting GPU thread...");
s_requested_renderer = renderer;
g_gpu_settings = g_settings;
s_last_performance_counter_update_time = Common::Timer::GetCurrentValue();
s_presents_since_last_update = 0;
s_average_gpu_time = 0.0f;
s_gpu_usage = 0.0f;
GPUBackend::ResetStatistics();
s_shutdown_flag.store(false, std::memory_order_release);
s_run_idle_flag.store(false, std::memory_order_release);
m_gpu_thread.Start(&GPUThread::RunGPULoop);
m_sync_semaphore.Wait();
if (!s_open_flag.load(std::memory_order_acquire))
{
ERROR_LOG("Failed to create GPU thread.");
if (error)
*error = s_open_error;
m_gpu_thread.Join();
return false;
}
VERBOSE_LOG("GPU thread started.");
return true;
}
bool GPUThread::StartFullscreenUI(Error* error)
{
// NOTE: Racey read.
if (FullscreenUI::IsInitialized())
return true;
if (IsStarted())
{
RunOnThread([]() {
// TODO: Error handling.
if (!FullscreenUI::Initialize())
Panic("Failed");
});
return true;
}
s_start_fullscreen_ui = true;
if (!Start(std::nullopt, error))
{
s_start_fullscreen_ui = false;
return false;
}
return true;
}
std::optional<GPURenderer> GPUThread::GetRequestedRenderer()
{
return s_requested_renderer;
}
bool GPUThread::CreateGPUBackend(GPURenderer renderer, Error* error)
{
if (IsStarted())
{
s_requested_renderer = renderer;
std::atomic_thread_fence(std::memory_order_release);
PushCommandAndSync(AllocateCommand(GPUBackendCommandType::ChangeBackend, sizeof(GPUThreadCommand)), false);
return true;
}
else
{
return Start(renderer, error);
}
}
bool GPUThread::SwitchGPUBackend(GPURenderer renderer, bool force_recreate_device, Error* error)
{
if (!force_recreate_device)
{
s_requested_renderer = renderer;
std::atomic_thread_fence(std::memory_order_release);
PushCommandAndSync(AllocateCommand(GPUBackendCommandType::ChangeBackend, sizeof(GPUThreadCommand)), false);
return true;
}
const bool was_running_fsui = s_start_fullscreen_ui;
Shutdown();
s_requested_renderer = renderer;
s_start_fullscreen_ui = was_running_fsui;
if (!Start(renderer, error))
{
s_requested_renderer.reset();
s_start_fullscreen_ui = false;
return false;
}
return true;
}
void GPUThread::DestroyGPUBackend()
{
if (!IsStarted())
return;
if (s_start_fullscreen_ui)
{
VERBOSE_LOG("Keeping GPU thread open for fullscreen UI");
s_requested_renderer.reset();
std::atomic_thread_fence(std::memory_order_release);
PushCommandAndSync(AllocateCommand(GPUBackendCommandType::ChangeBackend, sizeof(GPUThreadCommand)), false);
return;
}
Shutdown();
}
void GPUThread::Shutdown()
{
if (!IsStarted())
return;
s_shutdown_flag.store(true, std::memory_order_release);
s_start_fullscreen_ui = false;
s_requested_renderer.reset();
WakeGPUThread();
m_gpu_thread.Join();
INFO_LOG("GPU thread stopped.");
}
GPUThreadCommand* GPUThread::AllocateCommand(GPUBackendCommandType command, u32 size)
{
// Ensure size is a multiple of 4 so we don't end up with an unaligned command.
size = Common::AlignUpPow2(size, 4);
for (;;)
{
u32 read_ptr = m_command_fifo_read_ptr.load(std::memory_order_acquire);
u32 write_ptr = m_command_fifo_write_ptr.load(std::memory_order_relaxed);
if (read_ptr > write_ptr)
{
u32 available_size = read_ptr - write_ptr;
while (available_size < (size + sizeof(GPUBackendCommandType)))
{
WakeGPUThread();
read_ptr = m_command_fifo_read_ptr.load(std::memory_order_acquire);
available_size = (read_ptr > write_ptr) ? (read_ptr - write_ptr) : (COMMAND_QUEUE_SIZE - write_ptr);
}
}
else
{
const u32 available_size = COMMAND_QUEUE_SIZE - write_ptr;
if ((size + sizeof(GPUBackendCommand)) > available_size)
{
// allocate a dummy command to wrap the buffer around
GPUBackendCommand* dummy_cmd = reinterpret_cast<GPUBackendCommand*>(&m_command_fifo_data[write_ptr]);
dummy_cmd->type = GPUBackendCommandType::Wraparound;
dummy_cmd->size = available_size;
dummy_cmd->params.bits = 0;
m_command_fifo_write_ptr.store(0, std::memory_order_release);
continue;
}
}
GPUThreadCommand* cmd = reinterpret_cast<GPUThreadCommand*>(&m_command_fifo_data[write_ptr]);
cmd->type = command;
cmd->size = size;
return cmd;
}
}
u32 GPUThread::GetPendingCommandSize()
{
const u32 read_ptr = m_command_fifo_read_ptr.load();
const u32 write_ptr = m_command_fifo_write_ptr.load();
return (write_ptr >= read_ptr) ? (write_ptr - read_ptr) : (COMMAND_QUEUE_SIZE - read_ptr + write_ptr);
}
void GPUThread::PushCommand(GPUThreadCommand* cmd)
{
const u32 new_write_ptr = m_command_fifo_write_ptr.fetch_add(cmd->size, std::memory_order_release) + cmd->size;
DebugAssert(new_write_ptr <= COMMAND_QUEUE_SIZE);
UNREFERENCED_VARIABLE(new_write_ptr);
if (GetPendingCommandSize() >= THRESHOLD_TO_WAKE_GPU)
WakeGPUThread();
}
void GPUThread::PushCommandAndWakeThread(GPUThreadCommand* cmd)
{
const u32 new_write_ptr = m_command_fifo_write_ptr.fetch_add(cmd->size, std::memory_order_release) + cmd->size;
DebugAssert(new_write_ptr <= COMMAND_QUEUE_SIZE);
UNREFERENCED_VARIABLE(new_write_ptr);
WakeGPUThread();
}
void GPUThread::PushCommandAndSync(GPUThreadCommand* cmd, bool spin)
{
const u32 new_write_ptr = m_command_fifo_write_ptr.fetch_add(cmd->size, std::memory_order_release) + cmd->size;
DebugAssert(new_write_ptr <= COMMAND_QUEUE_SIZE);
UNREFERENCED_VARIABLE(new_write_ptr);
WakeGPUThread();
if (spin)
s_work_sema.WaitForEmptyWithSpin();
else
s_work_sema.WaitForEmpty();
}
void GPUThread::WakeGPUThread()
{
s_work_sema.NotifyOfWork();
}
void GPUThread::RunGPULoop()
{
Threading::SetNameOfCurrentThread("GPUThread");
if (!CreateDeviceOnThread(
Settings::GetRenderAPIForRenderer(s_requested_renderer.value_or(g_gpu_settings.gpu_renderer)), &s_open_error))
{
Host::ReleaseRenderWindow();
s_open_flag.store(false, std::memory_order_release);
m_sync_semaphore.Post();
return;
}
CreateGPUBackendOnThread(true);
s_open_flag.store(true, std::memory_order_release);
m_sync_semaphore.Post();
for (;;)
{
u32 write_ptr = m_command_fifo_write_ptr.load(std::memory_order_acquire);
u32 read_ptr = m_command_fifo_read_ptr.load(std::memory_order_relaxed);
if (read_ptr == write_ptr)
{
if (s_shutdown_flag.load(std::memory_order_relaxed))
{
break;
}
else if (s_run_idle_flag.load(std::memory_order_relaxed))
{
if (!s_work_sema.CheckForWork())
{
Internal::PresentFrame(false, 0);
if (!g_gpu_device->IsVSyncModeBlocking())
g_gpu_device->ThrottlePresentation();
continue;
}
// we should have something to do, since we got woken...
}
else
{
s_work_sema.WaitForWork();
continue;
}
}
write_ptr = (write_ptr < read_ptr) ? COMMAND_QUEUE_SIZE : write_ptr;
while (read_ptr < write_ptr)
{
GPUThreadCommand* cmd = reinterpret_cast<GPUThreadCommand*>(&m_command_fifo_data[read_ptr]);
DebugAssert((read_ptr + cmd->size) <= COMMAND_QUEUE_SIZE);
read_ptr += cmd->size;
switch (cmd->type)
{
case GPUBackendCommandType::Wraparound:
{
DebugAssert(read_ptr == COMMAND_QUEUE_SIZE);
write_ptr = m_command_fifo_write_ptr.load(std::memory_order_acquire);
read_ptr = 0;
// let the CPU thread know as early as possible that we're here
m_command_fifo_read_ptr.store(read_ptr, std::memory_order_release);
}
break;
case GPUBackendCommandType::AsyncCall:
{
GPUThreadAsyncCallCommand* acmd = static_cast<GPUThreadAsyncCallCommand*>(cmd);
acmd->func();
acmd->~GPUThreadAsyncCallCommand();
}
break;
case GPUBackendCommandType::ChangeBackend:
{
ChangeGPUBackendOnThread();
}
break;
case GPUBackendCommandType::UpdateVSync:
{
UpdateVSyncOnThread();
}
break;
default:
{
DebugAssert(s_gpu_backend);
s_gpu_backend->HandleCommand(cmd);
}
break;
}
}
m_command_fifo_read_ptr.store(read_ptr, std::memory_order_release);
}
DestroyGPUBackendOnThread();
DestroyDeviceOnThread();
Host::ReleaseRenderWindow();
}
bool GPUThread::CreateDeviceOnThread(RenderAPI api, 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<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_texture_copy_to_self)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
Error create_error;
if (!g_gpu_device ||
!g_gpu_device->Create(
g_gpu_settings.gpu_adapter,
g_gpu_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_gpu_settings.gpu_use_debug_device, s_requested_vsync,
s_requested_allow_present_throttle, g_gpu_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
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));
s_render_api = RenderAPI::None;
std::atomic_thread_fence(std::memory_order_release);
return false;
}
if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_show_osd_messages,
&create_error) ||
(s_start_fullscreen_ui && !FullscreenUI::Initialize()))
{
ERROR_LOG("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
FullscreenUI::Shutdown();
ImGuiManager::Shutdown();
g_gpu_device->Destroy();
g_gpu_device.reset();
s_render_api = RenderAPI::None;
std::atomic_thread_fence(std::memory_order_release);
return false;
}
s_accumulated_gpu_time = 0.0f;
s_presents_since_last_update = 0;
s_render_api = g_gpu_device->GetRenderAPI();
std::atomic_thread_fence(std::memory_order_release);
return true;
}
void GPUThread::DestroyDeviceOnThread()
{
if (!g_gpu_device)
return;
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();
}
void GPUThread::CreateGPUBackendOnThread(bool clear_vram)
{
Assert(!s_gpu_backend);
if (!s_requested_renderer.has_value())
return;
const bool is_hardware = (s_requested_renderer.value() != GPURenderer::Software);
if (is_hardware)
s_gpu_backend = GPUBackend::CreateHardwareBackend();
else
s_gpu_backend = GPUBackend::CreateSoftwareBackend();
Error error;
DebugAssert(s_gpu_backend);
if (!s_gpu_backend->Initialize(clear_vram, &error))
{
ERROR_LOG("Failed to create {} renderer: {}", Settings::GetRendererName(s_requested_renderer.value()),
error.GetDescription());
if (is_hardware)
{
Host::AddIconOSDMessage(
"GPUBackendCreationFailed", ICON_FA_PAINT_ROLLER,
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to initialize {} renderer, falling back to software renderer."),
Settings::GetRendererName(s_requested_renderer.value())),
Host::OSD_CRITICAL_ERROR_DURATION);
s_requested_renderer = GPURenderer::Software;
s_gpu_backend = GPUBackend::CreateSoftwareBackend();
if (!s_gpu_backend)
Panic("Failed to initialize software backend.");
}
}
}
ALWAYS_INLINE_RELEASE void GPUThread::ChangeGPUBackendOnThread()
{
std::atomic_thread_fence(std::memory_order_acquire);
if (!s_requested_renderer.has_value())
{
if (s_gpu_backend)
DestroyGPUBackendOnThread();
return;
}
// Readback old VRAM for hardware renderers.
s_gpu_backend->ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
if (s_requested_renderer.value() == GPURenderer::Software)
{
// Just recreate the backend, software works with everything.
DestroyGPUBackendOnThread();
CreateGPUBackendOnThread(false);
return;
}
DestroyGPUBackendOnThread();
DebugAssert(g_gpu_device);
const RenderAPI current_api = s_render_api;
const RenderAPI expected_api = Settings::GetRenderAPIForRenderer(s_requested_renderer.value());
if (!GPUDevice::IsSameRenderAPI(current_api, expected_api))
{
WARNING_LOG("Recreating GPU device, expecting {} got {}", GPUDevice::RenderAPIToString(expected_api),
GPUDevice::RenderAPIToString(current_api));
DestroyDeviceOnThread();
// Things tend to break when you don't recreate the window, after switching APIs.
Host::ReleaseRenderWindow();
Error error;
if (!CreateDeviceOnThread(expected_api, &error))
{
Host::AddIconOSDMessage(
"DeviceSwitchFailed", ICON_FA_PAINT_ROLLER,
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to create {} GPU device, reverting to {}.\n{}"),
GPUDevice::RenderAPIToString(expected_api), GPUDevice::RenderAPIToString(current_api),
error.GetDescription()),
Host::OSD_CRITICAL_ERROR_DURATION);
Host::ReleaseRenderWindow();
if (!CreateDeviceOnThread(current_api, &error))
Panic("Failed to switch back to old API after creation failure");
}
}
CreateGPUBackendOnThread(false);
}
void GPUThread::DestroyGPUBackendOnThread()
{
if (!s_gpu_backend)
return;
VERBOSE_LOG("Shutting down GPU backend...");
PostProcessing::Shutdown();
s_gpu_backend.reset();
}
void GPUThread::UpdateSettingsOnThread(const Settings& old_settings)
{
if (g_gpu_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage ||
g_gpu_settings.display_show_gpu_stats != old_settings.display_show_gpu_stats)
{
s_performance_counters_updated.clear(std::memory_order_relaxed);
s_last_performance_counter_update_time = Common::Timer::GetCurrentValue();
s_presents_since_last_update = 0;
}
if (g_gpu_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage)
{
s_accumulated_gpu_time = 0.0f;
s_average_gpu_time = 0.0f;
s_gpu_usage = 0.0f;
}
if (s_gpu_backend)
s_gpu_backend->UpdateSettings(old_settings);
}
void GPUThread::UpdateVSyncOnThread()
{
std::atomic_thread_fence(std::memory_order_acquire);
g_gpu_device->SetVSyncMode(s_requested_vsync, s_requested_allow_present_throttle);
}
void GPUThread::RunOnThread(AsyncCallType func)
{
GPUThreadAsyncCallCommand* cmd = static_cast<GPUThreadAsyncCallCommand*>(
AllocateCommand(GPUBackendCommandType::AsyncCall, sizeof(GPUThreadAsyncCallCommand)));
new (cmd) GPUThreadAsyncCallCommand;
cmd->func = std::move(func);
PushCommandAndWakeThread(cmd);
}
void GPUThread::UpdateSettings()
{
AssertMsg(IsStarted(), "GPU Thread is running");
RunOnThread([settings = g_settings]() {
VERBOSE_LOG("Updating GPU settings on thread...");
Settings old_settings = std::move(g_gpu_settings);
g_gpu_settings = std::move(settings);
UpdateSettingsOnThread(old_settings);
});
}
void GPUThread::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
AssertMsg(IsStarted(), "GPU Thread is running");
RunOnThread([width, height, scale]() {
if (!g_gpu_device)
return;
DEV_LOG("Display window resized to {}x{}", width, height);
g_gpu_device->ResizeWindow(width, height, scale);
ImGuiManager::WindowResized();
// If we're paused, re-present the current frame at the new window size.
if (System::IsValid())
{
if (System::IsPaused())
{
// Hackity hack, on some systems, presenting a single frame isn't enough to actually get it
// displayed. Two seems to be good enough. Maybe something to do with direct scanout.
PresentCurrentFrame();
PresentCurrentFrame();
}
}
});
// TODO: The window size for GTE and stuff isn't going to be correct here.
System::HostDisplayResized();
}
void GPUThread::UpdateDisplayWindow()
{
AssertMsg(IsStarted(), "MTGS is running");
RunOnThread([]() {
if (!g_gpu_device)
return;
if (!g_gpu_device->UpdateWindow())
{
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
return;
}
ImGuiManager::WindowResized();
if (System::IsValid())
{
// Fix up vsync etc.
System::UpdateSpeedLimiterState();
// If we're paused, re-present the current frame at the new window size.
if (System::IsPaused())
PresentCurrentFrame();
}
});
}
void GPUThread::SetVSync(GPUVSyncMode mode, bool allow_present_throttle)
{
Assert(IsStarted());
if (s_requested_vsync == mode && s_requested_allow_present_throttle == allow_present_throttle)
return;
s_requested_vsync = mode;
s_requested_allow_present_throttle = allow_present_throttle;
std::atomic_thread_fence(std::memory_order_release);
PushCommandAndWakeThread(AllocateCommand(GPUBackendCommandType::UpdateVSync, sizeof(GPUThreadCommand)));
}
void GPUThread::PresentCurrentFrame()
{
if (s_run_idle_flag.load(std::memory_order_relaxed))
{
// If we're running idle, we're going to re-present anyway.
return;
}
RunOnThread([]() { Internal::PresentFrame(false, 0); });
}
void GPUThread::Internal::PresentFrame(bool allow_skip_present, Common::Timer::Value present_time)
{
// Make sure the GPU is flushed, otherwise the VB might still be mapped.
// TODO: Make this suck less...
if (s_gpu_backend)
s_gpu_backend->FlushRender();
s_presents_since_last_update++;
if (!s_performance_counters_updated.test_and_set(std::memory_order_acq_rel))
UpdatePerformanceCountersOnThread();
const bool skip_present = (allow_skip_present && g_gpu_device->ShouldSkipPresentingFrame());
const bool explicit_present = (present_time != 0 && g_gpu_device->GetFeatures().explicit_present);
// TODO: Maybe?
//(present_time != 0 && Common::Timer::GetCurrentValue() > present_time && !s_last_frame_skipped)));
// acquire for IO.MousePos.
std::atomic_thread_fence(std::memory_order_acquire);
if (!skip_present)
{
FullscreenUI::Render();
ImGuiManager::RenderTextOverlays();
ImGuiManager::RenderOSDMessages();
if (System::GetState() == System::State::Running)
ImGuiManager::RenderSoftwareCursors();
}
// Debug windows are always rendered, otherwise mouse input breaks on skip.
ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows();
if (s_gpu_backend && !skip_present)
s_last_frame_skipped = !s_gpu_backend->PresentDisplay();
else
s_last_frame_skipped = !g_gpu_device->BeginPresent(skip_present);
if (!s_last_frame_skipped)
{
g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(explicit_present);
if (g_gpu_device->IsGPUTimingEnabled())
s_accumulated_gpu_time += g_gpu_device->GetAndResetAccumulatedGPUTime();
if (explicit_present)
{
// See note in System::Throttle().
#if !defined(__linux__) && !defined(__ANDROID__)
Common::Timer::SleepUntil(present_time, false);
#else
Common::Timer::SleepUntil(present_time, true);
#endif
g_gpu_device->SubmitPresent();
}
}
else
{
// Still need to kick ImGui or it gets cranky.
ImGui::Render();
}
ImGuiManager::NewFrame();
if (s_gpu_backend)
s_gpu_backend->RestoreDeviceContext();
}
void GPUThread::SetRunIdle(bool enabled)
{
s_run_idle_flag.store(enabled, std::memory_order_release);
DEV_LOG("GPU thread now {} idle", enabled ? "running" : "NOT running");
}
float GPUThread::GetGPUUsage()
{
return s_gpu_usage;
}
float GPUThread::GetGPUAverageTime()
{
return s_average_gpu_time;
}
void GPUThread::SetPerformanceCounterUpdatePending()
{
s_performance_counters_updated.clear(std::memory_order_release);
}
void GPUThread::UpdatePerformanceCountersOnThread()
{
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
const u32 frames = std::exchange(s_presents_since_last_update, 0);
const float time = static_cast<float>(Common::Timer::ConvertValueToSeconds(
current_time - std::exchange(s_last_performance_counter_update_time, current_time)));
if (g_gpu_device->IsGPUTimingEnabled())
{
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(std::max(frames, 1u));
s_gpu_usage = static_cast<float>(s_accumulated_gpu_time / (time * 10.0));
s_accumulated_gpu_time = 0.0f;
}
if (g_settings.display_show_gpu_stats)
GPUBackend::UpdateStatistics(frames);
}

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

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "gpu_types.h"
#include "common/threading.h"
#include <optional>
class Error;
enum class RenderAPI : u32;
enum class GPUVSyncMode : u8;
namespace GPUThread {
using AsyncCallType = std::function<void()>;
const Threading::ThreadHandle& GetThreadHandle();
RenderAPI GetRenderAPI();
bool IsStarted();
bool WasFullscreenUIRequested();
/// Starts Big Picture UI.
bool StartFullscreenUI(Error* error);
/// Backend control.
std::optional<GPURenderer> GetRequestedRenderer();
bool CreateGPUBackend(GPURenderer renderer, Error* error);
bool SwitchGPUBackend(GPURenderer renderer, bool force_recreate_device, Error* error);
void DestroyGPUBackend();
/// Fully stops the thread, closing in the process if needed.
void Shutdown();
/// 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();
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
void UpdateSettings();
void RunOnThread(AsyncCallType func);
void SetVSync(GPUVSyncMode mode, bool allow_present_throttle);
void SetRunIdle(bool enabled);
float GetGPUUsage();
float GetGPUAverageTime();
void SetPerformanceCounterUpdatePending();
GPUThreadCommand* AllocateCommand(GPUBackendCommandType command, u32 size);
void PushCommand(GPUThreadCommand* cmd);
void PushCommandAndWakeThread(GPUThreadCommand* cmd);
void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
// NOTE: Only called by GPUBackend
namespace Internal {
void PresentFrame(bool allow_skip_present, Common::Timer::Value present_time);
}
} // namespace GPUThread

View File

@ -2,11 +2,21 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "types.h"
#include "util/gpu_texture.h"
#include "common/bitfield.h"
#include "common/bitutils.h"
#include "common/gsvector.h"
#include "types.h"
#include "common/timer.h"
#include <array>
#include <functional>
#include <vector>
class StateWrapper;
enum : u32
{
@ -166,7 +176,7 @@ static constexpr s32 TruncateGPUVertexPosition(s32 x)
union GPUDrawModeReg
{
static constexpr u16 MASK = 0b1111111111111;
static constexpr u16 TEXTURE_PAGE_MASK = UINT16_C(0b0000000000011111);
static constexpr u16 TEXTURE_PAGE_AND_MODE_MASK = UINT16_C(0b0000000110011111);
// Polygon texpage commands only affect bits 0-8, 11
static constexpr u16 POLYGON_TEXPAGE_MASK = 0b0000100111111111;
@ -227,7 +237,9 @@ union GPUTexturePaletteReg
}
};
struct GPUTextureWindow
union GPUTextureWindow
{
struct
{
u8 and_x;
u8 and_y;
@ -235,6 +247,9 @@ struct GPUTextureWindow
u8 or_y;
};
u32 bits;
};
// 4x4 dither matrix.
static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-4, +0, -3, +1}, // row 0
{+2, -2, +3, -1}, // row 1
@ -249,17 +264,100 @@ static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-
enum class GPUBackendCommandType : u8
{
Wraparound,
Sync,
AsyncCall,
ChangeBackend,
UpdateVSync,
ClearVRAM,
DoState,
ClearDisplay,
UpdateDisplay,
ClearCache,
BufferSwapped,
FlushRender,
UpdateResolutionScale,
RenderScreenshotToBuffer,
ReadVRAM,
FillVRAM,
UpdateVRAM,
CopyVRAM,
SetDrawingArea,
UpdateCLUT,
DrawPolygon,
DrawPrecisePolygon,
DrawRectangle,
DrawLine,
};
struct GPUThreadCommand
{
u32 size;
GPUBackendCommandType type;
};
struct GPUThreadAsyncCallCommand : public GPUThreadCommand
{
std::function<void()> func;
};
struct GPUThreadRenderScreenshotToBufferCommand : public GPUThreadCommand
{
u32 width;
u32 height;
s32 draw_rect[4];
std::vector<u32>* out_pixels;
u32* out_stride;
GPUTexture::Format* out_format;
bool* out_result;
bool postfx;
};
struct GPUBackendDoStateCommand : public GPUThreadCommand
{
GPUTexture** host_texture;
bool is_reading;
bool update_display;
};
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;
u16 X; // TODO: Can we get rid of this?
union
{
u8 bits;
BitField<u8, bool, 0, 1> interlaced_display_enabled;
BitField<u8, u8, 1, 1> interlaced_display_field;
BitField<u8, bool, 2, 1> interlaced_display_interleaved;
BitField<u8, bool, 3, 1> display_24bit;
BitField<u8, bool, 4, 1> display_disabled;
BitField<u8, bool, 6, 1> allow_present_skip;
BitField<u8, bool, 7, 1> present_frame;
};
float display_aspect_ratio;
Common::Timer::Value present_time;
};
struct GPUBackendReadVRAMCommand : public GPUThreadCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
};
union GPUBackendCommandParameters
{
u8 bits;
@ -287,18 +385,12 @@ union GPUBackendCommandParameters
}
};
struct GPUBackendCommand
// TODO: Merge this into the other structs, saves padding bytes
struct GPUBackendCommand : public GPUThreadCommand
{
u32 size;
GPUBackendCommandType type;
GPUBackendCommandParameters params;
};
struct GPUBackendSyncCommand : public GPUBackendCommand
{
bool allow_sleep;
};
struct GPUBackendFillVRAMCommand : public GPUBackendCommand
{
u16 x;
@ -339,8 +431,10 @@ struct GPUBackendUpdateCLUTCommand : public GPUBackendCommand
bool clut_is_8bit;
};
// TODO: Pack texpage
struct GPUBackendDrawCommand : public GPUBackendCommand
{
// TODO: Cut this down
GPUDrawModeReg draw_mode;
GPURenderCommand rc;
GPUTexturePaletteReg palette;
@ -351,7 +445,7 @@ struct GPUBackendDrawCommand : public GPUBackendCommand
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
{
u16 num_vertices;
u8 num_vertices;
struct Vertex
{
@ -372,14 +466,22 @@ struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
};
u16 texcoord;
};
};
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_, u16 texcoord_)
Vertex vertices[0];
};
struct GPUBackendDrawPrecisePolygonCommand : public GPUBackendDrawCommand
{
x = x_;
y = y_;
color = color_;
texcoord = texcoord_;
}
u8 num_vertices;
bool valid_w;
struct Vertex
{
float x, y, w;
s32 native_x, native_y;
u32 color;
u16 texcoord;
};
Vertex vertices[0];
@ -387,9 +489,9 @@ struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
struct GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
{
s32 x, y;
u16 width, height;
u16 texcoord;
s32 x, y;
u32 color;
};

View File

@ -2,17 +2,11 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "host.h"
#include "fullscreen_ui.h"
#include "gpu.h"
#include "imgui_overlays.h"
#include "shader_cache_version.h"
#include "system.h"
#include "scmversion/scmversion.h"
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/layered_settings_interface.h"
@ -259,125 +253,3 @@ void Host::ReportFormattedDebuggerMessage(const char* format, ...)
ReportDebuggerMessage(message);
}
bool Host::CreateGPUDevice(RenderAPI api, 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<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_texture_copy_to_self)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
Error create_error;
if (!g_gpu_device || !g_gpu_device->Create(g_settings.gpu_adapter,
g_settings.gpu_disable_shader_cache ? std::string_view() :
std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device,
System::GetEffectiveVSyncMode(), System::ShouldAllowPresentThrottle(),
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
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_show_osd_messages,
&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();
return false;
}
return true;
}
void Host::UpdateDisplayWindow()
{
if (!g_gpu_device)
return;
if (!g_gpu_device->UpdateWindow())
{
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
return;
}
ImGuiManager::WindowResized();
if (System::IsValid())
{
// Fix up vsync etc.
System::UpdateSpeedLimiterState();
// If we're paused, re-present the current frame at the new window size.
if (System::IsPaused())
System::InvalidateDisplay();
}
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
if (!g_gpu_device)
return;
DEV_LOG("Display window resized to {}x{}", width, height);
g_gpu_device->ResizeWindow(width, height, scale);
ImGuiManager::WindowResized();
// If we're paused, re-present the current frame at the new window size.
if (System::IsValid())
{
if (System::IsPaused())
{
// Hackity hack, on some systems, presenting a single frame isn't enough to actually get it
// displayed. Two seems to be good enough. Maybe something to do with direct scanout.
System::InvalidateDisplay();
System::InvalidateDisplay();
}
System::HostDisplayResized();
}
}
void Host::ReleaseGPUDevice()
{
if (!g_gpu_device)
return;
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();
}

View File

@ -87,18 +87,6 @@ void DisplayLoadingScreen(const char* message, int progress_min = -1, int progre
/// Safely executes a function on the VM thread.
void RunOnCPUThread(std::function<void()> function, bool block = false);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api, Error* error);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow();
/// 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();

View File

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

View File

@ -9,6 +9,8 @@
#include "dma.h"
#include "fullscreen_ui.h"
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "host.h"
#include "mdec.h"
#include "resources.h"
@ -176,17 +178,17 @@ void ImGuiManager::RenderDebugWindows()
{
if (System::IsValid())
{
if (g_settings.debugging.show_gpu_state)
if (g_gpu_settings.debugging.show_gpu_state)
g_gpu->DrawDebugStateWindow();
if (g_settings.debugging.show_cdrom_state)
if (g_gpu_settings.debugging.show_cdrom_state)
CDROM::DrawDebugWindow();
if (g_settings.debugging.show_timers_state)
if (g_gpu_settings.debugging.show_timers_state)
Timers::DrawDebugStateWindow();
if (g_settings.debugging.show_spu_state)
if (g_gpu_settings.debugging.show_spu_state)
SPU::DrawDebugStateWindow();
if (g_settings.debugging.show_mdec_state)
if (g_gpu_settings.debugging.show_mdec_state)
MDEC::DrawDebugStateWindow();
if (g_settings.debugging.show_dma_state)
if (g_gpu_settings.debugging.show_dma_state)
DMA::DrawDebugStateWindow();
}
}
@ -194,14 +196,14 @@ void ImGuiManager::RenderDebugWindows()
void ImGuiManager::RenderTextOverlays()
{
const System::State state = System::GetState();
if (state != System::State::Shutdown)
if (state == System::State::Paused || state == System::State::Running)
{
DrawPerformanceOverlay();
if (g_settings.display_show_enhancements && state != System::State::Paused)
if (g_gpu_settings.display_show_enhancements && state != System::State::Paused)
DrawEnhancementsOverlay();
if (g_settings.display_show_inputs && state != System::State::Paused)
if (g_gpu_settings.display_show_inputs && state != System::State::Paused)
DrawInputsOverlay();
}
}
@ -219,9 +221,9 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub
void ImGuiManager::DrawPerformanceOverlay()
{
if (!(g_settings.display_show_fps || g_settings.display_show_speed || g_settings.display_show_gpu_stats ||
g_settings.display_show_resolution || g_settings.display_show_cpu_usage ||
(g_settings.display_show_status_indicators &&
if (!(g_gpu_settings.display_show_fps || g_gpu_settings.display_show_speed || g_gpu_settings.display_show_gpu_stats ||
g_gpu_settings.display_show_resolution || g_gpu_settings.display_show_cpu_usage ||
(g_gpu_settings.display_show_status_indicators &&
(System::IsPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
{
return;
@ -258,12 +260,12 @@ void ImGuiManager::DrawPerformanceOverlay()
if (state == System::State::Running)
{
const float speed = System::GetEmulationSpeed();
if (g_settings.display_show_fps)
if (g_gpu_settings.display_show_fps)
{
text.append_format("G: {:.2f} | V: {:.2f}", System::GetFPS(), System::GetVPS());
first = false;
}
if (g_settings.display_show_speed)
if (g_gpu_settings.display_show_speed)
{
text.append_format("{}{}%", first ? "" : " | ", static_cast<u32>(std::round(speed)));
@ -288,33 +290,33 @@ void ImGuiManager::DrawPerformanceOverlay()
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);
GPUBackend::GetStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
g_gpu->GetMemoryStatsString(text);
GPUBackend::GetMemoryStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_resolution)
if (g_gpu_settings.display_show_resolution)
{
// TODO: this seems wrong?
const auto [effective_width, effective_height] = g_gpu->GetEffectiveDisplayResolution();
const auto [src_width, src_height] = GPUBackend::GetLastDisplaySourceSize();
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
const bool pal = g_gpu->IsInPALMode();
text.format("{}x{} {} {}", effective_width, effective_height, pal ? "PAL" : "NTSC",
text.format("{}x{} {} {}", src_width, src_height, pal ? "PAL" : "NTSC",
interlaced ? "Interlaced" : "Progressive");
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_latency_stats)
if (g_gpu_settings.display_show_latency_stats)
{
System::FormatLatencyStats(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_cpu_usage)
if (g_gpu_settings.display_show_cpu_usage)
{
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", System::GetMinimumFrameTime(), System::GetAverageFrameTime(),
System::GetMaximumFrameTime());
@ -369,22 +371,19 @@ void ImGuiManager::DrawPerformanceOverlay()
FormatProcessorStat(text, System::GetCPUThreadUsage(), System::GetCPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
if (g_gpu->GetSWThread())
{
text.assign("SW: ");
FormatProcessorStat(text, System::GetSWThreadUsage(), System::GetSWThreadAverageTime());
text.assign("RNDR: ");
FormatProcessorStat(text, System::GetGPUThreadUsage(), System::GetGPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
}
if (g_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled())
if (g_gpu_settings.display_show_gpu_usage)
{
text.assign("GPU: ");
FormatProcessorStat(text, System::GetGPUUsage(), System::GetGPUAverageTime());
FormatProcessorStat(text, GPUThread::GetGPUUsage(), GPUThread::GetGPUAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
if (g_settings.display_show_status_indicators)
if (g_gpu_settings.display_show_status_indicators)
{
const bool rewinding = System::IsRewinding();
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
@ -394,7 +393,7 @@ void ImGuiManager::DrawPerformanceOverlay()
}
}
if (g_settings.display_show_frame_times)
if (g_gpu_settings.display_show_frame_times)
{
const ImVec2 history_size(200.0f * scale, 50.0f * scale);
ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y));
@ -455,7 +454,7 @@ void ImGuiManager::DrawPerformanceOverlay()
ImGui::PopStyleColor(3);
}
}
else if (g_settings.display_show_status_indicators && state == System::State::Paused &&
else if (g_gpu_settings.display_show_status_indicators && state == System::State::Paused &&
!FullscreenUI::HasActiveWindow())
{
text.assign(ICON_FA_PAUSE);
@ -470,7 +469,7 @@ void ImGuiManager::DrawEnhancementsOverlay()
LargeString text;
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
g_gpu->IsHardwareRenderer() ? "HW" : "SW");
GPUBackend::IsUsingHardwareBackend() ? "HW" : "SW");
if (g_settings.rewind_enable)
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
@ -784,7 +783,11 @@ void SaveStateSelectorUI::ClearList()
for (ListEntry& li : s_slots)
{
if (li.preview_texture)
g_gpu_device->RecycleTexture(std::move(li.preview_texture));
{
GPUThread::RunOnThread([tex = li.preview_texture.release()]() {
g_gpu_device->RecycleTexture(std::unique_ptr<GPUTexture>(tex));
});
}
}
s_slots.clear();
}
@ -1102,7 +1105,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
}
}
Close();
GPUThread::RunOnThread(&Close);
}
void SaveStateSelectorUI::SaveCurrentSlot()
@ -1119,7 +1122,7 @@ void SaveStateSelectorUI::SaveCurrentSlot()
}
}
Close();
GPUThread::RunOnThread(&Close);
}
void SaveStateSelectorUI::ShowSlotOSDMessage()
@ -1143,7 +1146,7 @@ void SaveStateSelectorUI::ShowSlotOSDMessage()
void ImGuiManager::RenderOverlayWindows()
{
const System::State state = System::GetState();
if (state != System::State::Shutdown)
if (state == System::State::Paused || state == System::State::Running)
{
if (SaveStateSelectorUI::s_open)
SaveStateSelectorUI::Draw();

View File

@ -25,6 +25,7 @@
Log_SetChannel(Settings);
Settings g_settings;
Settings g_gpu_settings;
const char* SettingInfo::StringDefaultValue() const
{
@ -191,7 +192,7 @@ void Settings::Load(SettingsInterface& si)
gpu_disable_texture_buffers = si.GetBoolValue("GPU", "DisableTextureBuffers", false);
gpu_disable_texture_copy_to_self = si.GetBoolValue("GPU", "DisableTextureCopyToSelf", false);
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
gpu_max_queued_frames = static_cast<u8>(si.GetUIntValue("GPU", "MaxQueuedFrames", DEFAULT_GPU_MAX_QUEUED_FRAMES));
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_threaded_presentation = si.GetBoolValue("GPU", "ThreadedPresentation", DEFAULT_THREADED_PRESENTATION);
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
@ -495,7 +496,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
}
si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading);
si.SetBoolValue("GPU", "UseThread", gpu_use_thread);
si.SetUIntValue("GPU", "MaxQueuedFrames", gpu_max_queued_frames);
si.SetBoolValue("GPU", "ThreadedPresentation", gpu_threaded_presentation);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "TrueColor", gpu_true_color);

View File

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

View File

@ -17,6 +17,8 @@
#include "game_database.h"
#include "game_list.h"
#include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "gte.h"
#include "host.h"
#include "host_interface_progress_callback.h"
@ -122,13 +124,12 @@ static void ClearRunningGame();
static void DestroySystem();
static std::string GetMediaPathFromSaveState(const char* path);
static bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state);
static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error);
static bool SaveUndoLoadState();
static void WarnAboutUnsafeSettings();
static void LogUnsafeSettingsToConsole(const SmallStringBase& messages);
/// Throttles the system, i.e. sleeps until it's time to execute the next frame.
static void Throttle(Common::Timer::Value current_time);
static void Throttle(Common::Timer::Value current_time, Common::Timer::Value sleep_until);
static void UpdatePerformanceCounters();
static void AccumulatePreFrameSleepTime();
static void UpdatePreFrameSleepTime();
@ -170,7 +171,6 @@ static std::string s_input_profile_name;
static System::State s_state = System::State::Shutdown;
static std::atomic_bool s_startup_cancelled{false};
static bool s_keep_gpu_device_on_shutdown = false;
static ConsoleRegion s_region = ConsoleRegion::NTSC_U;
TickCount System::g_ticks_per_second = System::MASTER_CLOCK;
@ -226,11 +226,8 @@ static float s_maximum_frame_time = 0.0f;
static float s_average_frame_time = 0.0f;
static float s_cpu_thread_usage = 0.0f;
static float s_cpu_thread_time = 0.0f;
static float s_sw_thread_usage = 0.0f;
static float s_sw_thread_time = 0.0f;
static float s_average_gpu_time = 0.0f;
static float s_accumulated_gpu_time = 0.0f;
static float s_gpu_usage = 0.0f;
static float s_gpu_thread_usage = 0.0f;
static float s_gpu_thread_time = 0.0f;
static System::FrameTimeHistory s_frame_time_history;
static u32 s_frame_time_history_pos = 0;
static u32 s_last_frame_number = 0;
@ -238,7 +235,6 @@ static u32 s_last_internal_frame_number = 0;
static u32 s_last_global_tick_counter = 0;
static u64 s_last_cpu_time = 0;
static u64 s_last_sw_time = 0;
static u32 s_presents_since_last_update = 0;
static Common::Timer s_fps_timer;
static Common::Timer s_frame_timer;
static Threading::ThreadHandle s_cpu_thread_handle;
@ -648,21 +644,13 @@ float System::GetCPUThreadAverageTime()
{
return s_cpu_thread_time;
}
float System::GetSWThreadUsage()
float System::GetGPUThreadUsage()
{
return s_sw_thread_usage;
return s_gpu_thread_usage;
}
float System::GetSWThreadAverageTime()
float System::GetGPUThreadAverageTime()
{
return s_sw_thread_time;
}
float System::GetGPUUsage()
{
return s_gpu_usage;
}
float System::GetGPUAverageTime()
{
return s_average_gpu_time;
return s_gpu_thread_time;
}
const System::FrameTimeHistory& System::GetFrameTimeHistory()
{
@ -1059,7 +1047,6 @@ std::string System::GetInputProfilePath(std::string_view name)
bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool update_display /* = true*/)
{
ClearMemorySaveStates();
g_gpu->RestoreDeviceContext();
// save current state
std::unique_ptr<ByteStream> state_stream = ByteStream::CreateGrowableMemoryStream();
@ -1068,16 +1055,8 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
if (!state_valid)
ERROR_LOG("Failed to save old GPU state when switching renderers");
// create new renderer
g_gpu.reset();
if (force_recreate_device)
{
PostProcessing::Shutdown();
Host::ReleaseGPUDevice();
}
Error error;
if (!CreateGPU(renderer, true, &error))
if (!GPUThread::SwitchGPUBackend(renderer, force_recreate_device, &error))
{
if (!IsStartupCancelled())
Host::ReportErrorAsync("Error", error.GetDescription());
@ -1090,7 +1069,6 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
{
state_stream->SeekAbsolute(0);
sw.SetMode(StateWrapper::Mode::Read);
g_gpu->RestoreDeviceContext();
g_gpu->DoState(sw, nullptr, update_display);
TimingEvents::DoState(sw);
}
@ -1326,9 +1304,6 @@ void System::PauseSystem(bool paused)
if (paused)
{
// Make sure the GPU is flushed, otherwise the VB might still be mapped.
g_gpu->FlushRender();
FullscreenUI::OnSystemPaused();
InputManager::PauseVibration();
@ -1345,7 +1320,7 @@ void System::PauseSystem(bool paused)
Host::OnSystemPaused();
Host::OnIdleStateChanged();
UpdateDisplayVSync();
InvalidateDisplay();
GPUThread::PresentCurrentFrame();
}
else
{
@ -1420,7 +1395,7 @@ bool System::LoadState(const char* filename, Error* error)
ResetThrottler();
if (IsPaused())
InvalidateDisplay();
GPUThread::PresentCurrentFrame();
VERBOSE_LOG("Loading state took {:.2f} msec", load_timer.GetTimeMilliseconds());
return true;
@ -1511,7 +1486,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Assert(s_state == State::Shutdown);
s_state = State::Starting;
s_startup_cancelled.store(false);
s_keep_gpu_device_on_shutdown = static_cast<bool>(g_gpu_device);
s_region = g_settings.region;
Host::OnSystemStarting();
@ -1801,15 +1775,11 @@ bool System::Initialize(bool force_software_renderer, Error* error)
s_average_frame_time = 0.0f;
s_cpu_thread_usage = 0.0f;
s_cpu_thread_time = 0.0f;
s_sw_thread_usage = 0.0f;
s_sw_thread_time = 0.0f;
s_average_gpu_time = 0.0f;
s_accumulated_gpu_time = 0.0f;
s_gpu_usage = 0.0f;
s_gpu_thread_usage = 0.0f;
s_gpu_thread_time = 0.0f;
s_last_frame_number = 0;
s_last_internal_frame_number = 0;
s_last_global_tick_counter = 0;
s_presents_since_last_update = 0;
s_last_cpu_time = 0;
s_fps_timer.Reset();
s_frame_timer.Reset();
@ -1828,34 +1798,33 @@ bool System::Initialize(bool force_software_renderer, Error* error)
CPU::CodeCache::Initialize();
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error))
// TODO: Drop pointer
g_gpu = std::make_unique<GPU>();
g_gpu->Initialize();
if (!GPUThread::CreateGPUBackend(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, error))
{
g_gpu.reset();
Bus::Shutdown();
CPU::Shutdown();
return false;
}
// Was startup cancelled? (e.g. shading compilers took too long and the user closed the application)
if (IsStartupCancelled())
{
GPUThread::DestroyGPUBackend();
g_gpu.reset();
CPU::Shutdown();
Bus::Shutdown();
return false;
}
GTE::UpdateAspectRatio();
if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Initialize();
// Was startup cancelled? (e.g. shading compilers took too long and the user closed the application)
if (IsStartupCancelled())
{
g_gpu.reset();
if (!s_keep_gpu_device_on_shutdown)
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Shutdown();
CPU::Shutdown();
Bus::Shutdown();
return false;
}
DMA::Initialize();
CDROM::Initialize();
Pad::Initialize();
@ -1919,16 +1888,9 @@ void System::DestroySystem()
TimingEvents::Shutdown();
ClearRunningGame();
// Restore present-all-frames behavior.
if (s_keep_gpu_device_on_shutdown && g_gpu_device)
{
if (GPUThread::WasFullscreenUIRequested())
UpdateDisplayVSync();
}
else
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
GPUThread::DestroyGPUBackend();
s_bios_hash = {};
s_bios_image_info = nullptr;
@ -1967,9 +1929,6 @@ void System::Execute()
case State::Running:
{
s_system_executing = true;
// TODO: Purge reset/restore
g_gpu->RestoreDeviceContext();
TimingEvents::UpdateCPUDowncount();
if (s_rewind_load_counter >= 0)
@ -1998,9 +1957,6 @@ void System::FrameDone()
{
s_frame_number++;
// Vertex buffer is shared, need to flush what we have.
g_gpu->FlushRender();
// Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns.
// TODO: when running ahead, we can skip this (and the flush above)
SPU::GeneratePendingSamples();
@ -2051,7 +2007,6 @@ void System::FrameDone()
// counter-acts that.
Host::PumpMessagesOnCPUThread();
InputManager::PollSources();
g_gpu->RestoreDeviceContext();
if (IsExecutionInterrupted())
{
@ -2078,48 +2033,6 @@ void System::FrameDone()
if (s_pre_frame_sleep)
AccumulatePreFrameSleepTime();
// explicit present (frame pacing)
const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number);
s_last_presented_internal_frame_number = s_internal_frame_number;
const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame &&
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
g_gpu_device->ShouldSkipPresentingFrame()) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
if (!skip_this_frame)
{
s_skipped_frame_count = 0;
const bool throttle_before_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted());
const bool explicit_present = (throttle_before_present && g_gpu_device->GetFeatures().explicit_present);
if (explicit_present)
{
const bool do_present = PresentDisplay(false, true);
Throttle(current_time);
if (do_present)
g_gpu_device->SubmitPresent();
}
else
{
if (throttle_before_present)
Throttle(current_time);
PresentDisplay(false, false);
if (!throttle_before_present && s_throttler_enabled && !IsExecutionInterrupted())
Throttle(current_time);
}
}
else
{
DEBUG_LOG("Skipping displaying frame");
s_skipped_frame_count++;
if (s_throttler_enabled)
Throttle(current_time);
}
// pre-frame sleep (input lag reduction)
current_time = Common::Timer::GetCurrentValue();
if (s_pre_frame_sleep)
@ -2128,10 +2041,15 @@ void System::FrameDone()
if (pre_frame_sleep_until > current_time &&
Common::Timer::ConvertValueToMilliseconds(pre_frame_sleep_until - current_time) >= 1)
{
Common::Timer::SleepUntil(pre_frame_sleep_until, true);
Throttle(current_time, pre_frame_sleep_until);
current_time = Common::Timer::GetCurrentValue();
}
}
else
{
if (s_throttler_enabled)
Throttle(current_time, s_next_frame_time);
}
s_frame_start_time = current_time;
@ -2149,14 +2067,42 @@ void System::FrameDone()
}
}
g_gpu->RestoreDeviceContext();
// Update perf counters *after* throttling, we want to measure from start-of-frame
// to start-of-frame, not end-of-frame to end-of-frame (will be noisy due to different
// amounts of computation happening in each frame).
System::UpdatePerformanceCounters();
}
void System::GetFramePresentationDetails(bool* present_frame, bool* allow_present_skip,
Common::Timer::Value* present_time)
{
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
// explicit present (frame pacing)
const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number);
s_last_presented_internal_frame_number = s_internal_frame_number;
const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame &&
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT)) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
const bool should_allow_present_skip = !s_syncing_to_host_with_vsync && !s_optimal_frame_pacing;
*present_frame = !skip_this_frame;
*allow_present_skip = should_allow_present_skip;
*present_time = (should_allow_present_skip && !IsExecutionInterrupted()) ? s_next_frame_time : 0;
if (!skip_this_frame)
{
s_skipped_frame_count = 0;
}
else
{
DEBUG_LOG("Skipping displaying frame");
s_skipped_frame_count++;
}
}
void System::SetThrottleFrequency(float frequency)
{
if (s_throttle_frequency == frequency)
@ -2188,12 +2134,12 @@ void System::ResetThrottler()
s_pre_frame_sleep_time = 0;
}
void System::Throttle(Common::Timer::Value current_time)
void System::Throttle(Common::Timer::Value current_time, Common::Timer::Value sleep_until)
{
// If we're running too slow, advance the next frame time based on the time we lost. Effectively skips
// running those frames at the intended time, because otherwise if we pause in the debugger, we'll run
// hundreds of frames when we resume.
if (current_time > s_next_frame_time)
if (current_time > sleep_until)
{
const Common::Timer::Value diff = static_cast<s64>(current_time) - static_cast<s64>(s_next_frame_time);
s_next_frame_time += (diff / s_frame_period) * s_frame_period + s_frame_period;
@ -2208,11 +2154,10 @@ void System::Throttle(Common::Timer::Value current_time)
Common::Timer::Value poll_start_time = current_time;
for (;;)
{
const u32 sleep_ms =
static_cast<u32>(Common::Timer::ConvertValueToMilliseconds(s_next_frame_time - poll_start_time));
const u32 sleep_ms = static_cast<u32>(Common::Timer::ConvertValueToMilliseconds(sleep_until - poll_start_time));
s_socket_multiplexer->PollEventsWithTimeout(sleep_ms);
poll_start_time = Common::Timer::GetCurrentValue();
if (poll_start_time >= s_next_frame_time || (!g_settings.display_optimal_frame_pacing && sleep_ms == 0))
if (poll_start_time >= sleep_until || (!g_settings.display_optimal_frame_pacing && sleep_ms == 0))
break;
}
}
@ -2221,21 +2166,21 @@ void System::Throttle(Common::Timer::Value current_time)
// Use a spinwait if we undersleep for all platforms except android.. don't want to burn battery.
// Linux also seems to do a much better job of waking up at the requested time.
#if !defined(__linux__)
Common::Timer::SleepUntil(s_next_frame_time, g_settings.display_optimal_frame_pacing);
Common::Timer::SleepUntil(sleep_until, g_settings.display_optimal_frame_pacing);
#else
Common::Timer::SleepUntil(s_next_frame_time, false);
Common::Timer::SleepUntil(sleep_until, false);
#endif
}
#else
// No spinwait on Android, see above.
Common::Timer::SleepUntil(s_next_frame_time, false);
Common::Timer::SleepUntil(sleep_until, false);
#endif
#if 0
DEV_LOG("Asked for {:.2f} ms, slept for {:.2f} ms, {:.2f} ms late",
Common::Timer::ConvertValueToMilliseconds(s_next_frame_time - current_time),
Common::Timer::ConvertValueToMilliseconds(sleep_until - current_time),
Common::Timer::ConvertValueToMilliseconds(Common::Timer::GetCurrentValue() - current_time),
Common::Timer::ConvertValueToMilliseconds(Common::Timer::GetCurrentValue() - s_next_frame_time));
Common::Timer::ConvertValueToMilliseconds(Common::Timer::GetCurrentValue() - sleep_until));
#endif
s_next_frame_time += s_frame_period;
@ -2293,62 +2238,6 @@ void System::RecreateSystem()
PauseSystem(true);
}
bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
{
const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
if (!g_gpu_device ||
(renderer != GPURenderer::Software && !GPUDevice::IsSameRenderAPI(g_gpu_device->GetRenderAPI(), api)))
{
if (g_gpu_device)
{
WARNING_LOG("Recreating GPU device, expecting {} got {}", GPUDevice::RenderAPIToString(api),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
PostProcessing::Shutdown();
}
Host::ReleaseGPUDevice();
if (!Host::CreateGPUDevice(api, error))
{
Host::ReleaseRenderWindow();
return false;
}
if (is_switching)
PostProcessing::Initialize();
}
if (renderer == GPURenderer::Software)
g_gpu = GPU::CreateSoftwareRenderer();
else
g_gpu = GPU::CreateHardwareRenderer();
if (!g_gpu)
{
ERROR_LOG("Failed to initialize {} renderer, falling back to software renderer",
Settings::GetRendererName(renderer));
Host::AddOSDMessage(
fmt::format(TRANSLATE_FS("System", "Failed to initialize {} renderer, falling back to software renderer."),
Settings::GetRendererName(renderer)),
Host::OSD_CRITICAL_ERROR_DURATION);
g_gpu.reset();
g_gpu = GPU::CreateSoftwareRenderer();
if (!g_gpu)
{
ERROR_LOG("Failed to create fallback software renderer.");
if (!s_keep_gpu_device_on_shutdown)
{
PostProcessing::Shutdown();
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
return false;
}
}
return true;
}
bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state)
{
if (!sw.DoMarker("System"))
@ -2398,7 +2287,6 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
if (!sw.DoMarker("InterruptController") || !InterruptController::DoState(sw))
return false;
g_gpu->RestoreDeviceContext();
if (!sw.DoMarker("GPU") || !g_gpu->DoState(sw, host_texture, update_display))
return false;
@ -2745,7 +2633,7 @@ bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_s
std::vector<u32> screenshot_buffer;
u32 screenshot_stride;
GPUTexture::Format screenshot_format;
if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height,
if (GPUBackend::RenderScreenshotToBuffer(screenshot_width, screenshot_height,
GSVector4i(0, 0, screenshot_width, screenshot_height), false,
&screenshot_buffer, &screenshot_stride, &screenshot_format) &&
GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride,
@ -2782,9 +2670,6 @@ bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_s
// write data
{
header.offset_to_data = static_cast<u32>(state->GetPosition());
g_gpu->RestoreDeviceContext();
header.data_compression_type = compression_method;
bool result = false;
@ -2869,9 +2754,9 @@ void System::UpdatePerformanceCounters()
100.0f;
s_last_global_tick_counter = global_tick_counter;
const Threading::Thread* sw_thread = g_gpu->GetSWThread();
const Threading::ThreadHandle& gpu_thread = GPUThread::GetThreadHandle();
const u64 cpu_time = s_cpu_thread_handle ? s_cpu_thread_handle.GetCPUTime() : 0;
const u64 sw_time = sw_thread ? sw_thread->GetCPUTime() : 0;
const u64 sw_time = gpu_thread ? gpu_thread.GetCPUTime() : 0;
const u64 cpu_delta = cpu_time - s_last_cpu_time;
const u64 sw_delta = sw_time - s_last_sw_time;
s_last_cpu_time = cpu_time;
@ -2879,27 +2764,19 @@ void System::UpdatePerformanceCounters()
s_cpu_thread_usage = static_cast<float>(static_cast<double>(cpu_delta) * pct_divider);
s_cpu_thread_time = static_cast<float>(static_cast<double>(cpu_delta) * time_divider);
s_sw_thread_usage = static_cast<float>(static_cast<double>(sw_delta) * pct_divider);
s_sw_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider);
s_gpu_thread_usage = static_cast<float>(static_cast<double>(sw_delta) * pct_divider);
s_gpu_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider);
s_fps_timer.ResetTo(now_ticks);
if (g_gpu_device->IsGPUTimingEnabled())
{
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(std::max(s_presents_since_last_update, 1u));
s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f);
}
s_accumulated_gpu_time = 0.0f;
s_presents_since_last_update = 0;
if (g_settings.display_show_gpu_stats)
g_gpu->UpdateStatistics(frames_run);
if (g_settings.display_show_gpu_stats || g_settings.display_show_gpu_stats)
GPUThread::SetPerformanceCounterUpdatePending();
if (s_pre_frame_sleep)
UpdatePreFrameSleepTime();
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Average: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_fps,
s_vps, s_cpu_thread_usage, s_gpu_usage, s_average_frame_time, s_minimum_frame_time, s_maximum_frame_time);
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} Average: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_fps, s_vps,
s_cpu_thread_usage, s_average_frame_time, s_minimum_frame_time, s_maximum_frame_time);
Host::OnPerformanceCountersUpdated();
}
@ -2910,8 +2787,8 @@ void System::ResetPerformanceCounters()
s_last_internal_frame_number = s_internal_frame_number;
s_last_global_tick_counter = GetGlobalTickCounter();
s_last_cpu_time = s_cpu_thread_handle ? s_cpu_thread_handle.GetCPUTime() : 0;
if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread)
s_last_sw_time = sw_thread->GetCPUTime();
if (const Threading::ThreadHandle& sw_thread = GPUThread::GetThreadHandle(); sw_thread)
s_last_sw_time = sw_thread.GetCPUTime();
else
s_last_sw_time = 0;
@ -3045,7 +2922,7 @@ void System::UpdateDisplayVSync()
s_syncing_to_host_with_vsync ? " (for throttling)" : "",
allow_present_throttle ? " (present throttle allowed)" : "");
g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle);
GPUThread::SetVSync(vsync_mode, allow_present_throttle);
}
GPUVSyncMode System::GetEffectiveVSyncMode()
@ -3653,7 +3530,6 @@ bool System::DumpVRAM(const char* filename)
if (!IsValid())
return false;
g_gpu->RestoreDeviceContext();
return g_gpu->DumpVRAMToFile(filename);
}
@ -3805,10 +3681,11 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
if (s_running_game_serial != prev_serial)
UpdateSessionTime(prev_serial);
// TODO GPU-THREAD: Racey...
if (SaveStateSelectorUI::IsOpen())
SaveStateSelectorUI::RefreshList(s_running_game_serial);
else
SaveStateSelectorUI::ClearList();
{
GPUThread::RunOnThread([serial = s_running_game_serial]() { SaveStateSelectorUI::RefreshList(serial); });
}
UpdateRichPresence(booting);
@ -4082,7 +3959,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale ||
g_settings.gpu_multisamples != old_settings.gpu_multisamples ||
g_settings.gpu_per_sample_shading != old_settings.gpu_per_sample_shading ||
g_settings.gpu_use_thread != old_settings.gpu_use_thread ||
g_settings.gpu_max_queued_frames != old_settings.gpu_max_queued_frames ||
g_settings.gpu_use_software_renderer_for_readbacks != old_settings.gpu_use_software_renderer_for_readbacks ||
g_settings.gpu_fifo_size != old_settings.gpu_fifo_size ||
g_settings.gpu_max_run_ahead != old_settings.gpu_max_run_ahead ||
@ -4116,9 +3993,9 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.rewind_enable != old_settings.rewind_enable ||
g_settings.runahead_frames != old_settings.runahead_frames)
{
g_gpu->UpdateSettings(old_settings);
GPUThread::UpdateSettings();
if (IsPaused())
InvalidateDisplay();
GPUThread::PresentCurrentFrame();
}
if (g_settings.gpu_widescreen_hack != old_settings.gpu_widescreen_hack ||
@ -4144,9 +4021,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
CPU::CodeCache::Reset();
}
if (g_settings.display_show_gpu_stats != old_settings.display_show_gpu_stats)
g_gpu->ResetStatistics();
if (g_settings.cdrom_readahead_sectors != old_settings.cdrom_readahead_sectors)
CDROM::SetReadaheadSectors(g_settings.cdrom_readahead_sectors);
@ -4392,6 +4266,9 @@ void System::CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64
void System::ClearMemorySaveStates()
{
if (!s_rewind_states.empty() || !s_runahead_states.empty())
Panic("FIXME TEXTURE CLEAR");
s_rewind_states.clear();
s_runahead_states.clear();
}
@ -4563,11 +4440,12 @@ void System::DoRewind()
s_rewind_load_counter--;
}
InvalidateDisplay();
// TODO FIXME InvalidateDisplay();
Host::PumpMessagesOnCPUThread();
Internal::IdlePollUpdate();
Throttle(Common::Timer::GetCurrentValue());
Throttle(Common::Timer::GetCurrentValue(), s_next_frame_time);
}
void System::SaveRunaheadState()
@ -4858,7 +4736,9 @@ bool System::SaveScreenshot(const char* filename, DisplayScreenshotMode mode, Di
filename = auto_filename.c_str();
}
return g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread, true);
Panic("TODO FIXME");
// return g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread, true);
return false;
}
std::string System::GetGameSaveStateFileName(std::string_view serial, s32 slot)
@ -5322,7 +5202,8 @@ void System::ToggleSoftwareRendering()
if (IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software)
return;
const GPURenderer new_renderer = g_gpu->IsHardwareRenderer() ? GPURenderer::Software : g_settings.gpu_renderer;
const GPURenderer new_renderer =
GPUBackend::IsUsingHardwareBackend() ? GPURenderer::Software : g_settings.gpu_renderer;
Host::AddIconOSDMessage("SoftwareRendering", ICON_FA_PAINT_ROLLER,
fmt::format(TRANSLATE_FS("OSDMessage", "Switching to {} renderer..."),
@ -5338,7 +5219,7 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/)
return;
if (scale == 0.0f)
scale = g_gpu->IsHardwareRenderer() ? static_cast<float>(g_settings.gpu_resolution_scale) : 1.0f;
scale = GPUBackend::IsUsingHardwareBackend() ? static_cast<float>(g_settings.gpu_resolution_scale) : 1.0f;
const float y_scale =
(static_cast<float>(g_gpu->GetCRTCDisplayWidth()) / static_cast<float>(g_gpu->GetCRTCDisplayHeight())) /
@ -5360,62 +5241,7 @@ void System::HostDisplayResized()
if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow)
GTE::UpdateAspectRatio();
g_gpu->UpdateResolutionScale();
}
bool System::PresentDisplay(bool skip_present, bool explicit_present)
{
// acquire for IO.MousePos.
std::atomic_thread_fence(std::memory_order_acquire);
if (!skip_present)
{
FullscreenUI::Render();
ImGuiManager::RenderTextOverlays();
ImGuiManager::RenderOSDMessages();
if (s_state == State::Running)
ImGuiManager::RenderSoftwareCursors();
}
// Debug windows are always rendered, otherwise mouse input breaks on skip.
ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows();
bool do_present;
if (g_gpu && !skip_present)
do_present = g_gpu->PresentDisplay();
else
do_present = g_gpu_device->BeginPresent(skip_present);
if (do_present)
{
g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(explicit_present);
if (g_gpu_device->IsGPUTimingEnabled())
{
s_accumulated_gpu_time += g_gpu_device->GetAndResetAccumulatedGPUTime();
s_presents_since_last_update++;
}
}
else
{
// Still need to kick ImGui or it gets cranky.
ImGui::Render();
}
ImGuiManager::NewFrame();
return do_present;
}
void System::InvalidateDisplay()
{
PresentDisplay(false, false);
if (g_gpu)
g_gpu->RestoreDeviceContext();
GPUBackend::PushCommand(GPUBackend::NewUpdateResolutionScaleCommand());
}
void System::SetTimerResolutionIncreased(bool enabled)

View File

@ -232,10 +232,8 @@ float GetMaximumFrameTime();
float GetThrottleFrequency();
float GetCPUThreadUsage();
float GetCPUThreadAverageTime();
float GetSWThreadUsage();
float GetSWThreadAverageTime();
float GetGPUUsage();
float GetGPUAverageTime();
float GetGPUThreadUsage();
float GetGPUThreadAverageTime();
const FrameTimeHistory& GetFrameTimeHistory();
u32 GetFrameTimeHistoryPos();
void FormatLatencyStats(SmallStringBase& str);
@ -299,6 +297,7 @@ void SetThrottleFrequency(float frequency);
void UpdateThrottlePeriod();
void ResetThrottler();
void ResetPerformanceCounters();
void GetFramePresentationDetails(bool* present_frame, bool* allow_present_skip, Common::Timer::Value* present_time);
// Access controllers for simulating input.
Controller* GetController(u32 slot);
@ -479,10 +478,6 @@ void RequestDisplaySize(float scale = 0.0f);
/// Call when host display size changes, use with "match display" aspect ratio setting.
void HostDisplayResized();
/// Renders the display.
bool PresentDisplay(bool skip_present, bool explicit_present);
void InvalidateDisplay();
//////////////////////////////////////////////////////////////////////////
// Memory Save States (Rewind and Runahead)
//////////////////////////////////////////////////////////////////////////

View File

@ -18,6 +18,8 @@
#include "core/game_list.h"
#include "core/gdb_server.h"
#include "core/gpu.h"
#include "core/gpu_backend.h"
#include "core/gpu_thread.h"
#include "core/host.h"
#include "core/imgui_overlays.h"
#include "core/memory_card.h"
@ -762,12 +764,9 @@ void EmuThread::startFullscreenUI()
m_run_fullscreen_ui = true;
Error error;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), &error) ||
!FullscreenUI::Initialize())
if (!GPUThread::StartFullscreenUI(&error))
{
Host::ReportErrorAsync("Error", error.GetDescription());
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
m_run_fullscreen_ui = false;
return;
}
@ -803,8 +802,7 @@ void EmuThread::stopFullscreenUI()
if (!g_gpu_device)
return;
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
GPUThread::Shutdown();
}
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -911,7 +909,10 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
{
Host::ResizeDisplayWindow(width, height, scale);
if (!GPUThread::IsStarted())
return;
GPUThread::ResizeDisplayWindow(width, height, scale);
}
void EmuThread::redrawDisplayWindow()
@ -925,7 +926,7 @@ void EmuThread::redrawDisplayWindow()
if (!g_gpu_device || System::IsShutdown())
return;
System::InvalidateDisplay();
GPUThread::PresentCurrentFrame();
}
void EmuThread::toggleFullscreen()
@ -953,7 +954,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
m_is_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow();
GPUThread::UpdateDisplayWindow();
}
bool Host::IsFullscreen()
@ -978,7 +979,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return;
m_is_surfaceless = surfaceless;
Host::UpdateDisplayWindow();
GPUThread::UpdateDisplayWindow();
}
void EmuThread::requestDisplaySize(float scale)
@ -1757,33 +1758,16 @@ void EmuThread::run()
// main loop
while (!m_shutdown_flag)
{
// TODO: Maybe make this better?
if (System::IsRunning())
{
System::Execute();
}
else
{
// we want to keep rendering the UI when paused and fullscreen UI is enabled
if (!FullscreenUI::HasActiveWindow() && !System::IsRunning())
{
// wait until we have a system before running
m_event_loop->exec();
continue;
}
m_event_loop->processEvents(QEventLoop::AllEvents);
System::Internal::IdlePollUpdate();
if (g_gpu_device)
{
System::PresentDisplay(false, false);
if (!g_gpu_device->IsVSyncModeBlocking())
g_gpu_device->ThrottlePresentation();
}
}
}
if (System::IsValid())
System::ShutdownSystem(false);
GPUThread::Shutdown();
destroyBackgroundControllerPollTimer();
System::Internal::CPUThreadShutdown();
@ -1979,13 +1963,13 @@ void Host::ReleaseRenderWindow()
void EmuThread::updatePerformanceCounters()
{
const RenderAPI render_api = g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::None;
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer();
const RenderAPI render_api = GPUThread::GetRenderAPI();
const bool hardware_renderer = GPUBackend::IsUsingHardwareBackend();
u32 render_width = 0;
u32 render_height = 0;
if (g_gpu)
std::tie(render_width, render_height) = g_gpu->GetEffectiveDisplayResolution();
if (System::IsValid())
std::tie(render_width, render_height) = GPUBackend::GetLastDisplaySourceSize();
if (render_api != m_last_render_api || hardware_renderer != m_last_hardware_renderer)
{

View File

@ -96,6 +96,8 @@ public:
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
// TODO: Maybe remove this...
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);