dolphin/Source/Core/Core/Core.cpp

1031 lines
28 KiB
C++
Raw Normal View History

// Copyright 2008 Dolphin Emulator Project
2015-05-17 23:08:10 +00:00
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/Core.h"
#include <atomic>
2015-09-28 15:57:16 +00:00
#include <cstring>
2017-01-30 22:49:21 +00:00
#include <locale>
#include <mutex>
#include <queue>
#include <utility>
#ifdef _WIN32
#include <windows.h>
#endif
#include "AudioCommon/AudioCommon.h"
#include "Common/CPUDetect.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "Common/Logging/LogManager.h"
#include "Common/MathUtil.h"
#include "Common/MemoryUtil.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "Common/Timer.h"
#include "Core/Analytics.h"
#include "Core/BootManager.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/DSPEmulator.h"
#include "Core/Host.h"
#include "Core/MemTools.h"
#ifdef USE_MEMORYWATCHER
#include "Core/MemoryWatcher.h"
#endif
#include "Core/Boot/Boot.h"
#include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/AudioInterface.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DSP.h"
#include "Core/HW/EXI/EXI.h"
2015-01-16 22:36:05 +00:00
#include "Core/HW/GCKeyboard.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/HW.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SystemTimers.h"
#include "Core/HW/VideoInterface.h"
#include "Core/HW/Wiimote.h"
#include "Core/IOS/Network/Socket.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
#include "Core/PatchEngine.h"
2014-09-06 10:41:17 +00:00
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/State.h"
#include "Core/WiiRoot.h"
2013-01-06 10:28:27 +00:00
#ifdef USE_GDBSTUB
#include "Core/PowerPC/GDBStub.h"
2013-01-06 10:28:27 +00:00
#endif
#include "DiscIO/FileMonitor.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/GCAdapter.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoBackendBase.h"
// Android and OSX haven't implemented the keyword yet.
#if defined __ANDROID__ || defined __APPLE__
#include <pthread.h>
#else // Everything besides OSX and Android
#define ThreadLocalStorage thread_local
#endif
namespace Core
{
2015-10-17 02:52:38 +00:00
// TODO: ugly, remove
bool g_aspect_wide;
bool g_want_determinism;
// Declarations and definitions
static Common::Timer s_timer;
static std::atomic<u32> s_drawn_frame;
static std::atomic<u32> s_drawn_video;
// Function forwarding
void Callback_WiimoteInterruptChannel(int _number, u16 _channelID, const void* _pData, u32 _Size);
// Function declarations
void EmuThread();
static bool s_is_stopping = false;
static bool s_hardware_initialized = false;
static bool s_is_started = false;
static Common::Flag s_is_booting;
static void* s_window_handle = nullptr;
static std::string s_state_filename;
static std::thread s_emu_thread;
static StoppedCallbackFunc s_on_stopped_callback = nullptr;
static std::thread s_cpu_thread;
static bool s_request_refresh_info = false;
static int s_pause_and_lock_depth = 0;
static bool s_is_throttler_temp_disabled = false;
struct HostJob
{
std::function<void()> job;
bool run_after_stop;
};
static std::mutex s_host_jobs_lock;
static std::queue<HostJob> s_host_jobs_queue;
#ifdef ThreadLocalStorage
static ThreadLocalStorage bool tls_is_cpu_thread = false;
#else
static pthread_key_t s_tls_is_cpu_key;
static pthread_once_t s_cpu_key_is_init = PTHREAD_ONCE_INIT;
static void InitIsCPUKey()
{
pthread_key_create(&s_tls_is_cpu_key, nullptr);
}
#endif
bool GetIsThrottlerTempDisabled()
{
return s_is_throttler_temp_disabled;
}
void SetIsThrottlerTempDisabled(bool disable)
{
s_is_throttler_temp_disabled = disable;
}
std::string GetStateFileName()
{
return s_state_filename;
}
void SetStateFileName(const std::string& val)
{
s_state_filename = val;
}
2015-06-06 05:20:51 +00:00
void FrameUpdateOnCPUThread()
{
if (NetPlay::IsNetPlayRunning())
NetPlayClient::SendTimeBase();
2015-06-06 05:20:51 +00:00
}
// Display messages and return values
// Formatted stop message
std::string StopMessage(bool main_thread, const std::string& message)
{
return StringFromFormat("Stop [%s %i]\t%s\t%s", main_thread ? "Main Thread" : "Video Thread",
2016-08-07 17:03:07 +00:00
Common::CurrentThreadId(), Common::MemUsage().c_str(), message.c_str());
}
void DisplayMessage(const std::string& message, int time_in_ms)
{
if (!IsRunning())
return;
// Actually displaying non-ASCII could cause things to go pear-shaped
for (const char& c : message)
{
2017-01-30 22:49:21 +00:00
if (!std::isprint(c, std::locale::classic()))
return;
}
OSD::AddMessage(message, time_in_ms);
Host_UpdateTitle(message);
}
bool IsRunning()
{
return (GetState() != CORE_UNINITIALIZED || s_hardware_initialized) && !s_is_stopping;
}
bool IsRunningAndStarted()
{
return s_is_started && !s_is_stopping;
}
bool IsRunningInCurrentThread()
{
return IsRunning() && IsCPUThread();
}
bool IsCPUThread()
{
#ifdef ThreadLocalStorage
return tls_is_cpu_thread;
#else
// Use pthread implementation for Android and Mac
// Make sure that s_tls_is_cpu_key is initialized
pthread_once(&s_cpu_key_is_init, InitIsCPUKey);
return pthread_getspecific(s_tls_is_cpu_key);
#endif
}
bool IsGPUThread()
{
const SConfig& _CoreParameter = SConfig::GetInstance();
if (_CoreParameter.bCPUThread)
{
return (s_emu_thread.joinable() && (s_emu_thread.get_id() == std::this_thread::get_id()));
}
else
{
return IsCPUThread();
}
}
2013-02-26 19:49:00 +00:00
// This is called from the GUI thread. See the booting call schedule in
// BootManager.cpp
bool Init()
{
if (s_emu_thread.joinable())
{
if (IsRunning())
{
PanicAlertT("Emu Thread already running");
return false;
}
// The Emu Thread was stopped, synchronize with it.
s_emu_thread.join();
}
// Drain any left over jobs
HostDispatchJobs();
Core::UpdateWantDeterminism(/*initial*/ true);
INFO_LOG(OSREPORT, "Starting core = %s mode", SConfig::GetInstance().bWii ? "Wii" : "GameCube");
INFO_LOG(OSREPORT, "CPU Thread separate = %s", SConfig::GetInstance().bCPUThread ? "Yes" : "No");
Host_UpdateMainFrame(); // Disable any menus or buttons at boot
s_window_handle = Host_GetRenderHandle();
// Start the emu thread
s_emu_thread = std::thread(EmuThread);
return true;
}
// Called from GUI thread
void Stop() // - Hammertime!
{
if (GetState() == CORE_STOPPING)
return;
const SConfig& _CoreParameter = SConfig::GetInstance();
s_is_stopping = true;
// Dump left over jobs
HostDispatchJobs();
Fifo::EmulatorState(false);
INFO_LOG(CONSOLE, "Stop [Main Thread]\t\t---- Shutting down ----");
// Stop the CPU
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stop CPU").c_str());
CPU::Stop();
if (_CoreParameter.bCPUThread)
{
// Video_EnterLoop() should now exit so that EmuThread()
// will continue concurrently with the rest of the commands
// in this function. We no longer rely on Postmessage.
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Wait for Video Loop to exit ...").c_str());
g_video_backend->Video_ExitLoop();
}
#if defined(__LIBUSB__)
GCAdapter::ResetRumble();
#endif
#ifdef USE_MEMORYWATCHER
MemoryWatcher::Shutdown();
#endif
}
void DeclareAsCPUThread()
{
#ifdef ThreadLocalStorage
tls_is_cpu_thread = true;
#else
// Use pthread implementation for Android and Mac
// Make sure that s_tls_is_cpu_key is initialized
pthread_once(&s_cpu_key_is_init, InitIsCPUKey);
pthread_setspecific(s_tls_is_cpu_key, (void*)true);
#endif
}
void UndeclareAsCPUThread()
{
#ifdef ThreadLocalStorage
tls_is_cpu_thread = false;
#else
// Use pthread implementation for Android and Mac
// Make sure that s_tls_is_cpu_key is initialized
pthread_once(&s_cpu_key_is_init, InitIsCPUKey);
pthread_setspecific(s_tls_is_cpu_key, (void*)false);
#endif
}
// For the CPU Thread only.
static void CPUSetInitialExecutionState()
{
QueueHostJob([] {
SetState(SConfig::GetInstance().bBootToPause ? CORE_PAUSE : CORE_RUN);
Host_UpdateMainFrame();
});
}
// Create the CPU thread, which is a CPU + Video thread in Single Core mode.
2014-07-08 12:29:26 +00:00
static void CpuThread()
{
DeclareAsCPUThread();
const SConfig& _CoreParameter = SConfig::GetInstance();
if (_CoreParameter.bCPUThread)
{
Common::SetCurrentThreadName("CPU thread");
}
else
{
Common::SetCurrentThreadName("CPU-GPU thread");
g_video_backend->Video_Prepare();
}
// This needs to be delayed until after the video backend is ready.
DolphinAnalytics::Instance()->ReportGameStart();
if (_CoreParameter.bFastmem)
EMM::InstallExceptionHandler(); // Let's run under memory watch
if (!s_state_filename.empty())
{
// Needs to PauseAndLock the Core
// NOTE: EmuThread should have left us in CPU_STEPPING so nothing will happen
// until after the job is serviced.
QueueHostJob([] {
// Recheck in case Movie cleared it since.
if (!s_state_filename.empty())
State::LoadAs(s_state_filename);
});
}
s_is_started = true;
CPUSetInitialExecutionState();
#ifdef USE_GDBSTUB
#ifndef _WIN32
if (!_CoreParameter.gdb_socket.empty())
{
gdb_init_local(_CoreParameter.gdb_socket.data());
gdb_break();
}
else
#endif
if (_CoreParameter.iGDBPort > 0)
{
gdb_init(_CoreParameter.iGDBPort);
// break at next instruction (the first instruction)
gdb_break();
}
#endif
#ifdef USE_MEMORYWATCHER
MemoryWatcher::Init();
#endif
// Enter CPU run loop. When we leave it - we are done.
CPU::Run();
s_is_started = false;
if (!_CoreParameter.bCPUThread)
g_video_backend->Video_Cleanup();
if (_CoreParameter.bFastmem)
EMM::UninstallExceptionHandler();
return;
}
2014-07-08 12:29:26 +00:00
static void FifoPlayerThread()
{
DeclareAsCPUThread();
const SConfig& _CoreParameter = SConfig::GetInstance();
if (_CoreParameter.bCPUThread)
{
Common::SetCurrentThreadName("FIFO player thread");
}
else
{
g_video_backend->Video_Prepare();
Common::SetCurrentThreadName("FIFO-GPU thread");
}
// Enter CPU run loop. When we leave it - we are done.
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
{
if (auto cpu_core = FifoPlayer::GetInstance().GetCPUCore())
{
PowerPC::InjectExternalCPUCore(cpu_core.get());
s_is_started = true;
CPUSetInitialExecutionState();
CPU::Run();
s_is_started = false;
PowerPC::InjectExternalCPUCore(nullptr);
}
FifoPlayer::GetInstance().Close();
}
// If we did not enter the CPU Run Loop above then run a fake one instead.
// We need to be IsRunningAndStarted() for DolphinWX to stop us.
if (CPU::GetState() != CPU::CPU_POWERDOWN)
{
s_is_started = true;
Host_Message(WM_USER_STOP);
while (CPU::GetState() != CPU::CPU_POWERDOWN)
{
if (!_CoreParameter.bCPUThread)
g_video_backend->PeekMessages();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
s_is_started = false;
}
if (!_CoreParameter.bCPUThread)
g_video_backend->Video_Cleanup();
return;
}
// Initialize and create emulation thread
// Call browser: Init():s_emu_thread().
// See the BootManager.cpp file description for a complete call schedule.
void EmuThread()
{
const SConfig& core_parameter = SConfig::GetInstance();
s_is_booting.Set();
Common::SetCurrentThreadName("Emuthread - Starting");
if (SConfig::GetInstance().m_OCEnable)
DisplayMessage("WARNING: running at non-native CPU clock! Game may not be stable.", 8000);
DisplayMessage(cpu_info.brand_string, 8000);
DisplayMessage(cpu_info.Summarize(), 8000);
DisplayMessage(core_parameter.m_strFilename, 3000);
// For a time this acts as the CPU thread...
DeclareAsCPUThread();
Movie::Init();
HW::Init();
if (!g_video_backend->Initialize(s_window_handle))
{
s_is_booting.Clear();
PanicAlert("Failed to initialize video backend!");
Host_Message(WM_USER_STOP);
return;
}
OSD::AddMessage("Dolphin " + g_video_backend->GetName() + " Video Backend.", 5000);
if (cpu_info.HTT)
SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 4;
else
SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 2;
if (!DSP::GetDSPEmulator()->Initialize(core_parameter.bWii, core_parameter.bDSPThread))
{
s_is_booting.Clear();
HW::Shutdown();
g_video_backend->Shutdown();
PanicAlert("Failed to initialize DSP emulation!");
Host_Message(WM_USER_STOP);
return;
}
bool init_controllers = false;
if (!g_controller_interface.IsInit())
{
g_controller_interface.Initialize(s_window_handle);
Pad::Initialize();
Keyboard::Initialize();
init_controllers = true;
}
else
{
// Update references in case controllers were refreshed
Pad::LoadConfig();
Keyboard::LoadConfig();
}
// Load and Init Wiimotes - only if we are booting in Wii mode
if (core_parameter.bWii && !SConfig::GetInstance().m_bt_passthrough_enabled)
{
if (init_controllers)
Wiimote::Initialize(!s_state_filename.empty() ?
Wiimote::InitializeMode::DO_WAIT_FOR_WIIMOTES :
Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
else
Wiimote::LoadConfig();
// Activate Wiimotes which don't have source set to "None"
for (unsigned int i = 0; i != MAX_BBMOTES; ++i)
if (g_wiimote_sources[i])
IOS::HLE::GetUsbPointer()->AccessWiiMote(i | 0x100)->Activate(true);
}
AudioCommon::InitSoundStream();
// The hardware is initialized.
s_hardware_initialized = true;
s_is_booting.Clear();
// Set execution state to known values (CPU/FIFO/Audio Paused)
CPU::Break();
// Load GCM/DOL/ELF whatever ... we boot with the interpreter core
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
CBoot::BootUp();
// This adds the SyncGPU handler to CoreTiming, so now CoreTiming::Advance might block.
Fifo::Prepare();
// Thread is no longer acting as CPU Thread
UndeclareAsCPUThread();
// Setup our core, but can't use dynarec if we are compare server
if (core_parameter.iCPUCore != PowerPC::CORE_INTERPRETER &&
(!core_parameter.bRunCompareServer || core_parameter.bRunCompareClient))
{
PowerPC::SetMode(PowerPC::CoreMode::JIT);
}
else
{
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
}
// Update the window again because all stuff is initialized
Host_UpdateDisasmDialog();
Host_UpdateMainFrame();
// Determine the CPU thread function
void (*cpuThreadFunc)(void);
if (core_parameter.m_BootType == SConfig::BOOT_DFF)
cpuThreadFunc = FifoPlayerThread;
else
cpuThreadFunc = CpuThread;
// ENTER THE VIDEO THREAD LOOP
if (core_parameter.bCPUThread)
{
// This thread, after creating the EmuWindow, spawns a CPU
// thread, and then takes over and becomes the video thread
Common::SetCurrentThreadName("Video thread");
g_video_backend->Video_Prepare();
// Spawn the CPU thread
s_cpu_thread = std::thread(cpuThreadFunc);
// become the GPU thread
Fifo::RunGpuLoop();
// We have now exited the Video Loop
INFO_LOG(CONSOLE, "%s", StopMessage(false, "Video Loop Ended").c_str());
}
else // SingleCore mode
{
// The spawned CPU Thread also does the graphics.
// The EmuThread is thus an idle thread, which sleeps while
// waiting for the program to terminate. Without this extra
// thread, the video backend window hangs in single core mode
// because no one is pumping messages.
Common::SetCurrentThreadName("Emuthread - Idle");
// Spawn the CPU+GPU thread
s_cpu_thread = std::thread(cpuThreadFunc);
while (CPU::GetState() != CPU::CPU_POWERDOWN)
{
g_video_backend->PeekMessages();
Common::SleepCurrentThread(20);
}
}
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping Emu thread ...").c_str());
// Wait for s_cpu_thread to exit
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping CPU-GPU thread ...").c_str());
#ifdef USE_GDBSTUB
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping GDB ...").c_str());
gdb_deinit();
INFO_LOG(CONSOLE, "%s", StopMessage(true, "GDB stopped.").c_str());
#endif
s_cpu_thread.join();
INFO_LOG(CONSOLE, "%s", StopMessage(true, "CPU thread stopped.").c_str());
if (core_parameter.bCPUThread)
g_video_backend->Video_Cleanup();
FileMon::Close();
// Stop audio thread - Actually this does nothing when using HLE
// emulation, but stops the DSP Interpreter when using LLE emulation.
DSP::GetDSPEmulator()->DSP_StopSoundStream();
// We must set up this flag before executing HW::Shutdown()
s_hardware_initialized = false;
INFO_LOG(CONSOLE, "%s", StopMessage(false, "Shutting down HW").c_str());
HW::Shutdown();
INFO_LOG(CONSOLE, "%s", StopMessage(false, "HW shutdown").c_str());
if (init_controllers)
{
Wiimote::Shutdown();
Keyboard::Shutdown();
Pad::Shutdown();
g_controller_interface.Shutdown();
init_controllers = false;
}
g_video_backend->Shutdown();
AudioCommon::ShutdownSoundStream();
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Main Emu thread stopped").c_str());
// Clear on screen messages that haven't expired
OSD::ClearMessages();
BootManager::RestoreConfig();
INFO_LOG(CONSOLE, "Stop [Video Thread]\t\t---- Shutdown complete ----");
Movie::Shutdown();
PatchEngine::Shutdown();
HLE::Clear();
s_is_stopping = false;
if (s_on_stopped_callback)
s_on_stopped_callback();
}
// Set or get the running state
void SetState(EState state)
{
// State cannot be controlled until the CPU Thread is operational
if (!IsRunningAndStarted())
return;
switch (state)
{
case CORE_PAUSE:
// NOTE: GetState() will return CORE_PAUSE immediately, even before anything has
// stopped (including the CPU).
CPU::EnableStepping(true); // Break
Wiimote::Pause();
#if defined(__LIBUSB__)
GCAdapter::ResetRumble();
#endif
break;
case CORE_RUN:
CPU::EnableStepping(false);
Wiimote::Resume();
break;
default:
PanicAlert("Invalid state");
break;
}
}
EState GetState()
{
if (s_is_stopping)
return CORE_STOPPING;
if (s_hardware_initialized)
{
if (CPU::IsStepping())
return CORE_PAUSE;
return CORE_RUN;
}
return CORE_UNINITIALIZED;
}
static std::string GenerateScreenshotFolderPath()
{
const std::string& gameId = SConfig::GetInstance().GetGameID();
std::string path = File::GetUserPath(D_SCREENSHOTS_IDX) + gameId + DIR_SEP_CHR;
if (!File::CreateFullPath(path))
{
// fallback to old-style screenshots, without folder.
path = File::GetUserPath(D_SCREENSHOTS_IDX);
}
return path;
}
static std::string GenerateScreenshotName()
{
std::string path = GenerateScreenshotFolderPath();
// append gameId, path only contains the folder here.
path += SConfig::GetInstance().GetGameID();
std::string name;
for (int i = 1; File::Exists(name = StringFromFormat("%s-%d.png", path.c_str(), i)); ++i)
{
// TODO?
}
return name;
}
void SaveScreenShot()
{
const bool bPaused = (GetState() == CORE_PAUSE);
SetState(CORE_PAUSE);
Renderer::SetScreenshot(GenerateScreenshotName());
if (!bPaused)
SetState(CORE_RUN);
}
void SaveScreenShot(const std::string& name)
{
const bool bPaused = (GetState() == CORE_PAUSE);
SetState(CORE_PAUSE);
std::string filePath = GenerateScreenshotFolderPath() + name + ".png";
Renderer::SetScreenshot(filePath);
if (!bPaused)
SetState(CORE_RUN);
}
void RequestRefreshInfo()
{
s_request_refresh_info = true;
}
bool PauseAndLock(bool do_lock, bool unpause_on_unlock)
{
// WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread
if (!IsRunning())
return true;
// let's support recursive locking to simplify things on the caller's side,
// and let's do it at this outer level in case the individual systems don't support it.
if (do_lock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth)
return true;
bool was_unpaused = true;
if (do_lock)
{
// first pause the CPU
// This acquires a wrapper mutex and converts the current thread into
// a temporary replacement CPU Thread.
was_unpaused = CPU::PauseAndLock(true);
}
ExpansionInterface::PauseAndLock(do_lock, false);
// audio has to come after CPU, because CPU thread can wait for audio thread (m_throttle).
DSP::GetDSPEmulator()->PauseAndLock(do_lock, false);
// video has to come after CPU, because CPU thread can wait for video thread
// (s_efbAccessRequested).
Fifo::PauseAndLock(do_lock, false);
#if defined(__LIBUSB__)
GCAdapter::ResetRumble();
#endif
// CPU is unlocked last because CPU::PauseAndLock contains the synchronization
// mechanism that prevents CPU::Break from racing.
if (!do_lock)
{
// The CPU is responsible for managing the Audio and FIFO state so we use its
// mechanism to unpause them. If we unpaused the systems above when releasing
// the locks then they could call CPU::Break which would require detecting it
// and re-pausing with CPU::EnableStepping.
was_unpaused = CPU::PauseAndLock(false, unpause_on_unlock, true);
}
return was_unpaused;
}
// Display FPS info
// This should only be called from VI
void VideoThrottle()
{
// Update info per second
u32 ElapseTime = (u32)s_timer.GetTimeDifference();
if ((ElapseTime >= 1000 && s_drawn_video.load() > 0) || s_request_refresh_info)
{
UpdateTitle();
// Reset counter
s_timer.Update();
s_drawn_frame.store(0);
s_drawn_video.store(0);
}
s_drawn_video++;
}
// Executed from GPU thread
// reports if a frame should be skipped or not
// depending on the emulation speed set
bool ShouldSkipFrame(int skipped)
{
u32 TargetFPS = VideoInterface::GetTargetRefreshRate();
if (SConfig::GetInstance().m_EmulationSpeed > 0.0f)
TargetFPS = u32(TargetFPS * SConfig::GetInstance().m_EmulationSpeed);
const u32 frames = s_drawn_frame.load();
const bool fps_slow = !(s_timer.GetTimeDifference() < (frames + skipped) * 1000 / TargetFPS);
return fps_slow;
}
// --- Callbacks for backends / engine ---
// Should be called from GPU thread when a frame is drawn
void Callback_VideoCopiedToXFB(bool video_update)
{
if (video_update)
s_drawn_frame++;
Movie::FrameUpdate();
}
void UpdateTitle()
{
u32 ElapseTime = (u32)s_timer.GetTimeDifference();
s_request_refresh_info = false;
SConfig& _CoreParameter = SConfig::GetInstance();
if (ElapseTime == 0)
ElapseTime = 1;
float FPS = (float)(s_drawn_frame.load() * 1000.0 / ElapseTime);
float VPS = (float)(s_drawn_video.load() * 1000.0 / ElapseTime);
float Speed = (float)(s_drawn_video.load() * (100 * 1000.0) /
(VideoInterface::GetTargetRefreshRate() * ElapseTime));
// Settings are shown the same for both extended and summary info
std::string SSettings = StringFromFormat(
"%s %s | %s | %s", PowerPC::GetCPUName(), _CoreParameter.bCPUThread ? "DC" : "SC",
g_video_backend->GetDisplayName().c_str(), _CoreParameter.bDSPHLE ? "HLE" : "LLE");
std::string SFPS;
if (Movie::IsPlayingInput())
SFPS = StringFromFormat("Input: %u/%u - VI: %u - FPS: %.0f - VPS: %.0f - %.0f%%",
2016-08-04 16:54:45 +00:00
(u32)Movie::GetCurrentInputCount(), (u32)Movie::GetTotalInputCount(),
(u32)Movie::GetCurrentFrame(), FPS, VPS, Speed);
else if (Movie::IsRecordingInput())
SFPS = StringFromFormat("Input: %u - VI: %u - FPS: %.0f - VPS: %.0f - %.0f%%",
(u32)Movie::GetCurrentInputCount(), (u32)Movie::GetCurrentFrame(), FPS,
VPS, Speed);
else
{
SFPS = StringFromFormat("FPS: %.0f - VPS: %.0f - %.0f%%", FPS, VPS, Speed);
if (SConfig::GetInstance().m_InterfaceExtendedFPSInfo)
{
// Use extended or summary information. The summary information does not print the ticks data,
// that's more of a debugging interest, it can always be optional of course if someone is
// interested.
static u64 ticks = 0;
static u64 idleTicks = 0;
u64 newTicks = CoreTiming::GetTicks();
u64 newIdleTicks = CoreTiming::GetIdleTicks();
u64 diff = (newTicks - ticks) / 1000000;
u64 idleDiff = (newIdleTicks - idleTicks) / 1000000;
ticks = newTicks;
idleTicks = newIdleTicks;
float TicksPercentage =
(float)diff / (float)(SystemTimers::GetTicksPerSecond() / 1000000) * 100;
SFPS += StringFromFormat(" | CPU: ~%i MHz [Real: %i + IdleSkip: %i] / %i MHz (~%3.0f%%)",
(int)(diff), (int)(diff - idleDiff), (int)(idleDiff),
SystemTimers::GetTicksPerSecond() / 1000000, TicksPercentage);
}
}
// This is our final "frame counter" string
std::string SMessage = StringFromFormat("%s | %s", SSettings.c_str(), SFPS.c_str());
// Update the audio timestretcher with the current speed
if (g_sound_stream)
{
CMixer* pMixer = g_sound_stream->GetMixer();
pMixer->UpdateSpeed((float)Speed / 100);
}
Host_UpdateTitle(SMessage);
}
void Shutdown()
{
// During shutdown DXGI expects us to handle some messages on the UI thread.
// Therefore we can't immediately block and wait for the emu thread to shut
// down, so we join the emu thread as late as possible when the UI has already
// shut down.
// For more info read "DirectX Graphics Infrastructure (DXGI): Best Practices"
// on MSDN.
if (s_emu_thread.joinable())
s_emu_thread.join();
// Make sure there's nothing left over in case we're about to exit.
HostDispatchJobs();
}
void SetOnStoppedCallback(StoppedCallbackFunc callback)
{
s_on_stopped_callback = callback;
}
void UpdateWantDeterminism(bool initial)
{
// For now, this value is not itself configurable. Instead, individual
// settings that depend on it, such as GPU determinism mode. should have
// override options for testing,
2016-07-05 16:50:06 +00:00
bool new_want_determinism = Movie::IsMovieActive() || NetPlay::IsNetPlayRunning();
if (new_want_determinism != g_want_determinism || initial)
{
NOTICE_LOG(COMMON, "Want determinism <- %s", new_want_determinism ? "true" : "false");
bool was_unpaused = Core::PauseAndLock(true);
g_want_determinism = new_want_determinism;
IOS::HLE::WiiSockMan::GetInstance().UpdateWantDeterminism(new_want_determinism);
Fifo::UpdateWantDeterminism(new_want_determinism);
// We need to clear the cache because some parts of the JIT depend on want_determinism, e.g. use
// of FMA.
JitInterface::ClearCache();
Core::InitializeWiiRoot(g_want_determinism);
Core::PauseAndLock(false, was_unpaused);
}
}
void QueueHostJob(std::function<void()> job, bool run_during_stop)
{
if (!job)
return;
bool send_message = false;
{
std::lock_guard<std::mutex> guard(s_host_jobs_lock);
send_message = s_host_jobs_queue.empty();
s_host_jobs_queue.emplace(HostJob{std::move(job), run_during_stop});
}
// If the the queue was empty then kick the Host to come and get this job.
if (send_message)
Host_Message(WM_USER_JOB_DISPATCH);
}
void HostDispatchJobs()
{
// WARNING: This should only run on the Host Thread.
// NOTE: This function is potentially re-entrant. If a job calls
// Core::Stop for instance then we'll enter this a second time.
std::unique_lock<std::mutex> guard(s_host_jobs_lock);
while (!s_host_jobs_queue.empty())
{
HostJob job = std::move(s_host_jobs_queue.front());
s_host_jobs_queue.pop();
// NOTE: Memory ordering is important. The booting flag needs to be
// checked first because the state transition is:
// CORE_UNINITIALIZED: s_is_booting -> s_hardware_initialized
// We need to check variables in the same order as the state
// transition, otherwise we race and get transient failures.
if (!job.run_after_stop && !s_is_booting.IsSet() && !IsRunning())
continue;
guard.unlock();
job.job();
guard.lock();
}
}
} // Core