mirror of
https://github.com/Tinob/Ishiiruka.git
synced 2024-06-16 03:17:27 -04:00
merge latest master changes
add correct texture dumping to all backends
This commit is contained in:
parent
c87cca23d9
commit
1ed5b02613
|
@ -2,11 +2,12 @@
|
|||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
FastDiscSpeed = True
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues =
|
||||
EmulationIssues = Can hang during loading screens if Speed Up Disc Transfer Rate isn't used
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
FastDiscSpeed = True
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues =
|
||||
EmulationIssues = Can hang during loading screens if Speed Up Disc Transfer Rate isn't used
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# GINE69, GINX69 - Batman
|
||||
# GINE69, GINX69 - Batman Begins
|
||||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
|
@ -6,7 +6,7 @@
|
|||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues = FPS drops to 20 in some areas (don't know if this is normal)
|
||||
EmulationIssues =
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
@ -16,4 +16,3 @@ EmulationIssues = FPS drops to 20 in some areas (don't know if this is normal)
|
|||
|
||||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# GINE69 - Batman
|
||||
# GINE69 - Batman Begins
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
|
6
Data/Sys/GameSettings/GINX69.ini
Normal file
6
Data/Sys/GameSettings/GINX69.ini
Normal file
|
@ -0,0 +1,6 @@
|
|||
# GINX69 - Batman Begins
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 3
|
||||
EmulationIssues = The game speed is too slow when emulation is running at 100% speed
|
|
@ -17,8 +17,5 @@ EmulationIssues =
|
|||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Hacks]
|
||||
EFBEmulateFormatChanges = True
|
||||
|
||||
[Video_Stereoscopy]
|
||||
StereoConvergence = 6
|
||||
|
|
22
Data/Sys/GameSettings/RIZ.ini
Normal file
22
Data/Sys/GameSettings/RIZ.ini
Normal file
|
@ -0,0 +1,22 @@
|
|||
# RIZENR - Indianapolis 500 Legends
|
||||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues =
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
||||
[OnFrame]
|
||||
# Add memory patches to be applied every frame here.
|
||||
|
||||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Settings]
|
||||
UseXFB = True
|
||||
UseRealXFB = False
|
|
@ -6,7 +6,7 @@
|
|||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 5
|
||||
EmulationIssues =
|
||||
EmulationIssues = Needs real xfb for the videos to display.
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
@ -17,3 +17,6 @@ EmulationIssues =
|
|||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Settings]
|
||||
UseXFB = True
|
||||
UseRealXFB = True
|
||||
|
|
22
Data/Sys/GameSettings/RMO.ini
Normal file
22
Data/Sys/GameSettings/RMO.ini
Normal file
|
@ -0,0 +1,22 @@
|
|||
# RMOE52 - Monster Jam
|
||||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues = Flickers if XFB is disabled
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
||||
[OnFrame]
|
||||
# Add memory patches to be applied every frame here.
|
||||
|
||||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Settings]
|
||||
UseXFB = True
|
||||
UseRealXFB = False
|
24
Data/Sys/GameSettings/RWO.ini
Normal file
24
Data/Sys/GameSettings/RWO.ini
Normal file
|
@ -0,0 +1,24 @@
|
|||
# RWOE69, RWOJ13, RWOP69 - Monopoly
|
||||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues =
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
||||
[OnFrame]
|
||||
# Add memory patches to be applied every frame here.
|
||||
|
||||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Hacks]
|
||||
EFBEmulateFormatChanges = True
|
||||
|
||||
[Video_Settings]
|
||||
SafeTextureCacheColorSamples = 0
|
22
Data/Sys/GameSettings/SPR.ini
Normal file
22
Data/Sys/GameSettings/SPR.ini
Normal file
|
@ -0,0 +1,22 @@
|
|||
# SPRE41 - The Price Is Right 2010 Edition
|
||||
|
||||
[Core]
|
||||
# Values set here will override the main Dolphin settings.
|
||||
|
||||
[EmuState]
|
||||
# The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||
EmulationStateId = 4
|
||||
EmulationIssues =
|
||||
|
||||
[OnLoad]
|
||||
# Add memory patches to be loaded once on boot here.
|
||||
|
||||
[OnFrame]
|
||||
# Add memory patches to be applied every frame here.
|
||||
|
||||
[ActionReplay]
|
||||
# Add action replay cheats here.
|
||||
|
||||
[Video_Settings]
|
||||
UseXFB = True
|
||||
UseRealXFB = False
|
|
@ -27,7 +27,7 @@ public final class Game
|
|||
public static final int COUNTRY_WORLD = 12;
|
||||
public static final int COUNTRY_UNKNOWN = 13;
|
||||
|
||||
private static final String PATH_SCREENSHOT_FOLDER = Environment.getExternalStorageDirectory().getPath() + "/dolphin-emu/ScreenShots/";
|
||||
private static final String PATH_SCREENSHOT_FOLDER = Environment.getExternalStorageDirectory().getPath() + "dolphin-emu/ScreenShots/";
|
||||
|
||||
private String mTitle;
|
||||
private String mDescription;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <cstdlib>
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <android/log.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
|
@ -29,7 +31,6 @@
|
|||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/Profiler.h"
|
||||
|
||||
#include "DiscIO/Volume.h"
|
||||
|
@ -65,9 +66,24 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
void Host_NotifyMapLoaded() {}
|
||||
void Host_RefreshDSPDebuggerWindow() {}
|
||||
|
||||
// The Core only supports using a single Host thread.
|
||||
// If multiple threads want to call host functions then they need to queue
|
||||
// sequentially for access.
|
||||
static std::mutex s_host_identity_lock;
|
||||
Common::Event updateMainFrameEvent;
|
||||
static bool s_have_wm_user_stop = false;
|
||||
void Host_Message(int Id)
|
||||
{
|
||||
if (Id == WM_USER_JOB_DISPATCH)
|
||||
{
|
||||
updateMainFrameEvent.Set();
|
||||
}
|
||||
else if (Id == WM_USER_STOP)
|
||||
{
|
||||
s_have_wm_user_stop = true;
|
||||
if (Core::IsRunning())
|
||||
Core::QueueHostJob(&Core::Stop);
|
||||
}
|
||||
}
|
||||
|
||||
void* Host_GetRenderHandle()
|
||||
|
@ -388,15 +404,18 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_UnPauseEmulation(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
Core::SetState(Core::CORE_RUN);
|
||||
}
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
Core::SaveScreenShot("thumb");
|
||||
Renderer::s_screenshotCompleted.WaitFor(std::chrono::seconds(2));
|
||||
Core::Stop();
|
||||
|
@ -485,6 +504,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Supports
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
Core::SaveScreenShot();
|
||||
}
|
||||
|
||||
|
@ -529,11 +549,13 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetFilename(
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv *env, jobject obj, jint slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
State::Save(slot);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv *env, jobject obj, jint slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
State::Load(slot);
|
||||
}
|
||||
|
||||
|
@ -560,6 +582,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_CreateUserFo
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory(JNIEnv *env, jobject obj, jstring jDirectory)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
std::string directory = GetJString(env, jDirectory);
|
||||
g_set_userpath = directory;
|
||||
UICommon::SetUserDirectory(directory);
|
||||
|
@ -572,6 +595,7 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserDi
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling(JNIEnv *env, jobject obj, jboolean enable)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
JitInterface::ClearCache();
|
||||
Profiler::g_ProfileBlocks = enable;
|
||||
|
@ -580,6 +604,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
std::string filename = File::GetUserPath(D_DUMP_IDX) + "Debug/profiler.txt";
|
||||
File::CreateFullPath(filename);
|
||||
JitInterface::WriteProfileResults(filename);
|
||||
|
@ -638,6 +663,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
|
|||
}
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv *env, jobject obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
WiimoteReal::Refresh();
|
||||
}
|
||||
|
||||
|
@ -651,21 +677,37 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *
|
|||
|
||||
RegisterMsgAlertHandler(&MsgAlert);
|
||||
|
||||
std::unique_lock<std::mutex> guard(s_host_identity_lock);
|
||||
UICommon::SetUserDirectory(g_set_userpath);
|
||||
UICommon::Init();
|
||||
|
||||
WiimoteReal::InitAdapterClass();
|
||||
|
||||
// No use running the loop when booting fails
|
||||
s_have_wm_user_stop = false;
|
||||
if ( BootManager::BootCore( g_filename.c_str() ) )
|
||||
{
|
||||
PowerPC::Start();
|
||||
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
static constexpr int TIMEOUT = 10000;
|
||||
static constexpr int WAIT_STEP = 25;
|
||||
int time_waited = 0;
|
||||
// A Core::CORE_ERROR state would be helpful here.
|
||||
while (!Core::IsRunning() && time_waited < TIMEOUT && !s_have_wm_user_stop)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_STEP));
|
||||
time_waited += WAIT_STEP;
|
||||
}
|
||||
while (Core::IsRunning())
|
||||
{
|
||||
guard.unlock();
|
||||
updateMainFrameEvent.Wait();
|
||||
guard.lock();
|
||||
Core::HostDispatchJobs();
|
||||
}
|
||||
}
|
||||
|
||||
Core::Shutdown();
|
||||
UICommon::Shutdown();
|
||||
guard.unlock();
|
||||
|
||||
if (surf)
|
||||
{
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
#include "Core/HW/AudioInterface.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
|
||||
// UGLINESS
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
@ -154,12 +151,6 @@ u32 CMixer::Mix(s16* samples, u32 num_samples, bool consider_framelimit)
|
|||
if (!samples)
|
||||
return 0;
|
||||
std::lock_guard<std::mutex> lk(m_cs_mixing);
|
||||
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
|
||||
{
|
||||
// Silence
|
||||
memset(samples, 0, num_samples * 2 * sizeof(s16));
|
||||
return num_samples;
|
||||
}
|
||||
// reset float output buffer
|
||||
m_output_buffer.resize(num_samples * 2);
|
||||
std::fill_n(m_output_buffer.begin(), num_samples * 2, 0.f);
|
||||
|
@ -185,11 +176,6 @@ u32 CMixer::Mix(float* samples, u32 num_samples, bool consider_framelimit)
|
|||
return 0;
|
||||
std::lock_guard<std::mutex> lk(m_cs_mixing);
|
||||
memset(samples, 0, num_samples * 2 * sizeof(float));
|
||||
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
|
||||
{
|
||||
// Silence
|
||||
return num_samples;
|
||||
}
|
||||
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
|
|
|
@ -84,6 +84,7 @@ enum HOST_COMM
|
|||
WM_USER_STOP = 10,
|
||||
WM_USER_CREATE,
|
||||
WM_USER_SETCURSOR,
|
||||
WM_USER_JOB_DISPATCH,
|
||||
};
|
||||
|
||||
// Used for notification on emulation state
|
||||
|
|
|
@ -42,8 +42,7 @@ public:
|
|||
return;
|
||||
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_condvar.wait(lk, [&]{ return m_flag.IsSet(); });
|
||||
m_flag.Clear();
|
||||
m_condvar.wait(lk, [&]{ return m_flag.TestAndClear(); });
|
||||
}
|
||||
|
||||
template<class Rep, class Period>
|
||||
|
@ -54,8 +53,7 @@ public:
|
|||
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
bool signaled = m_condvar.wait_for(lk, rel_time,
|
||||
[&]{ return m_flag.IsSet(); });
|
||||
m_flag.Clear();
|
||||
[&]{ return m_flag.TestAndClear(); });
|
||||
|
||||
return signaled;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include "Common/GL/GLInterface/EGL.h"
|
||||
|
||||
class cInterfaceEGLAndroid : public cInterfaceEGL
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
@ -102,6 +105,7 @@ void EmuThread();
|
|||
static bool s_is_stopping = false;
|
||||
static bool s_hardware_initialized = false;
|
||||
static bool s_is_started = false;
|
||||
static std::atomic<bool> s_is_booting{ false };
|
||||
static void* s_window_handle = nullptr;
|
||||
static std::string s_state_filename;
|
||||
static std::thread s_emu_thread;
|
||||
|
@ -112,6 +116,14 @@ 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
|
||||
|
@ -225,6 +237,9 @@ bool Init()
|
|||
s_emu_thread.join();
|
||||
}
|
||||
|
||||
// Drain any left over jobs
|
||||
HostDispatchJobs();
|
||||
|
||||
Core::UpdateWantDeterminism(/*initial*/ true);
|
||||
|
||||
INFO_LOG(OSREPORT, "Starting core = %s mode",
|
||||
|
@ -260,16 +275,16 @@ void Stop() // - Hammertime!
|
|||
|
||||
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());
|
||||
PowerPC::Stop();
|
||||
|
||||
// Kick it if it's waiting (code stepping wait loop)
|
||||
CPU::StepOpcode();
|
||||
CPU::Stop();
|
||||
|
||||
if (_CoreParameter.bCPUThread)
|
||||
{
|
||||
|
@ -313,6 +328,16 @@ void UndeclareAsCPUThread()
|
|||
#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.
|
||||
static void CpuThread()
|
||||
{
|
||||
|
@ -335,10 +360,20 @@ static void CpuThread()
|
|||
EMM::InstallExceptionHandler(); // Let's run under memory watch
|
||||
|
||||
if (!s_state_filename.empty())
|
||||
State::LoadAs(s_state_filename);
|
||||
{
|
||||
// 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
|
||||
|
@ -377,6 +412,7 @@ static void CpuThread()
|
|||
|
||||
static void FifoPlayerThread()
|
||||
{
|
||||
DeclareAsCPUThread();
|
||||
const SConfig& _CoreParameter = SConfig::GetInstance();
|
||||
VideoBackendBase* video_backend = g_video_backend;
|
||||
if (_CoreParameter.bCPUThread)
|
||||
|
@ -389,21 +425,40 @@ static void FifoPlayerThread()
|
|||
Common::SetCurrentThreadName("FIFO-GPU thread");
|
||||
}
|
||||
|
||||
s_is_started = true;
|
||||
DeclareAsCPUThread();
|
||||
|
||||
// Enter CPU run loop. When we leave it - we are done.
|
||||
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
|
||||
{
|
||||
FifoPlayer::GetInstance().Play();
|
||||
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();
|
||||
}
|
||||
|
||||
UndeclareAsCPUThread();
|
||||
s_is_started = false;
|
||||
// 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)
|
||||
video_backend->PeekMessages();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
s_is_started = false;
|
||||
}
|
||||
|
||||
if (!_CoreParameter.bCPUThread)
|
||||
g_video_backend->Video_Cleanup();
|
||||
video_backend->Video_Cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -414,6 +469,7 @@ static void FifoPlayerThread()
|
|||
void EmuThread()
|
||||
{
|
||||
const SConfig& core_parameter = SConfig::GetInstance();
|
||||
s_is_booting.store(true);
|
||||
|
||||
Common::SetCurrentThreadName("Emuthread - Starting");
|
||||
VideoBackendBase* video_backend = g_video_backend;
|
||||
|
@ -432,6 +488,7 @@ void EmuThread()
|
|||
|
||||
if (!g_video_backend->Initialize(s_window_handle))
|
||||
{
|
||||
s_is_booting.store(false);
|
||||
PanicAlert("Failed to initialize video backend!");
|
||||
Host_Message(WM_USER_STOP);
|
||||
return;
|
||||
|
@ -446,6 +503,7 @@ void EmuThread()
|
|||
|
||||
if (!DSP::GetDSPEmulator()->Initialize(core_parameter.bWii, core_parameter.bDSPThread))
|
||||
{
|
||||
s_is_booting.store(false);
|
||||
HW::Shutdown();
|
||||
video_backend->Shutdown();
|
||||
PanicAlert("Failed to initialize DSP emulation!");
|
||||
|
@ -486,9 +544,10 @@ void EmuThread()
|
|||
|
||||
// The hardware is initialized.
|
||||
s_hardware_initialized = true;
|
||||
s_is_booting.store(false);
|
||||
|
||||
// Boot to pause or not
|
||||
Core::SetState(core_parameter.bBootToPause ? Core::CORE_PAUSE : Core::CORE_RUN);
|
||||
// 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::MODE_INTERPRETER);
|
||||
|
@ -553,7 +612,7 @@ void EmuThread()
|
|||
// Spawn the CPU+GPU thread
|
||||
s_cpu_thread = std::thread(cpuThreadFunc);
|
||||
|
||||
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
while (CPU::GetState() != CPU::CPU_POWERDOWN)
|
||||
{
|
||||
video_backend->PeekMessages();
|
||||
Common::SleepCurrentThread(20);
|
||||
|
@ -622,11 +681,17 @@ void EmuThread()
|
|||
|
||||
// Set or get the running state
|
||||
|
||||
void SetState(EState _State)
|
||||
void SetState(EState state)
|
||||
{
|
||||
switch (_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__) || defined(_WIN32)
|
||||
|
@ -720,30 +785,50 @@ void RequestRefreshInfo()
|
|||
s_request_refresh_info = true;
|
||||
}
|
||||
|
||||
bool PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||
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 (doLock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth)
|
||||
if (do_lock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth)
|
||||
return true;
|
||||
|
||||
// first pause or unpause the CPU
|
||||
bool wasUnpaused = CPU::PauseAndLock(doLock, unpauseOnUnlock);
|
||||
ExpansionInterface::PauseAndLock(doLock, unpauseOnUnlock);
|
||||
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(doLock, unpauseOnUnlock);
|
||||
DSP::GetDSPEmulator()->PauseAndLock(do_lock, false);
|
||||
|
||||
// video has to come after CPU, because CPU thread can wait for video thread (s_efbAccessRequested).
|
||||
Fifo::PauseAndLock(doLock, unpauseOnUnlock);
|
||||
Fifo::PauseAndLock(do_lock, false);
|
||||
|
||||
#if defined(__LIBUSB__) || defined(_WIN32)
|
||||
GCAdapter::ResetRumble();
|
||||
#endif
|
||||
return wasUnpaused;
|
||||
|
||||
// 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
|
||||
|
@ -815,7 +900,7 @@ void UpdateTitle()
|
|||
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", cpu_core_base->GetName(), _CoreParameter.bCPUThread ? "DC" : "SC",
|
||||
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;
|
||||
|
@ -869,6 +954,9 @@ void Shutdown()
|
|||
// 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)
|
||||
|
@ -902,4 +990,45 @@ void UpdateWantDeterminism(bool initial)
|
|||
}
|
||||
}
|
||||
|
||||
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.load() && !IsRunning())
|
||||
continue;
|
||||
|
||||
guard.unlock();
|
||||
job.job();
|
||||
guard.lock();
|
||||
}
|
||||
}
|
||||
|
||||
} // Core
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -52,7 +53,8 @@ bool IsRunningInCurrentThread(); // this tells us whether we are running in the
|
|||
bool IsCPUThread(); // this tells us whether we are the CPU thread.
|
||||
bool IsGPUThread();
|
||||
|
||||
void SetState(EState _State);
|
||||
// [NOT THREADSAFE] For use by Host only
|
||||
void SetState(EState state);
|
||||
EState GetState();
|
||||
|
||||
void SaveScreenShot();
|
||||
|
@ -80,13 +82,29 @@ void UpdateTitle();
|
|||
// or, if doLock is false, releases a lock on that state and optionally unpauses.
|
||||
// calls must be balanced (once with doLock true, then once with doLock false) but may be recursive.
|
||||
// the return value of the first call should be passed in as the second argument of the second call.
|
||||
bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
|
||||
// [NOT THREADSAFE] Host only
|
||||
bool PauseAndLock(bool doLock, bool unpauseOnUnlock = true);
|
||||
|
||||
// for calling back into UI code without introducing a dependency on it in core
|
||||
typedef void(*StoppedCallbackFunc)(void);
|
||||
void SetOnStoppedCallback(StoppedCallbackFunc callback);
|
||||
|
||||
// Run on the GUI thread when the factors change.
|
||||
// Run on the Host thread when the factors change. [NOT THREADSAFE]
|
||||
void UpdateWantDeterminism(bool initial = false);
|
||||
|
||||
// Queue an arbitrary function to asynchronously run once on the Host thread later.
|
||||
// Threadsafe. Can be called by any thread, including the Host itself.
|
||||
// Jobs will be executed in RELATIVE order. If you queue 2 jobs from the same thread
|
||||
// then they will be executed in the order they were queued; however, there is no
|
||||
// global order guarantee across threads - jobs from other threads may execute in
|
||||
// between.
|
||||
// NOTE: Make sure the jobs check the global state instead of assuming everything is
|
||||
// still the same as when the job was queued.
|
||||
// NOTE: Jobs that are not set to run during stop will be discarded instead.
|
||||
void QueueHostJob(std::function<void()> job, bool run_during_stop = false);
|
||||
|
||||
// Should be called periodically by the Host to run pending jobs.
|
||||
// WM_USER_JOB_DISPATCH will be sent when something is added to the queue.
|
||||
void HostDispatchJobs();
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "Core/Host.h"
|
||||
#include "Core/Debugger/Debugger_SymbolMap.h"
|
||||
#include "Core/Debugger/PPCDebugInterface.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/DSP.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
@ -20,7 +19,7 @@
|
|||
std::string PPCDebugInterface::Disassemble(unsigned int address)
|
||||
{
|
||||
// PowerPC::HostRead_U32 seemed to crash on shutdown
|
||||
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
|
||||
if (!IsAlive())
|
||||
return "";
|
||||
|
||||
if (Core::GetState() == Core::CORE_PAUSE)
|
||||
|
@ -51,7 +50,7 @@ std::string PPCDebugInterface::Disassemble(unsigned int address)
|
|||
|
||||
void PPCDebugInterface::GetRawMemoryString(int memory, unsigned int address, char *dest, int max_size)
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (IsAlive())
|
||||
{
|
||||
if (memory || PowerPC::HostIsRAMAddress(address))
|
||||
{
|
||||
|
@ -96,7 +95,7 @@ unsigned int PPCDebugInterface::ReadInstruction(unsigned int address)
|
|||
|
||||
bool PPCDebugInterface::IsAlive()
|
||||
{
|
||||
return Core::GetState() != Core::CORE_UNINITIALIZED;
|
||||
return Core::IsRunning();
|
||||
}
|
||||
|
||||
bool PPCDebugInterface::IsBreakpoint(unsigned int address)
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/FifoPlayer/FifoDataFile.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/GPFifo.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
|
@ -58,55 +60,103 @@ void FifoPlayer::Close()
|
|||
m_FrameRangeEnd = 0;
|
||||
}
|
||||
|
||||
bool FifoPlayer::Play()
|
||||
class FifoPlayer::CPUCore final : public CPUCoreBase
|
||||
{
|
||||
if (!m_File)
|
||||
return false;
|
||||
|
||||
if (m_File->GetFrameCount() == 0)
|
||||
return false;
|
||||
|
||||
IsPlayingBackFifologWithBrokenEFBCopies = m_File->HasBrokenEFBCopies();
|
||||
|
||||
m_CurrentFrame = m_FrameRangeStart;
|
||||
|
||||
LoadMemory();
|
||||
|
||||
// This loop replaces the CPU loop that occurs when a game is run
|
||||
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
public:
|
||||
explicit CPUCore(FifoPlayer* parent)
|
||||
: m_parent(parent)
|
||||
{
|
||||
if (PowerPC::GetState() == PowerPC::CPU_RUNNING)
|
||||
}
|
||||
CPUCore(const CPUCore&) = delete;
|
||||
~CPUCore()
|
||||
{
|
||||
}
|
||||
CPUCore& operator=(const CPUCore&) = delete;
|
||||
|
||||
void Init() override
|
||||
{
|
||||
IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
|
||||
|
||||
m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
|
||||
m_parent->LoadMemory();
|
||||
}
|
||||
|
||||
void Shutdown() override
|
||||
{
|
||||
IsPlayingBackFifologWithBrokenEFBCopies = false;
|
||||
}
|
||||
|
||||
void ClearCache() override
|
||||
{
|
||||
// Nothing to clear.
|
||||
}
|
||||
|
||||
void SingleStep() override
|
||||
{
|
||||
// NOTE: AdvanceFrame() will get stuck forever in Dual Core because the FIFO
|
||||
// is disabled by CPU::EnableStepping(true) so the frame never gets displayed.
|
||||
PanicAlertT("Cannot SingleStep the FIFO. Use Frame Advance instead.");
|
||||
}
|
||||
|
||||
const char* GetName() override
|
||||
{
|
||||
return "FifoPlayer";
|
||||
}
|
||||
|
||||
void Run() override
|
||||
{
|
||||
while (CPU::GetState() == CPU::CPU_RUNNING)
|
||||
{
|
||||
if (m_CurrentFrame >= m_FrameRangeEnd)
|
||||
switch (m_parent->AdvanceFrame())
|
||||
{
|
||||
if (m_Loop)
|
||||
{
|
||||
m_CurrentFrame = m_FrameRangeStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::Stop();
|
||||
Host_Message(WM_USER_STOP);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_FrameWrittenCb)
|
||||
m_FrameWrittenCb();
|
||||
case CPU::CPU_POWERDOWN:
|
||||
CPU::Break();
|
||||
Host_Message(WM_USER_STOP);
|
||||
break;
|
||||
|
||||
if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
|
||||
WriteAllMemoryUpdates();
|
||||
|
||||
WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
|
||||
|
||||
++m_CurrentFrame;
|
||||
case CPU::CPU_STEPPING:
|
||||
CPU::Break();
|
||||
Host_UpdateMainFrame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsPlayingBackFifologWithBrokenEFBCopies = false;
|
||||
private:
|
||||
FifoPlayer* m_parent;
|
||||
};
|
||||
|
||||
return true;
|
||||
int FifoPlayer::AdvanceFrame()
|
||||
{
|
||||
if (m_CurrentFrame >= m_FrameRangeEnd)
|
||||
{
|
||||
if (!m_Loop)
|
||||
return CPU::CPU_POWERDOWN;
|
||||
// If there are zero frames in the range then sleep instead of busy spinning
|
||||
if (m_FrameRangeStart >= m_FrameRangeEnd)
|
||||
return CPU::CPU_STEPPING;
|
||||
|
||||
m_CurrentFrame = m_FrameRangeStart;
|
||||
}
|
||||
|
||||
if (m_FrameWrittenCb)
|
||||
m_FrameWrittenCb();
|
||||
|
||||
if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
|
||||
WriteAllMemoryUpdates();
|
||||
|
||||
WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
|
||||
|
||||
++m_CurrentFrame;
|
||||
return CPU::CPU_RUNNING;
|
||||
}
|
||||
|
||||
std::unique_ptr<CPUCoreBase> FifoPlayer::GetCPUCore()
|
||||
{
|
||||
if (!m_File || m_File->GetFrameCount() == 0)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<CPUCore>(this);
|
||||
}
|
||||
|
||||
u32 FifoPlayer::GetFrameObjectCount()
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h"
|
||||
#include "Core/PowerPC/CPUCoreBase.h"
|
||||
|
||||
class FifoDataFile;
|
||||
struct MemoryUpdate;
|
||||
|
@ -50,8 +52,12 @@ public:
|
|||
bool Open(const std::string& filename);
|
||||
void Close();
|
||||
|
||||
// Play is controlled by the state of PowerPC
|
||||
bool Play();
|
||||
// Returns a CPUCoreBase instance that can be injected into PowerPC as a
|
||||
// pseudo-CPU. The instance is only valid while the FifoPlayer is Open().
|
||||
// Returns nullptr if the FifoPlayer is not initialized correctly.
|
||||
// Play/Pause/Stop of the FifoLog can be controlled normally via the
|
||||
// PowerPC state.
|
||||
std::unique_ptr<CPUCoreBase> GetCPUCore();
|
||||
|
||||
FifoDataFile *GetFile() { return m_File; }
|
||||
|
||||
|
@ -85,8 +91,12 @@ public:
|
|||
static FifoPlayer &GetInstance();
|
||||
|
||||
private:
|
||||
class CPUCore;
|
||||
|
||||
FifoPlayer();
|
||||
|
||||
int AdvanceFrame();
|
||||
|
||||
void WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo &info);
|
||||
void WriteFramePart(u32 dataStart, u32 dataEnd, u32 &nextMemUpdate, const FifoFrameInfo& frame, const AnalyzedFrameInfo& info);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HLE/HLE_Misc.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/PPCCache.h"
|
||||
|
||||
|
@ -35,7 +36,7 @@ void HLEPanicAlert()
|
|||
void HBReload()
|
||||
{
|
||||
// There isn't much we can do. Just stop cleanly.
|
||||
PowerPC::Pause();
|
||||
CPU::Break();
|
||||
Host_Message(WM_USER_STOP);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
|
@ -14,82 +16,177 @@
|
|||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static Common::Event m_StepEvent;
|
||||
static Common::Event *m_SyncEvent = nullptr;
|
||||
static std::mutex m_csCpuOccupied;
|
||||
}
|
||||
|
||||
namespace CPU
|
||||
{
|
||||
|
||||
// CPU Thread execution state.
|
||||
// Requires s_state_change_lock to modify the value.
|
||||
// Read access is unsynchronized.
|
||||
static State s_state = CPU_POWERDOWN;
|
||||
|
||||
// Synchronizes EnableStepping and PauseAndLock so only one instance can be
|
||||
// active at a time. Simplifies code by eliminating several edge cases where
|
||||
// the EnableStepping(true)/PauseAndLock(true) case must release the state lock
|
||||
// and wait for the CPU Thread which would otherwise require additional flags.
|
||||
// NOTE: When using the stepping lock, it must always be acquired first. If
|
||||
// the lock is acquired after the state lock then that is guaranteed to
|
||||
// deadlock because of the order inversion. (A -> X,Y; B -> Y,X; A waits for
|
||||
// B, B waits for A)
|
||||
static std::mutex s_stepping_lock;
|
||||
|
||||
// Primary lock. Protects changing s_state, requesting instruction stepping and
|
||||
// pause-and-locking.
|
||||
static std::mutex s_state_change_lock;
|
||||
// When s_state_cpu_thread_active changes to false
|
||||
static std::condition_variable s_state_cpu_idle_cvar;
|
||||
// When s_state changes / s_state_paused_and_locked becomes false (for CPU Thread only)
|
||||
static std::condition_variable s_state_cpu_cvar;
|
||||
static bool s_state_cpu_thread_active = false;
|
||||
static bool s_state_paused_and_locked = false;
|
||||
static bool s_state_system_request_stepping = false;
|
||||
static bool s_state_cpu_step_instruction = false;
|
||||
static Common::Event* s_state_cpu_step_instruction_sync = nullptr;
|
||||
|
||||
void Init(int cpu_core)
|
||||
{
|
||||
PowerPC::Init(cpu_core);
|
||||
m_SyncEvent = nullptr;
|
||||
s_state = CPU_STEPPING;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
Stop();
|
||||
PowerPC::Shutdown();
|
||||
m_SyncEvent = nullptr;
|
||||
}
|
||||
|
||||
// Requires holding s_state_change_lock
|
||||
static void FlushStepSyncEventLocked()
|
||||
{
|
||||
if (s_state_cpu_step_instruction_sync)
|
||||
{
|
||||
s_state_cpu_step_instruction_sync->Set();
|
||||
s_state_cpu_step_instruction_sync = nullptr;
|
||||
}
|
||||
s_state_cpu_step_instruction = false;
|
||||
}
|
||||
|
||||
void Run()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_csCpuOccupied);
|
||||
Host_UpdateDisasmDialog();
|
||||
|
||||
while (true)
|
||||
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
|
||||
while (s_state != CPU_POWERDOWN)
|
||||
{
|
||||
switch (PowerPC::GetState())
|
||||
s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
|
||||
|
||||
switch (s_state)
|
||||
{
|
||||
case PowerPC::CPU_RUNNING:
|
||||
//1: enter a fast runloop
|
||||
case CPU_RUNNING:
|
||||
s_state_cpu_thread_active = true;
|
||||
state_lock.unlock();
|
||||
|
||||
// Adjust PC for JIT when debugging
|
||||
// SingleStep so that the "continue", "step over" and "step out" debugger functions
|
||||
// work when the PC is at a breakpoint at the beginning of the block
|
||||
// If watchpoints are enabled, any instruction could be a breakpoint.
|
||||
if (PowerPC::GetMode() != PowerPC::MODE_INTERPRETER)
|
||||
{
|
||||
#ifndef ENABLE_MEM_CHECK
|
||||
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
|
||||
#endif
|
||||
{
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::MODE_INTERPRETER);
|
||||
PowerPC::SingleStep();
|
||||
PowerPC::SetMode(old_mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Enter a fast runloop
|
||||
PowerPC::RunLoop();
|
||||
|
||||
state_lock.lock();
|
||||
s_state_cpu_thread_active = false;
|
||||
s_state_cpu_idle_cvar.notify_all();
|
||||
break;
|
||||
|
||||
case PowerPC::CPU_STEPPING:
|
||||
m_csCpuOccupied.unlock();
|
||||
|
||||
//1: wait for step command..
|
||||
m_StepEvent.Wait();
|
||||
|
||||
m_csCpuOccupied.lock();
|
||||
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
|
||||
return;
|
||||
if (PowerPC::GetState() != PowerPC::CPU_STEPPING)
|
||||
case CPU_STEPPING:
|
||||
// Wait for step command.
|
||||
s_state_cpu_cvar.wait(state_lock, []
|
||||
{
|
||||
return s_state_cpu_step_instruction ||
|
||||
s_state != CPU_STEPPING;
|
||||
});
|
||||
if (s_state != CPU_STEPPING)
|
||||
{
|
||||
// Signal event if the mode changes.
|
||||
FlushStepSyncEventLocked();
|
||||
continue;
|
||||
}
|
||||
if (s_state_paused_and_locked)
|
||||
continue;
|
||||
|
||||
//3: do a step
|
||||
// Do step
|
||||
s_state_cpu_thread_active = true;
|
||||
state_lock.unlock();
|
||||
|
||||
PowerPC::SingleStep();
|
||||
|
||||
//4: update disasm dialog
|
||||
if (m_SyncEvent)
|
||||
{
|
||||
m_SyncEvent->Set();
|
||||
m_SyncEvent = nullptr;
|
||||
}
|
||||
state_lock.lock();
|
||||
s_state_cpu_thread_active = false;
|
||||
s_state_cpu_idle_cvar.notify_all();
|
||||
|
||||
// Update disasm dialog
|
||||
FlushStepSyncEventLocked();
|
||||
Host_UpdateDisasmDialog();
|
||||
break;
|
||||
|
||||
case PowerPC::CPU_POWERDOWN:
|
||||
//1: Exit loop!!
|
||||
return;
|
||||
case CPU_POWERDOWN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_lock.unlock();
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
// Requires holding s_state_change_lock
|
||||
static void RunAdjacentSystems(bool running)
|
||||
{
|
||||
// NOTE: We're assuming these will not try to call Break or EnableStepping.
|
||||
Fifo::EmulatorState(running);
|
||||
AudioCommon::ClearAudioBuffer(!running);
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
PowerPC::Stop();
|
||||
m_StepEvent.Set();
|
||||
// Change state and wait for it to be acknowledged.
|
||||
// We don't need the stepping lock because CPU_POWERDOWN is a priority state which
|
||||
// will stick permanently.
|
||||
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
|
||||
s_state = CPU_POWERDOWN;
|
||||
s_state_cpu_cvar.notify_one();
|
||||
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
|
||||
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), []
|
||||
{
|
||||
return !s_state_cpu_thread_active;
|
||||
});
|
||||
if (!success)
|
||||
ERROR_LOG(POWERPC, "CPU Thread failed to acknowledge CPU_POWERDOWN. It may be deadlocked.");
|
||||
RunAdjacentSystems(false);
|
||||
FlushStepSyncEventLocked();
|
||||
}
|
||||
|
||||
bool IsStepping()
|
||||
{
|
||||
return PowerPC::GetState() == PowerPC::CPU_STEPPING;
|
||||
return s_state == CPU_STEPPING;
|
||||
}
|
||||
|
||||
State GetState()
|
||||
{
|
||||
return s_state;
|
||||
}
|
||||
|
||||
const volatile State* GetStatePtr()
|
||||
{
|
||||
return &s_state;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
|
@ -98,87 +195,142 @@ void Reset()
|
|||
|
||||
void StepOpcode(Common::Event* event)
|
||||
{
|
||||
m_StepEvent.Set();
|
||||
if (PowerPC::GetState() == PowerPC::CPU_STEPPING)
|
||||
std::lock_guard<std::mutex> state_lock(s_state_change_lock);
|
||||
// If we're not stepping then this is pointless
|
||||
if (!IsStepping())
|
||||
{
|
||||
m_SyncEvent = event;
|
||||
if (event)
|
||||
event->Set();
|
||||
return;
|
||||
}
|
||||
|
||||
// Potential race where the previous step has not been serviced yet.
|
||||
if (s_state_cpu_step_instruction_sync && s_state_cpu_step_instruction_sync != event)
|
||||
s_state_cpu_step_instruction_sync->Set();
|
||||
|
||||
s_state_cpu_step_instruction = true;
|
||||
s_state_cpu_step_instruction_sync = event;
|
||||
s_state_cpu_cvar.notify_one();
|
||||
}
|
||||
|
||||
void EnableStepping(const bool stepping)
|
||||
// Requires s_state_change_lock
|
||||
static bool SetStateLocked(State s)
|
||||
{
|
||||
if (s_state == CPU_POWERDOWN)
|
||||
return false;
|
||||
s_state = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EnableStepping(bool stepping)
|
||||
{
|
||||
std::lock_guard<std::mutex> stepping_lock(s_stepping_lock);
|
||||
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
|
||||
|
||||
if (stepping)
|
||||
{
|
||||
PowerPC::Pause();
|
||||
m_StepEvent.Reset();
|
||||
Fifo::EmulatorState(false);
|
||||
AudioCommon::ClearAudioBuffer(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SingleStep so that the "continue", "step over" and "step out" debugger functions
|
||||
// work when the PC is at a breakpoint at the beginning of the block
|
||||
// If watchpoints are enabled, any instruction could be a breakpoint.
|
||||
bool could_be_bp;
|
||||
#ifdef ENABLE_MEM_CHECK
|
||||
could_be_bp = true;
|
||||
#else
|
||||
could_be_bp = PowerPC::breakpoints.IsAddressBreakPoint(PC);
|
||||
#endif
|
||||
if (could_be_bp && PowerPC::GetMode() != PowerPC::MODE_INTERPRETER)
|
||||
SetStateLocked(CPU_STEPPING);
|
||||
|
||||
// Wait for the CPU Thread to leave the run loop
|
||||
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
|
||||
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), []
|
||||
{
|
||||
PowerPC::CoreMode oldMode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::MODE_INTERPRETER);
|
||||
PowerPC::SingleStep();
|
||||
PowerPC::SetMode(oldMode);
|
||||
}
|
||||
PowerPC::Start();
|
||||
m_StepEvent.Set();
|
||||
Fifo::EmulatorState(true);
|
||||
AudioCommon::ClearAudioBuffer(false);
|
||||
return !s_state_cpu_thread_active;
|
||||
});
|
||||
if (!success)
|
||||
ERROR_LOG(POWERPC, "Abandoned waiting for CPU Thread! The Core may be deadlocked.");
|
||||
|
||||
RunAdjacentSystems(false);
|
||||
}
|
||||
else if (SetStateLocked(CPU_RUNNING))
|
||||
{
|
||||
s_state_cpu_cvar.notify_one();
|
||||
RunAdjacentSystems(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Break()
|
||||
{
|
||||
EnableStepping(true);
|
||||
std::lock_guard<std::mutex> state_lock(s_state_change_lock);
|
||||
|
||||
// If another thread is trying to PauseAndLock then we need to remember this
|
||||
// for later to ignore the unpause_on_unlock.
|
||||
if (s_state_paused_and_locked)
|
||||
{
|
||||
s_state_system_request_stepping = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll deadlock if we synchronize, the CPU may block waiting for our caller to
|
||||
// finish resulting in the CPU loop never terminating.
|
||||
SetStateLocked(CPU_STEPPING);
|
||||
RunAdjacentSystems(false);
|
||||
}
|
||||
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock)
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
|
||||
{
|
||||
static bool s_have_fake_cpu_thread;
|
||||
bool wasUnpaused = !IsStepping();
|
||||
// NOTE: This is protected by s_stepping_lock.
|
||||
static bool s_have_fake_cpu_thread = false;
|
||||
bool was_unpaused = false;
|
||||
|
||||
if (do_lock)
|
||||
{
|
||||
// we can't use EnableStepping, that would causes deadlocks with both audio and video
|
||||
PowerPC::Pause();
|
||||
s_stepping_lock.lock();
|
||||
|
||||
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
|
||||
s_state_paused_and_locked = true;
|
||||
|
||||
was_unpaused = s_state == CPU_RUNNING;
|
||||
SetStateLocked(CPU_STEPPING);
|
||||
|
||||
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
|
||||
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(10), []
|
||||
{
|
||||
return !s_state_cpu_thread_active;
|
||||
});
|
||||
if (!success)
|
||||
NOTICE_LOG(POWERPC, "Abandoned CPU Thread synchronization in CPU::PauseAndLock! We'll probably crash now.");
|
||||
|
||||
if (control_adjacent)
|
||||
RunAdjacentSystems(false);
|
||||
state_lock.unlock();
|
||||
|
||||
// NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a
|
||||
// depth counter instead of being a boolean.
|
||||
if (!Core::IsCPUThread())
|
||||
{
|
||||
m_csCpuOccupied.lock();
|
||||
s_have_fake_cpu_thread = true;
|
||||
Core::DeclareAsCPUThread();
|
||||
}
|
||||
else
|
||||
{
|
||||
s_have_fake_cpu_thread = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unpause_on_unlock)
|
||||
{
|
||||
PowerPC::Start();
|
||||
m_StepEvent.Set();
|
||||
}
|
||||
|
||||
// Only need the stepping lock for this
|
||||
if (s_have_fake_cpu_thread)
|
||||
{
|
||||
Core::UndeclareAsCPUThread();
|
||||
m_csCpuOccupied.unlock();
|
||||
s_have_fake_cpu_thread = false;
|
||||
Core::UndeclareAsCPUThread();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(s_state_change_lock);
|
||||
if (s_state_system_request_stepping)
|
||||
{
|
||||
s_state_system_request_stepping = false;
|
||||
}
|
||||
else if (unpause_on_unlock && SetStateLocked(CPU_RUNNING))
|
||||
{
|
||||
was_unpaused = true;
|
||||
}
|
||||
s_state_paused_and_locked = false;
|
||||
s_state_cpu_cvar.notify_one();
|
||||
|
||||
if (control_adjacent)
|
||||
RunAdjacentSystems(s_state == CPU_RUNNING);
|
||||
}
|
||||
s_stepping_lock.unlock();
|
||||
}
|
||||
return wasUnpaused;
|
||||
return was_unpaused;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,13 @@ namespace Common {
|
|||
namespace CPU
|
||||
{
|
||||
|
||||
enum State
|
||||
{
|
||||
CPU_RUNNING = 0,
|
||||
CPU_STEPPING = 2,
|
||||
CPU_POWERDOWN = 3
|
||||
};
|
||||
|
||||
// Init
|
||||
void Init(int cpu_core);
|
||||
|
||||
|
@ -18,32 +25,49 @@ void Init(int cpu_core);
|
|||
void Shutdown();
|
||||
|
||||
// Starts the CPU
|
||||
// To be called by the CPU Thread.
|
||||
void Run();
|
||||
|
||||
// Causes shutdown
|
||||
// Once started, CPU_POWERDOWN cannot be stopped.
|
||||
// Synchronizes with the CPU Thread (waits for CPU::Run to exit).
|
||||
void Stop();
|
||||
|
||||
// Reset
|
||||
// Reset [NOT IMPLEMENTED]
|
||||
void Reset();
|
||||
|
||||
// StepOpcode (Steps one Opcode)
|
||||
void StepOpcode(Common::Event* event = nullptr);
|
||||
|
||||
// Enable or Disable Stepping
|
||||
// Enable or Disable Stepping. [Will deadlock if called from a system thread]
|
||||
void EnableStepping(bool stepping);
|
||||
|
||||
// Break, same as EnableStepping(true).
|
||||
// Breakpoint activation for system threads. Similar to EnableStepping(true).
|
||||
// NOTE: Unlike EnableStepping, this does NOT synchronize with the CPU Thread
|
||||
// which enables it to avoid deadlocks but also makes it less safe so it
|
||||
// should not be used by the Host.
|
||||
void Break();
|
||||
|
||||
// Is stepping ?
|
||||
// Shorthand for GetState() == CPU_STEPPING.
|
||||
// WARNING: CPU_POWERDOWN will return false, not just CPU_RUNNING.
|
||||
bool IsStepping();
|
||||
|
||||
// Waits until is stepping and is ready for a command (paused and fully idle), and acquires a lock on that state.
|
||||
// or, if doLock is false, releases a lock on that state and optionally re-disables stepping.
|
||||
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
|
||||
// intended (but not required) to be called from another thread,
|
||||
// e.g. when the GUI thread wants to make sure everything is paused so that it can create a savestate.
|
||||
// the return value is whether the CPU was unpaused before the call.
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true);
|
||||
// Get current CPU Thread State
|
||||
State GetState();
|
||||
|
||||
// Direct State Access (Raw pointer for embedding into JIT Blocks)
|
||||
// Strictly read-only. A lock is required to change the value.
|
||||
const volatile State* GetStatePtr();
|
||||
|
||||
// Locks the CPU Thread (waiting for it to become idle).
|
||||
// While this lock is held, the CPU Thread will not perform any action so it is safe to access
|
||||
// PowerPC::ppcState, CoreTiming, etc. without racing the CPU Thread.
|
||||
// Cannot be used recursively. Must be paired as PauseAndLock(true)/PauseAndLock(false).
|
||||
// Return value for do_lock == true is whether the state was CPU_RUNNING or not.
|
||||
// Return value for do_lock == false is whether the state was changed *to* CPU_RUNNING or not.
|
||||
// Cannot be used by System threads as it will deadlock. It is threadsafe otherwise.
|
||||
// "control_adjacent" causes PauseAndLock to behave like EnableStepping by modifying the
|
||||
// state of the Audio and FIFO subsystems as well.
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true, bool control_adjacent = false);
|
||||
|
||||
}
|
||||
|
|
|
@ -481,8 +481,8 @@ static void InsertDiscCallback(u64 userdata, s64 cyclesLate)
|
|||
|
||||
void ChangeDisc(const std::string& newFileName)
|
||||
{
|
||||
bool is_cpu = Core::IsCPUThread();
|
||||
bool was_unpaused = is_cpu ? false : Core::PauseAndLock(true);
|
||||
// WARNING: Can only run on Host Thread
|
||||
bool was_unpaused = Core::PauseAndLock(true);
|
||||
std::string* _FileName = new std::string(newFileName);
|
||||
CoreTiming::ScheduleEvent(0, s_eject_disc);
|
||||
CoreTiming::ScheduleEvent(500000000, s_insert_disc, (u64)_FileName);
|
||||
|
@ -498,8 +498,7 @@ void ChangeDisc(const std::string& newFileName)
|
|||
}
|
||||
Movie::g_discChange = fileName.substr(sizeofpath);
|
||||
}
|
||||
if (!is_cpu)
|
||||
Core::PauseAndLock(false, was_unpaused);
|
||||
Core::PauseAndLock(false, was_unpaused);
|
||||
}
|
||||
|
||||
void SetLidOpen(bool open)
|
||||
|
|
|
@ -102,7 +102,7 @@ bool VolumeIsValid();
|
|||
// Disc detection and swapping
|
||||
void SetDiscInside(bool _DiscInside);
|
||||
bool IsDiscInside();
|
||||
void ChangeDisc(const std::string& fileName);
|
||||
void ChangeDisc(const std::string& fileName); // [NOT THREADSAFE] Host only
|
||||
|
||||
// DVD Access Functions
|
||||
bool ChangePartition(u64 offset);
|
||||
|
|
|
@ -27,7 +27,12 @@ GCPadStatus CSIDevice_GCAdapter::GetPadStatus()
|
|||
GCPadStatus PadStatus;
|
||||
memset(&PadStatus, 0, sizeof(PadStatus));
|
||||
|
||||
GCAdapter::Input(ISIDevice::m_iDeviceNumber, &PadStatus);
|
||||
// For netplay, the local controllers are polled in GetNetPads(), and
|
||||
// the remote controllers receive their status there as well
|
||||
if (!NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
GCAdapter::Input(ISIDevice::m_iDeviceNumber, &PadStatus);
|
||||
}
|
||||
|
||||
HandleMoviePadStatus(&PadStatus);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/HW/SI_Device.h"
|
||||
|
@ -144,7 +145,12 @@ GCPadStatus CSIDevice_GCController::GetPadStatus()
|
|||
GCPadStatus PadStatus;
|
||||
memset(&PadStatus, 0, sizeof(PadStatus));
|
||||
|
||||
Pad::GetStatus(ISIDevice::m_iDeviceNumber, &PadStatus);
|
||||
// For netplay, the local controllers are polled in GetNetPads(), and
|
||||
// the remote controllers receive their status there as well
|
||||
if (!NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
Pad::GetStatus(ISIDevice::m_iDeviceNumber, &PadStatus);
|
||||
}
|
||||
|
||||
HandleMoviePadStatus(&PadStatus);
|
||||
return PadStatus;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <mbedtls/config.h>
|
||||
#include <mbedtls/md.h>
|
||||
|
@ -21,6 +22,7 @@
|
|||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/State.h"
|
||||
#include "Core/DSP/DSPCore.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/DVDInterface.h"
|
||||
#include "Core/HW/EXI_Device.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
|
@ -84,11 +86,14 @@ static u8 s_language = 10; //Set to unknown until language is known
|
|||
static bool s_bRecordingFromSaveState = false;
|
||||
static bool s_bPolled = false;
|
||||
|
||||
// s_InputDisplay is used by both CPU and GPU (is mutable).
|
||||
static std::mutex s_input_display_lock;
|
||||
static std::string s_InputDisplay[8];
|
||||
|
||||
static GCManipFunction gcmfunc = nullptr;
|
||||
static WiiManipFunction wiimfunc = nullptr;
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
static void EnsureTmpInputSize(size_t bound)
|
||||
{
|
||||
if (tmpInputAllocated >= bound)
|
||||
|
@ -118,6 +123,7 @@ static bool IsMovieHeader(u8 magic[4])
|
|||
magic[3] == 0x1A;
|
||||
}
|
||||
|
||||
// NOTE: GPU Thread
|
||||
std::string GetInputDisplay()
|
||||
{
|
||||
if (!IsMovieActive())
|
||||
|
@ -132,14 +138,19 @@ std::string GetInputDisplay()
|
|||
}
|
||||
}
|
||||
|
||||
std::string inputDisplay = "";
|
||||
for (int i = 0; i < 8; ++i)
|
||||
if ((s_numPads & (1 << i)) != 0)
|
||||
inputDisplay.append(s_InputDisplay[i]);
|
||||
|
||||
return inputDisplay;
|
||||
std::string input_display;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_input_display_lock);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((s_numPads & (1 << i)) != 0)
|
||||
input_display += s_InputDisplay[i];
|
||||
}
|
||||
}
|
||||
return input_display;
|
||||
}
|
||||
|
||||
// NOTE: GPU Thread
|
||||
void FrameUpdate()
|
||||
{
|
||||
// TODO[comex]: This runs on the GPU thread, yet it messes with the CPU
|
||||
|
@ -155,8 +166,8 @@ void FrameUpdate()
|
|||
}
|
||||
if (s_bFrameStep)
|
||||
{
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
s_bFrameStep = false;
|
||||
CPU::Break();
|
||||
}
|
||||
|
||||
if (s_framesToSkip)
|
||||
|
@ -167,6 +178,7 @@ void FrameUpdate()
|
|||
|
||||
// called when game is booting up, even if no movie is active,
|
||||
// but potentially after BeginRecordingInput or PlayInput has been called.
|
||||
// NOTE: EmuThread
|
||||
void Init()
|
||||
{
|
||||
s_bPolled = false;
|
||||
|
@ -212,6 +224,7 @@ void Init()
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void InputUpdate()
|
||||
{
|
||||
g_currentInputCount++;
|
||||
|
@ -223,6 +236,7 @@ void InputUpdate()
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
void SetFrameSkipping(unsigned int framesToSkip)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(cs_frameSkip);
|
||||
|
@ -236,19 +250,21 @@ void SetFrameSkipping(unsigned int framesToSkip)
|
|||
Fifo::SetRendering(true);
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void SetPolledDevice()
|
||||
{
|
||||
s_bPolled = true;
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
void DoFrameStep()
|
||||
{
|
||||
if (Core::GetState() == Core::CORE_PAUSE)
|
||||
{
|
||||
// if already paused, frame advance for 1 frame
|
||||
Core::SetState(Core::CORE_RUN);
|
||||
Core::RequestRefreshInfo();
|
||||
s_bFrameStep = true;
|
||||
Core::RequestRefreshInfo();
|
||||
Core::SetState(Core::CORE_RUN);
|
||||
}
|
||||
else if (!s_bFrameStep)
|
||||
{
|
||||
|
@ -257,6 +273,7 @@ void DoFrameStep()
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
void SetReadOnly(bool bEnabled)
|
||||
{
|
||||
if (s_bReadOnly != bEnabled)
|
||||
|
@ -265,6 +282,7 @@ void SetReadOnly(bool bEnabled)
|
|||
s_bReadOnly = bEnabled;
|
||||
}
|
||||
|
||||
// NOTE: GPU Thread
|
||||
void FrameSkipping()
|
||||
{
|
||||
// Frameskipping will desync movie playback
|
||||
|
@ -398,6 +416,7 @@ bool IsNetPlayRecording()
|
|||
return s_bNetPlay;
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
void ChangePads(bool instantly)
|
||||
{
|
||||
if (!Core::IsRunning())
|
||||
|
@ -430,6 +449,7 @@ void ChangePads(bool instantly)
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host / Emu Threads
|
||||
void ChangeWiiPads(bool instantly)
|
||||
{
|
||||
int controllers = 0;
|
||||
|
@ -449,6 +469,7 @@ void ChangeWiiPads(bool instantly)
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
bool BeginRecordingInput(int controllers)
|
||||
{
|
||||
if (s_playMode != MODE_NONE || controllers == 0)
|
||||
|
@ -574,46 +595,51 @@ static std::string Analog1DToString(u8 v, const std::string& prefix, u8 range =
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
static void SetInputDisplayString(ControllerState padState, int controllerID)
|
||||
{
|
||||
s_InputDisplay[controllerID] = StringFromFormat("P%d:", controllerID + 1);
|
||||
std::string display_str = StringFromFormat("P%d:", controllerID + 1);
|
||||
|
||||
if (padState.A)
|
||||
s_InputDisplay[controllerID].append(" A");
|
||||
display_str += " A";
|
||||
if (padState.B)
|
||||
s_InputDisplay[controllerID].append(" B");
|
||||
display_str += " B";
|
||||
if (padState.X)
|
||||
s_InputDisplay[controllerID].append(" X");
|
||||
display_str += " X";
|
||||
if (padState.Y)
|
||||
s_InputDisplay[controllerID].append(" Y");
|
||||
display_str += " Y";
|
||||
if (padState.Z)
|
||||
s_InputDisplay[controllerID].append(" Z");
|
||||
display_str += " Z";
|
||||
if (padState.Start)
|
||||
s_InputDisplay[controllerID].append(" START");
|
||||
display_str += " START";
|
||||
|
||||
if (padState.DPadUp)
|
||||
s_InputDisplay[controllerID].append(" UP");
|
||||
display_str += " UP";
|
||||
if (padState.DPadDown)
|
||||
s_InputDisplay[controllerID].append(" DOWN");
|
||||
display_str += " DOWN";
|
||||
if (padState.DPadLeft)
|
||||
s_InputDisplay[controllerID].append(" LEFT");
|
||||
display_str += " LEFT";
|
||||
if (padState.DPadRight)
|
||||
s_InputDisplay[controllerID].append(" RIGHT");
|
||||
display_str += " RIGHT";
|
||||
if (padState.reset)
|
||||
s_InputDisplay[controllerID].append(" RESET");
|
||||
display_str += " RESET";
|
||||
|
||||
s_InputDisplay[controllerID].append(Analog1DToString(padState.TriggerL, " L"));
|
||||
s_InputDisplay[controllerID].append(Analog1DToString(padState.TriggerR, " R"));
|
||||
s_InputDisplay[controllerID].append(Analog2DToString(padState.AnalogStickX, padState.AnalogStickY, " ANA"));
|
||||
s_InputDisplay[controllerID].append(Analog2DToString(padState.CStickX, padState.CStickY, " C"));
|
||||
s_InputDisplay[controllerID].append("\n");
|
||||
display_str += Analog1DToString(padState.TriggerL, " L");
|
||||
display_str += Analog1DToString(padState.TriggerR, " R");
|
||||
display_str += Analog2DToString(padState.AnalogStickX, padState.AnalogStickY, " ANA");
|
||||
display_str += Analog2DToString(padState.CStickX, padState.CStickY, " C");
|
||||
display_str += '\n';
|
||||
|
||||
std::lock_guard<std::mutex> guard(s_input_display_lock);
|
||||
s_InputDisplay[controllerID] = std::move(display_str);
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
static void SetWiiInputDisplayString(int remoteID, u8* const data, const WiimoteEmu::ReportFeatures& rptf, int ext, const wiimote_key key)
|
||||
{
|
||||
int controllerID = remoteID + 4;
|
||||
|
||||
s_InputDisplay[controllerID] = StringFromFormat("R%d:", remoteID + 1);
|
||||
std::string display_str = StringFromFormat("R%d:", remoteID + 1);
|
||||
|
||||
u8* const coreData = rptf.core ? (data + rptf.core) : nullptr;
|
||||
u8* const accelData = rptf.accel ? (data + rptf.accel) : nullptr;
|
||||
|
@ -624,43 +650,43 @@ static void SetWiiInputDisplayString(int remoteID, u8* const data, const Wiimote
|
|||
{
|
||||
wm_buttons buttons = *(wm_buttons*)coreData;
|
||||
if(buttons.left)
|
||||
s_InputDisplay[controllerID].append(" LEFT");
|
||||
display_str += " LEFT";
|
||||
if(buttons.right)
|
||||
s_InputDisplay[controllerID].append(" RIGHT");
|
||||
display_str += " RIGHT";
|
||||
if(buttons.down)
|
||||
s_InputDisplay[controllerID].append(" DOWN");
|
||||
display_str += " DOWN";
|
||||
if(buttons.up)
|
||||
s_InputDisplay[controllerID].append(" UP");
|
||||
display_str += " UP";
|
||||
if(buttons.a)
|
||||
s_InputDisplay[controllerID].append(" A");
|
||||
display_str += " A";
|
||||
if(buttons.b)
|
||||
s_InputDisplay[controllerID].append(" B");
|
||||
display_str += " B";
|
||||
if(buttons.plus)
|
||||
s_InputDisplay[controllerID].append(" +");
|
||||
display_str += " +";
|
||||
if(buttons.minus)
|
||||
s_InputDisplay[controllerID].append(" -");
|
||||
display_str += " -";
|
||||
if(buttons.one)
|
||||
s_InputDisplay[controllerID].append(" 1");
|
||||
display_str += " 1";
|
||||
if(buttons.two)
|
||||
s_InputDisplay[controllerID].append(" 2");
|
||||
display_str += " 2";
|
||||
if(buttons.home)
|
||||
s_InputDisplay[controllerID].append(" HOME");
|
||||
display_str += " HOME";
|
||||
}
|
||||
|
||||
if (accelData)
|
||||
{
|
||||
wm_accel* dt = (wm_accel*)accelData;
|
||||
std::string accel = StringFromFormat(" ACC:%d,%d,%d",
|
||||
dt->x << 2 | ((wm_buttons*)coreData)->acc_x_lsb, dt->y << 2 | ((wm_buttons*)coreData)->acc_y_lsb << 1, dt->z << 2 | ((wm_buttons*)coreData)->acc_z_lsb << 1);
|
||||
s_InputDisplay[controllerID].append(accel);
|
||||
display_str += StringFromFormat(" ACC:%d,%d,%d",
|
||||
dt->x << 2 | ((wm_buttons*)coreData)->acc_x_lsb,
|
||||
dt->y << 2 | ((wm_buttons*)coreData)->acc_y_lsb << 1,
|
||||
dt->z << 2 | ((wm_buttons*)coreData)->acc_z_lsb << 1);
|
||||
}
|
||||
|
||||
if (irData)
|
||||
{
|
||||
u16 x = irData[0] | ((irData[2] >> 4 & 0x3) << 8);
|
||||
u16 y = irData[1] | ((irData[2] >> 6 & 0x3) << 8);
|
||||
std::string ir = StringFromFormat(" IR:%d,%d", x,y);
|
||||
s_InputDisplay[controllerID].append(ir);
|
||||
display_str += StringFromFormat(" IR:%d,%d", x, y);
|
||||
}
|
||||
|
||||
// Nunchuk
|
||||
|
@ -675,11 +701,11 @@ static void SetWiiInputDisplayString(int remoteID, u8* const data, const Wiimote
|
|||
(nunchuk.ax << 2) | nunchuk.bt.acc_x_lsb, (nunchuk.ay << 2) | nunchuk.bt.acc_y_lsb, (nunchuk.az << 2) | nunchuk.bt.acc_z_lsb);
|
||||
|
||||
if (nunchuk.bt.c)
|
||||
s_InputDisplay[controllerID].append(" C");
|
||||
display_str += " C";
|
||||
if (nunchuk.bt.z)
|
||||
s_InputDisplay[controllerID].append(" Z");
|
||||
s_InputDisplay[controllerID].append(accel);
|
||||
s_InputDisplay[controllerID].append(Analog2DToString(nunchuk.jx, nunchuk.jy, " ANA"));
|
||||
display_str += " Z";
|
||||
display_str += accel;
|
||||
display_str += Analog2DToString(nunchuk.jx, nunchuk.jy, " ANA");
|
||||
}
|
||||
|
||||
// Classic controller
|
||||
|
@ -691,41 +717,45 @@ static void SetWiiInputDisplayString(int remoteID, u8* const data, const Wiimote
|
|||
cc.bt.hex = cc.bt.hex ^ 0xFFFF;
|
||||
|
||||
if (cc.bt.regular_data.dpad_left)
|
||||
s_InputDisplay[controllerID].append(" LEFT");
|
||||
display_str += " LEFT";
|
||||
if (cc.bt.dpad_right)
|
||||
s_InputDisplay[controllerID].append(" RIGHT");
|
||||
display_str += " RIGHT";
|
||||
if (cc.bt.dpad_down)
|
||||
s_InputDisplay[controllerID].append(" DOWN");
|
||||
display_str += " DOWN";
|
||||
if (cc.bt.regular_data.dpad_up)
|
||||
s_InputDisplay[controllerID].append(" UP");
|
||||
display_str += " UP";
|
||||
if (cc.bt.a)
|
||||
s_InputDisplay[controllerID].append(" A");
|
||||
display_str += " A";
|
||||
if (cc.bt.b)
|
||||
s_InputDisplay[controllerID].append(" B");
|
||||
display_str += " B";
|
||||
if (cc.bt.x)
|
||||
s_InputDisplay[controllerID].append(" X");
|
||||
display_str += " X";
|
||||
if (cc.bt.y)
|
||||
s_InputDisplay[controllerID].append(" Y");
|
||||
display_str += " Y";
|
||||
if (cc.bt.zl)
|
||||
s_InputDisplay[controllerID].append(" ZL");
|
||||
display_str += " ZL";
|
||||
if (cc.bt.zr)
|
||||
s_InputDisplay[controllerID].append(" ZR");
|
||||
display_str += " ZR";
|
||||
if (cc.bt.plus)
|
||||
s_InputDisplay[controllerID].append(" +");
|
||||
display_str += " +";
|
||||
if (cc.bt.minus)
|
||||
s_InputDisplay[controllerID].append(" -");
|
||||
display_str += " -";
|
||||
if (cc.bt.home)
|
||||
s_InputDisplay[controllerID].append(" HOME");
|
||||
display_str += " HOME";
|
||||
|
||||
s_InputDisplay[controllerID].append(Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31));
|
||||
s_InputDisplay[controllerID].append(Analog1DToString(cc.rt, " R", 31));
|
||||
s_InputDisplay[controllerID].append(Analog2DToString(cc.regular_data.lx, cc.regular_data.ly, " ANA", 63));
|
||||
s_InputDisplay[controllerID].append(Analog2DToString(cc.rx1 | (cc.rx2 << 1) | (cc.rx3 << 3), cc.ry, " R-ANA", 31));
|
||||
display_str += Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31);
|
||||
display_str += Analog1DToString(cc.rt, " R", 31);
|
||||
display_str += Analog2DToString(cc.regular_data.lx, cc.regular_data.ly, " ANA", 63);
|
||||
display_str += Analog2DToString(cc.rx1 | (cc.rx2 << 1) | (cc.rx3 << 3), cc.ry, " R-ANA", 31);
|
||||
}
|
||||
|
||||
s_InputDisplay[controllerID].append("\n");
|
||||
display_str += '\n';
|
||||
|
||||
std::lock_guard<std::mutex> guard(s_input_display_lock);
|
||||
s_InputDisplay[controllerID] = std::move(display_str);
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void CheckPadStatus(GCPadStatus* PadStatus, int controllerID)
|
||||
{
|
||||
s_padState.A = ((PadStatus->button & PAD_BUTTON_A) != 0);
|
||||
|
@ -759,6 +789,7 @@ void CheckPadStatus(GCPadStatus* PadStatus, int controllerID)
|
|||
SetInputDisplayString(s_padState, controllerID);
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void RecordInput(GCPadStatus* PadStatus, int controllerID)
|
||||
{
|
||||
if (!IsRecordingInput() || !IsUsingPad(controllerID))
|
||||
|
@ -772,6 +803,7 @@ void RecordInput(GCPadStatus* PadStatus, int controllerID)
|
|||
s_totalBytes = s_currentByte;
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void CheckWiimoteStatus(int wiimote, u8 *data, const WiimoteEmu::ReportFeatures& rptf, int ext, const wiimote_key key)
|
||||
{
|
||||
SetWiiInputDisplayString(wiimote, data, rptf, ext, key);
|
||||
|
@ -793,6 +825,7 @@ void RecordWiimote(int wiimote, u8 *data, u8 size)
|
|||
s_totalBytes = s_currentByte;
|
||||
}
|
||||
|
||||
// NOTE: EmuThread / Host Thread
|
||||
void ReadHeader()
|
||||
{
|
||||
s_numPads = tmpHeader.numControllers;
|
||||
|
@ -831,6 +864,7 @@ void ReadHeader()
|
|||
s_DSPcoefHash = tmpHeader.DSPcoefHash;
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
bool PlayInput(const std::string& filename)
|
||||
{
|
||||
if (s_playMode != MODE_NONE)
|
||||
|
@ -901,6 +935,7 @@ void DoState(PointerWrap &p)
|
|||
// other variables (such as s_totalBytes and g_totalFrames) are set in LoadInput
|
||||
}
|
||||
|
||||
// NOTE: Host Thread
|
||||
void LoadInput(const std::string& filename)
|
||||
{
|
||||
File::IOFile t_record;
|
||||
|
@ -1038,6 +1073,7 @@ void LoadInput(const std::string& filename)
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
static void CheckInputEnd()
|
||||
{
|
||||
if (g_currentFrame > g_totalFrames || s_currentByte >= s_totalBytes || (CoreTiming::GetTicks() > s_totalTickCount && !IsRecordingInputFromSaveState()))
|
||||
|
@ -1046,6 +1082,7 @@ static void CheckInputEnd()
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void PlayController(GCPadStatus* PadStatus, int controllerID)
|
||||
{
|
||||
// Correct playback is entirely dependent on the emulator polling the controllers
|
||||
|
@ -1116,7 +1153,7 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
|
|||
{
|
||||
// This implementation assumes the disc change will only happen once. Trying to change more than that will cause
|
||||
// it to load the last disc every time. As far as i know though, there are no 3+ disc games, so this should be fine.
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
CPU::Break();
|
||||
bool found = false;
|
||||
std::string path;
|
||||
for (size_t i = 0; i < SConfig::GetInstance().m_ISOFolder.size(); ++i)
|
||||
|
@ -1130,8 +1167,16 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
|
|||
}
|
||||
if (found)
|
||||
{
|
||||
DVDInterface::ChangeDisc(path + '/' + g_discChange);
|
||||
Core::SetState(Core::CORE_RUN);
|
||||
path += '/' + g_discChange;
|
||||
|
||||
Core::QueueHostJob([=]
|
||||
{
|
||||
if (!Movie::IsPlayingInput())
|
||||
return;
|
||||
|
||||
DVDInterface::ChangeDisc(path);
|
||||
CPU::EnableStepping(false);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1146,6 +1191,7 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
|
|||
CheckInputEnd();
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
bool PlayWiimote(int wiimote, u8 *data, const WiimoteEmu::ReportFeatures& rptf, int ext, const wiimote_key key)
|
||||
{
|
||||
if (!IsPlayingInput() || !IsUsingWiimote(wiimote) || tmpInput == nullptr)
|
||||
|
@ -1188,6 +1234,7 @@ bool PlayWiimote(int wiimote, u8 *data, const WiimoteEmu::ReportFeatures& rptf,
|
|||
return true;
|
||||
}
|
||||
|
||||
// NOTE: Host / EmuThread / CPU Thread
|
||||
void EndPlayInput(bool cont)
|
||||
{
|
||||
if (cont)
|
||||
|
@ -1197,10 +1244,13 @@ void EndPlayInput(bool cont)
|
|||
}
|
||||
else if (s_playMode != MODE_NONE)
|
||||
{
|
||||
// We can be called by EmuThread during boot (CPU_POWERDOWN)
|
||||
bool was_running = Core::IsRunningAndStarted() && !CPU::IsStepping();
|
||||
if (was_running)
|
||||
CPU::Break();
|
||||
s_rerecords = 0;
|
||||
s_currentByte = 0;
|
||||
s_playMode = MODE_NONE;
|
||||
Core::UpdateWantDeterminism();
|
||||
Core::DisplayMessage("Movie End.", 2000);
|
||||
s_bRecordingFromSaveState = false;
|
||||
// we don't clear these things because otherwise we can't resume playback if we load a movie state later
|
||||
|
@ -1208,11 +1258,16 @@ void EndPlayInput(bool cont)
|
|||
//delete tmpInput;
|
||||
//tmpInput = nullptr;
|
||||
|
||||
if (SConfig::GetInstance().m_PauseMovie)
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
Core::QueueHostJob([=]
|
||||
{
|
||||
Core::UpdateWantDeterminism();
|
||||
if (was_running && !SConfig::GetInstance().m_PauseMovie)
|
||||
CPU::EnableStepping(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Save State + Host Thread
|
||||
void SaveRecording(const std::string& filename)
|
||||
{
|
||||
File::IOFile save_record(filename, "wb");
|
||||
|
@ -1291,17 +1346,20 @@ void SetWiiInputManip(WiiManipFunction func)
|
|||
wiimfunc = func;
|
||||
}
|
||||
|
||||
// NOTE: CPU Thread
|
||||
void CallGCInputManip(GCPadStatus* PadStatus, int controllerID)
|
||||
{
|
||||
if (gcmfunc)
|
||||
(*gcmfunc)(PadStatus, controllerID);
|
||||
}
|
||||
// NOTE: CPU Thread
|
||||
void CallWiiInputManip(u8* data, WiimoteEmu::ReportFeatures rptf, int controllerID, int ext, const wiimote_key key)
|
||||
{
|
||||
if (wiimfunc)
|
||||
(*wiimfunc)(data, rptf, controllerID, ext, key);
|
||||
}
|
||||
|
||||
// NOTE: GPU Thread
|
||||
void SetGraphicsConfig()
|
||||
{
|
||||
g_Config.bEFBAccessEnable = tmpHeader.bEFBAccessEnable;
|
||||
|
@ -1311,6 +1369,7 @@ void SetGraphicsConfig()
|
|||
g_Config.bUseRealXFB = tmpHeader.bUseRealXFB;
|
||||
}
|
||||
|
||||
// NOTE: EmuThread / Host Thread
|
||||
void GetSettings()
|
||||
{
|
||||
s_bSaveConfig = true;
|
||||
|
@ -1329,12 +1388,16 @@ void GetSettings()
|
|||
g_bClearSave = !File::Exists(SConfig::GetInstance().m_strMemoryCardA);
|
||||
s_memcards |= (SConfig::GetInstance().m_EXIDevice[0] == EXIDEVICE_MEMORYCARD) << 0;
|
||||
s_memcards |= (SConfig::GetInstance().m_EXIDevice[1] == EXIDEVICE_MEMORYCARD) << 1;
|
||||
|
||||
unsigned int tmp;
|
||||
for (size_t i = 0; i < scm_rev_git_str.size() / 2 ; ++i)
|
||||
memset(s_revision, 0, sizeof(s_revision));
|
||||
size_t revision_bytes_to_copy = std::min(scm_rev_git_str.size() / 2, ArraySize(s_revision));
|
||||
for (size_t i = 0; i < revision_bytes_to_copy; ++i)
|
||||
{
|
||||
sscanf(&scm_rev_git_str[2 * i], "%02x", &tmp);
|
||||
s_revision[i] = tmp;
|
||||
}
|
||||
|
||||
if (!s_bDSPHLE)
|
||||
{
|
||||
std::string irom_file = File::GetUserPath(D_GCUSER_IDX) + DSP_IROM;
|
||||
|
@ -1371,6 +1434,7 @@ void GetSettings()
|
|||
|
||||
static const mbedtls_md_info_t* s_md5_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
|
||||
|
||||
// NOTE: Entrypoint for own thread
|
||||
void CheckMD5()
|
||||
{
|
||||
for (int i = 0, n = 0; i < 16; ++i)
|
||||
|
@ -1392,6 +1456,7 @@ void CheckMD5()
|
|||
Core::DisplayMessage("Checksum of current game does not match the recorded game!", 3000);
|
||||
}
|
||||
|
||||
// NOTE: Entrypoint for own thread
|
||||
void GetMD5()
|
||||
{
|
||||
Core::DisplayMessage("Calculating checksum of game file...", 2000);
|
||||
|
@ -1400,6 +1465,7 @@ void GetMD5()
|
|||
Core::DisplayMessage("Finished calculating checksum.", 2000);
|
||||
}
|
||||
|
||||
// NOTE: EmuThread
|
||||
void Shutdown()
|
||||
{
|
||||
g_currentInputCount = g_totalInputCount = g_totalFrames = s_totalBytes = s_tickCountAtLastInput = 0;
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
#include "Core/HW/SI_DeviceGCController.h"
|
||||
#include "Core/HW/Sram.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h"
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
|
||||
static std::mutex crit_netplay_client;
|
||||
static NetPlayClient * netplay_client = nullptr;
|
||||
static std::array<int, 4> s_wiimote_sources_cache;
|
||||
NetSettings g_NetPlaySettings;
|
||||
|
||||
// called from ---GUI--- thread
|
||||
|
@ -724,6 +727,17 @@ bool NetPlayClient::StartGame(const std::string &path)
|
|||
|
||||
m_dialog->BootGame(path);
|
||||
|
||||
// Disable wiimotes on game start
|
||||
// TODO: remove this when re-implementing wiimote netplay
|
||||
if (SConfig::GetInstance().bWii)
|
||||
{
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
{
|
||||
s_wiimote_sources_cache[i] = g_wiimote_sources[i];
|
||||
WiimoteReal::ChangeWiimoteSource(i, WIIMOTE_SRC_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDevices();
|
||||
|
||||
return true;
|
||||
|
@ -739,35 +753,34 @@ bool NetPlayClient::ChangeGame(const std::string&)
|
|||
void NetPlayClient::UpdateDevices()
|
||||
{
|
||||
u8 local_pad = 0;
|
||||
// Add local pads first:
|
||||
// As stated in the comment in NetPlayClient::GetNetPads, the pads pertaining
|
||||
// to the local user are always locally mapped to the first gamecube ports,
|
||||
// so they should be added first.
|
||||
u8 pad = 0;
|
||||
|
||||
for (auto player_id : m_pad_map)
|
||||
{
|
||||
// Use local controller types for local controllers if they are compatible
|
||||
// Only GCController-like controllers are supported, GBA and similar
|
||||
// exotic devices are not supported on netplay.
|
||||
if (player_id == m_local_player->pid)
|
||||
{
|
||||
if (SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad]))
|
||||
{
|
||||
SerialInterface::AddDevice(SConfig::GetInstance().m_SIDevice[local_pad], local_pad);
|
||||
SerialInterface::AddDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad);
|
||||
}
|
||||
else
|
||||
{
|
||||
SerialInterface::AddDevice(SIDEVICE_GC_CONTROLLER, local_pad);
|
||||
SerialInterface::AddDevice(SIDEVICE_GC_CONTROLLER, pad);
|
||||
}
|
||||
local_pad++;
|
||||
}
|
||||
}
|
||||
for (auto player_id : m_pad_map)
|
||||
{
|
||||
if (player_id != m_local_player->pid)
|
||||
else if (player_id > 0)
|
||||
{
|
||||
// Only GCController-like controllers are supported, GBA and similar
|
||||
// exotic devices are not supported on netplay.
|
||||
SerialInterface::AddDevice(player_id > 0 ? SIDEVICE_GC_CONTROLLER : SIDEVICE_NONE, local_pad);
|
||||
local_pad++;
|
||||
SerialInterface::AddDevice(SIDEVICE_GC_CONTROLLER, pad);
|
||||
}
|
||||
else
|
||||
{
|
||||
SerialInterface::AddDevice(SIDEVICE_NONE, pad);
|
||||
}
|
||||
pad++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -850,39 +863,46 @@ bool NetPlayClient::GetNetPads(const u8 pad_nb, GCPadStatus* pad_status)
|
|||
// If you have a 4P game, then one of the GameCubes will have
|
||||
// a controller plugged into slot 1, and another in slot 2.
|
||||
//
|
||||
// The slot number is the "local" pad number, and what player
|
||||
// The slot number is the "local" pad number, and what player
|
||||
// it actually means is the "in-game" pad number.
|
||||
//
|
||||
// The interface here gives us the status of local pads, and
|
||||
// expects to get back "in-game" pad numbers back in response.
|
||||
// e.g. it asks "here's the input that slot 1 has, and by the
|
||||
// way, what's the state of P1?"
|
||||
//
|
||||
// We should add this split between "in-game" pads and "local"
|
||||
// pads higher up.
|
||||
|
||||
int in_game_num = LocalPadToInGamePad(pad_nb);
|
||||
|
||||
// If this in-game pad is one of ours, then update from the
|
||||
// information given.
|
||||
if (in_game_num < 4)
|
||||
// When the 1st in-game pad is polled, we assume the others will
|
||||
// will be polled as well. To reduce latency, we poll all local
|
||||
// controllers at once and then send the status to the other
|
||||
// clients.
|
||||
if (IsFirstInGamePad(pad_nb))
|
||||
{
|
||||
// adjust the buffer either up or down
|
||||
// inserting multiple padstates or dropping states
|
||||
while (m_pad_buffer[in_game_num].Size() <= m_target_buffer_size)
|
||||
const u8 num_local_pads = NumLocalPads();
|
||||
for (u8 local_pad = 0; local_pad < num_local_pads; local_pad++)
|
||||
{
|
||||
// add to buffer
|
||||
m_pad_buffer[in_game_num].Push(*pad_status);
|
||||
switch (SConfig::GetInstance().m_SIDevice[local_pad])
|
||||
{
|
||||
case SIDEVICE_WIIU_ADAPTER:
|
||||
GCAdapter::Input(local_pad, pad_status);
|
||||
break;
|
||||
case SIDEVICE_GC_CONTROLLER:
|
||||
default:
|
||||
Pad::GetStatus(local_pad, pad_status);
|
||||
break;
|
||||
}
|
||||
|
||||
// send
|
||||
SendPadState(in_game_num, *pad_status);
|
||||
u8 ingame_pad = LocalPadToInGamePad(local_pad);
|
||||
|
||||
// adjust the buffer either up or down
|
||||
// inserting multiple padstates or dropping states
|
||||
while (m_pad_buffer[ingame_pad].Size() <= m_target_buffer_size)
|
||||
{
|
||||
// add to buffer
|
||||
m_pad_buffer[ingame_pad].Push(*pad_status);
|
||||
|
||||
// send
|
||||
SendPadState(ingame_pad, *pad_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we need to swap out the local value with the values
|
||||
// retrieved from NetPlay. This could be the value we pushed
|
||||
// above if we're configured as P1 and the code is trying
|
||||
// to retrieve data for slot 1.
|
||||
// Now, we either use the data pushed earlier, or wait for the
|
||||
// other clients to send it to us
|
||||
while (!m_pad_buffer[pad_nb].Pop(*pad_status))
|
||||
{
|
||||
if (!m_is_running.load())
|
||||
|
@ -1018,6 +1038,17 @@ bool NetPlayClient::StopGame()
|
|||
// stop game
|
||||
m_dialog->StopGame();
|
||||
|
||||
// Restore wiimote settings on game stop
|
||||
// TODO: remove this when re-implementing wiimote netplay
|
||||
if (SConfig::GetInstance().bWii)
|
||||
{
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
{
|
||||
g_wiimote_sources[i] = s_wiimote_sources_cache[i];
|
||||
WiimoteReal::ChangeWiimoteSource(i, s_wiimote_sources_cache[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1043,6 +1074,20 @@ bool NetPlayClient::LocalPlayerHasControllerMapped() const
|
|||
std::any_of(m_wiimote_map.begin(), m_wiimote_map.end(), mapping_matches_player_id);
|
||||
}
|
||||
|
||||
bool NetPlayClient::IsFirstInGamePad(u8 ingame_pad) const
|
||||
{
|
||||
return std::none_of(m_pad_map.begin(), m_pad_map.begin() + ingame_pad, [](auto mapping) {
|
||||
return mapping > 0;
|
||||
});
|
||||
}
|
||||
|
||||
u8 NetPlayClient::NumLocalPads() const
|
||||
{
|
||||
return static_cast<u8>(std::count_if(m_pad_map.begin(), m_pad_map.end(), [this](auto mapping) {
|
||||
return mapping == m_local_player->pid;
|
||||
}));
|
||||
}
|
||||
|
||||
u8 NetPlayClient::InGamePadToLocalPad(u8 ingame_pad)
|
||||
{
|
||||
// not our pad
|
||||
|
|
|
@ -75,8 +75,11 @@ public:
|
|||
void OnConnectReady(ENetAddress addr) override;
|
||||
void OnConnectFailed(u8 reason) override;
|
||||
|
||||
bool IsFirstInGamePad(u8 ingame_pad) const;
|
||||
u8 NumLocalPads() const;
|
||||
|
||||
u8 InGamePadToLocalPad(u8 ingame_pad);
|
||||
u8 LocalPadToInGamePad(u8 localPad);
|
||||
u8 InGamePadToLocalPad(u8 localPad);
|
||||
|
||||
u8 LocalWiimoteToInGameWiimote(u8 local_pad);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/CachedInterpreter.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
@ -32,13 +33,10 @@ void CachedInterpreter::Shutdown()
|
|||
|
||||
void CachedInterpreter::Run()
|
||||
{
|
||||
while (!PowerPC::GetState())
|
||||
while (!CPU::GetState())
|
||||
{
|
||||
SingleStep();
|
||||
}
|
||||
|
||||
// Let the waiting thread know we are done leaving
|
||||
PowerPC::FinishStateMove();
|
||||
}
|
||||
|
||||
void CachedInterpreter::SingleStep()
|
||||
|
|
|
@ -200,7 +200,7 @@ int ShowSteps = 300;
|
|||
// FastRun - inspired by GCemu (to imitate the JIT so that they can be compared).
|
||||
void Interpreter::Run()
|
||||
{
|
||||
while (!PowerPC::GetState())
|
||||
while (!CPU::GetState())
|
||||
{
|
||||
//we have to check exceptions at branches apparently (or maybe just rfi?)
|
||||
if (SConfig::GetInstance().bEnableDebugging)
|
||||
|
@ -279,9 +279,6 @@ void Interpreter::Run()
|
|||
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
|
||||
// Let the waiting thread know we are done leaving
|
||||
PowerPC::FinishStateMove();
|
||||
}
|
||||
|
||||
void Interpreter::unknown_instruction(UGeckoInstruction _inst)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "Core/CoreTiming.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/GPFifo.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
|
@ -561,7 +562,7 @@ void Jit64::Jit(u32 em_address)
|
|||
// Comment out the following to disable breakpoints (speed-up)
|
||||
if (!Profiler::g_ProfileBlocks)
|
||||
{
|
||||
if (GetState() == CPU_STEPPING)
|
||||
if (CPU::GetState() == CPU::CPU_STEPPING)
|
||||
{
|
||||
blockSize = 1;
|
||||
|
||||
|
@ -800,7 +801,9 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
|
|||
js.firstFPInstructionFound = true;
|
||||
}
|
||||
|
||||
if (SConfig::GetInstance().bEnableDebugging && breakpoints.IsAddressBreakPoint(ops[i].address) && GetState() != CPU_STEPPING)
|
||||
if (SConfig::GetInstance().bEnableDebugging &&
|
||||
breakpoints.IsAddressBreakPoint(ops[i].address) &&
|
||||
CPU::GetState() != CPU::CPU_STEPPING)
|
||||
{
|
||||
// Turn off block linking if there are breakpoints so that the Step Over command does not link this block.
|
||||
jo.enableBlocklink = false;
|
||||
|
@ -812,7 +815,7 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
|
|||
ABI_PushRegistersAndAdjustStack({}, 0);
|
||||
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
|
||||
ABI_PopRegistersAndAdjustStack({}, 0);
|
||||
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
FixupBranch noBreakpoint = J_CC(CC_Z);
|
||||
|
||||
WriteExit(ops[i].address);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Common/x64Emitter.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/Jit64/Jit.h"
|
||||
|
@ -75,12 +76,12 @@ void Jit64AsmRoutineManager::Generate()
|
|||
|
||||
if (SConfig::GetInstance().bEnableDebugging)
|
||||
{
|
||||
TEST(32, M(PowerPC::GetStatePtr()), Imm32(PowerPC::CPU_STEPPING));
|
||||
TEST(32, M(CPU::GetStatePtr()), Imm32(CPU::CPU_STEPPING));
|
||||
FixupBranch notStepping = J_CC(CC_Z);
|
||||
ABI_PushRegistersAndAdjustStack({}, 0);
|
||||
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
|
||||
ABI_PopRegistersAndAdjustStack({}, 0);
|
||||
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
dbg_exit = J_CC(CC_NZ, true);
|
||||
SetJumpTarget(notStepping);
|
||||
}
|
||||
|
@ -208,7 +209,7 @@ void Jit64AsmRoutineManager::Generate()
|
|||
|
||||
// Check the state pointer to see if we are exiting
|
||||
// Gets checked on at the end of every slice
|
||||
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
J_CC(CC_Z, outerLoop);
|
||||
|
||||
//Landing pad for drec space
|
||||
|
@ -221,11 +222,6 @@ void Jit64AsmRoutineManager::Generate()
|
|||
POP(RSP);
|
||||
}
|
||||
|
||||
// Let the waiting thread know we are done leaving
|
||||
ABI_PushRegistersAndAdjustStack({}, 0);
|
||||
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::FinishStateMove));
|
||||
ABI_PopRegistersAndAdjustStack({}, 0);
|
||||
|
||||
ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8, 16);
|
||||
RET();
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "Common/x64Emitter.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/DSP.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
|
@ -120,7 +121,7 @@ void Jit64::lXXx(UGeckoInstruction inst)
|
|||
// TODO: We shouldn't use a debug read here. It should be possible to get
|
||||
// the following instructions out of the JIT state.
|
||||
if (SConfig::GetInstance().bSkipIdle &&
|
||||
PowerPC::GetState() != PowerPC::CPU_STEPPING &&
|
||||
CPU::GetState() != CPU::CPU_STEPPING &&
|
||||
inst.OPCD == 32 &&
|
||||
MergeAllowedNextInstructions(2) &&
|
||||
(inst.hex & 0xFFFF0000) == 0x800D0000 &&
|
||||
|
|
|
@ -35,6 +35,7 @@ The register allocation is linear scan allocation.
|
|||
#include "Common/x64ABI.h"
|
||||
#include "Common/x64Emitter.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
@ -2299,7 +2300,7 @@ static void DoWriteCode(IRBuilder* ibuild, JitIL* Jit, u32 exitAddress)
|
|||
|
||||
Jit->MOV(32, PPCSTATE(pc), Imm32(InstLoc));
|
||||
Jit->ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
|
||||
Jit->TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
Jit->TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
|
||||
FixupBranch noBreakpoint = Jit->J_CC(CC_Z);
|
||||
Jit->WriteExit(InstLoc);
|
||||
Jit->SetJumpTarget(noBreakpoint);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/Profiler.h"
|
||||
#include "Core/PowerPC/Jit64IL/JitIL.h"
|
||||
|
@ -478,7 +479,7 @@ void JitIL::Jit(u32 em_address)
|
|||
// Comment out the following to disable breakpoints (speed-up)
|
||||
if (!Profiler::g_ProfileBlocks)
|
||||
{
|
||||
if (GetState() == CPU_STEPPING)
|
||||
if (CPU::GetState() == CPU::CPU_STEPPING)
|
||||
{
|
||||
blockSize = 1;
|
||||
|
||||
|
@ -624,7 +625,9 @@ const u8* JitIL::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
|
|||
ibuild.EmitExtExceptionCheck(ibuild.EmitIntConst(ops[i].address));
|
||||
}
|
||||
|
||||
if (SConfig::GetInstance().bEnableDebugging && breakpoints.IsAddressBreakPoint(ops[i].address) && GetState() != CPU_STEPPING)
|
||||
if (SConfig::GetInstance().bEnableDebugging &&
|
||||
breakpoints.IsAddressBreakPoint(ops[i].address) &&
|
||||
CPU::GetState() != CPU::CPU_STEPPING)
|
||||
{
|
||||
// Turn off block linking if there are breakpoints so that the Step Over command does not link this block.
|
||||
jo.enableBlocklink = false;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/PerformanceCounter.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
@ -364,15 +365,9 @@ void JitArm64::Jit(u32)
|
|||
{
|
||||
ClearCache();
|
||||
}
|
||||
int block_num = blocks.AllocateBlock(PowerPC::ppcState.pc);
|
||||
JitBlock *b = blocks.GetBlock(block_num);
|
||||
const u8* BlockPtr = DoJit(PowerPC::ppcState.pc, &code_buffer, b);
|
||||
blocks.FinalizeBlock(block_num, jo.enableBlocklink, BlockPtr);
|
||||
}
|
||||
|
||||
const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBlock *b)
|
||||
{
|
||||
int blockSize = code_buf->GetSize();
|
||||
int blockSize = code_buffer.GetSize();
|
||||
u32 em_address = PowerPC::ppcState.pc;
|
||||
|
||||
if (SConfig::GetInstance().bEnableDebugging)
|
||||
{
|
||||
|
@ -380,6 +375,28 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
blockSize = 1;
|
||||
}
|
||||
|
||||
// Analyze the block, collect all instructions it is made of (including inlining,
|
||||
// if that is enabled), reorder instructions for optimal performance, and join joinable instructions.
|
||||
u32 nextPC = analyzer.Analyze(em_address, &code_block, &code_buffer, blockSize);
|
||||
|
||||
if (code_block.m_memory_exception)
|
||||
{
|
||||
// Address of instruction could not be translated
|
||||
NPC = nextPC;
|
||||
PowerPC::ppcState.Exceptions |= EXCEPTION_ISI;
|
||||
PowerPC::CheckExceptions();
|
||||
WARN_LOG(POWERPC, "ISI exception at 0x%08x", nextPC);
|
||||
return;
|
||||
}
|
||||
|
||||
int block_num = blocks.AllocateBlock(em_address);
|
||||
JitBlock *b = blocks.GetBlock(block_num);
|
||||
const u8* BlockPtr = DoJit(em_address, &code_buffer, b, nextPC);
|
||||
blocks.FinalizeBlock(block_num, jo.enableBlocklink, BlockPtr);
|
||||
}
|
||||
|
||||
const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBlock *b, u32 nextPC)
|
||||
{
|
||||
if (em_address == 0)
|
||||
{
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
|
@ -395,11 +412,6 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
js.skipInstructions = 0;
|
||||
js.curBlock = b;
|
||||
|
||||
u32 nextPC = em_address;
|
||||
// Analyze the block, collect all instructions it is made of (including inlining,
|
||||
// if that is enabled), reorder instructions for optimal performance, and join joinable instructions.
|
||||
nextPC = analyzer.Analyze(em_address, &code_block, code_buf, blockSize);
|
||||
|
||||
PPCAnalyst::CodeOp *ops = code_buf->codebuffer;
|
||||
|
||||
const u8 *start = GetCodePtr();
|
||||
|
@ -467,6 +479,7 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
js.compilerPC = ops[i].address;
|
||||
js.op = &ops[i];
|
||||
js.instructionNumber = i;
|
||||
js.instructionsLeft = (code_block.m_num_instructions - 1) - i;
|
||||
const GekkoOPInfo *opinfo = ops[i].opinfo;
|
||||
js.downcountAmount += opinfo->numCycles;
|
||||
|
||||
|
@ -578,12 +591,6 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
js.firstFPInstructionFound = true;
|
||||
}
|
||||
|
||||
if (jo.memcheck && (opinfo->flags & FL_USE_FPU))
|
||||
{
|
||||
// Don't do this yet
|
||||
BRK(0x7777);
|
||||
}
|
||||
|
||||
JitArm64Tables::CompileInstruction(ops[i]);
|
||||
|
||||
// If we have a register that will never be used again, flush it.
|
||||
|
@ -592,8 +599,22 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
|
||||
if (jo.memcheck && (opinfo->flags & FL_LOADSTORE))
|
||||
{
|
||||
// Don't do this yet
|
||||
BRK(0x666);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
LDR(INDEX_UNSIGNED, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
FixupBranch noException = TBZ(WA, IntLog2(EXCEPTION_DSI));
|
||||
|
||||
FixupBranch handleException = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(handleException);
|
||||
|
||||
gpr.Flush(FLUSH_MAINTAIN_STATE);
|
||||
fpr.Flush(FLUSH_MAINTAIN_STATE);
|
||||
|
||||
WriteExceptionExit(js.compilerPC);
|
||||
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(noException);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -601,12 +622,10 @@ const u8* JitArm64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitB
|
|||
js.skipInstructions = 0;
|
||||
}
|
||||
|
||||
if (code_block.m_memory_exception)
|
||||
BRK(0x500);
|
||||
|
||||
if (code_block.m_broken)
|
||||
{
|
||||
printf("Broken Block going to 0x%08x\n", nextPC);
|
||||
gpr.Flush(FLUSH_ALL);
|
||||
fpr.Flush(FLUSH_ALL);
|
||||
WriteExit(nextPC);
|
||||
}
|
||||
|
||||
|
|
|
@ -221,7 +221,7 @@ private:
|
|||
void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update);
|
||||
void SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s32 offset);
|
||||
|
||||
const u8* DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBlock *b);
|
||||
const u8* DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBlock *b, u32 nextPC);
|
||||
|
||||
void DoDownCount();
|
||||
void Cleanup();
|
||||
|
|
|
@ -375,6 +375,7 @@ void JitArm64::lXX(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreOff);
|
||||
FALLBACK_IF(jo.memcheck);
|
||||
|
||||
u32 a = inst.RA, b = inst.RB, d = inst.RD;
|
||||
s32 offset = inst.SIMM_16;
|
||||
|
@ -446,11 +447,11 @@ void JitArm64::lXX(UGeckoInstruction inst)
|
|||
|
||||
// LWZ idle skipping
|
||||
if (SConfig::GetInstance().bSkipIdle &&
|
||||
inst.OPCD == 32 &&
|
||||
(inst.hex & 0xFFFF0000) == 0x800D0000 &&
|
||||
(PowerPC::HostRead_U32(js.compilerPC + 4) == 0x28000000 ||
|
||||
(SConfig::GetInstance().bWii && PowerPC::HostRead_U32(js.compilerPC + 4) == 0x2C000000)) &&
|
||||
PowerPC::HostRead_U32(js.compilerPC + 8) == 0x4182fff8)
|
||||
inst.OPCD == 32 && MergeAllowedNextInstructions(2) &&
|
||||
(inst.hex & 0xFFFF0000) == 0x800D0000 && // lwz r0, XXXX(r13)
|
||||
(js.op[1].inst.hex == 0x28000000 ||
|
||||
(SConfig::GetInstance().bWii && js.op[1].inst.hex == 0x2C000000)) && // cmpXwi r0,0
|
||||
js.op[2].inst.hex == 0x4182fff8) // beq -8
|
||||
{
|
||||
// if it's still 0, we can wait until the next event
|
||||
FixupBranch noIdle = CBNZ(gpr.R(d));
|
||||
|
@ -480,6 +481,7 @@ void JitArm64::stX(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreOff);
|
||||
FALLBACK_IF(jo.memcheck);
|
||||
|
||||
u32 a = inst.RA, b = inst.RB, s = inst.RS;
|
||||
s32 offset = inst.SIMM_16;
|
||||
|
@ -557,7 +559,7 @@ void JitArm64::lmw(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreOff);
|
||||
FALLBACK_IF(!jo.fastmem);
|
||||
FALLBACK_IF(!jo.fastmem || jo.memcheck);
|
||||
|
||||
u32 a = inst.RA;
|
||||
|
||||
|
@ -643,7 +645,7 @@ void JitArm64::stmw(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreOff);
|
||||
FALLBACK_IF(!jo.fastmem);
|
||||
FALLBACK_IF(!jo.fastmem || jo.memcheck);
|
||||
|
||||
u32 a = inst.RA;
|
||||
|
||||
|
@ -803,6 +805,7 @@ void JitArm64::dcbz(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreOff);
|
||||
FALLBACK_IF(jo.memcheck);
|
||||
|
||||
int a = inst.RA, b = inst.RB;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ void JitArm64::lfXX(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreFloatingOff);
|
||||
FALLBACK_IF(jo.memcheck);
|
||||
|
||||
u32 a = inst.RA, b = inst.RB;
|
||||
|
||||
|
@ -210,6 +211,7 @@ void JitArm64::stfXX(UGeckoInstruction inst)
|
|||
{
|
||||
INSTRUCTION_START
|
||||
JITDISABLE(bJITLoadStoreFloatingOff);
|
||||
FALLBACK_IF(jo.memcheck);
|
||||
|
||||
u32 a = inst.RA, b = inst.RB;
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ void JitArm64::mtmsr(UGeckoInstruction inst)
|
|||
gpr.Flush(FlushMode::FLUSH_ALL);
|
||||
fpr.Flush(FlushMode::FLUSH_ALL);
|
||||
|
||||
WriteExit(js.compilerPC + 4);
|
||||
WriteExceptionExit(js.compilerPC + 4, true);
|
||||
}
|
||||
|
||||
void JitArm64::mfmsr(UGeckoInstruction inst)
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/JitRegister.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/JitArm64/Jit.h"
|
||||
|
@ -46,12 +48,36 @@ void JitArm64::GenerateAsm()
|
|||
|
||||
dispatcherNoCheck = GetCodePtr();
|
||||
|
||||
// This block of code gets the address of the compiled block of code
|
||||
// It runs though to the compiling portion if it isn't found
|
||||
BFM(DISPATCHER_PC, WSP, 3, 2); // Wipe the top 3 bits. Same as PC & JIT_ICACHE_MASK
|
||||
FixupBranch exram, vmem, not_exram, not_vmem;
|
||||
ARM64Reg pc_masked = W25;
|
||||
ARM64Reg cache_base = X27;
|
||||
|
||||
MOVI2R(X27, (u64)jit->GetBlockCache()->iCache.data());
|
||||
LDR(W27, X27, EncodeRegTo64(DISPATCHER_PC));
|
||||
// VMEM
|
||||
not_vmem = TBZ(DISPATCHER_PC, IntLog2(JIT_ICACHE_VMEM_BIT));
|
||||
ANDI2R(pc_masked, DISPATCHER_PC, JIT_ICACHE_MASK);
|
||||
MOVI2R(cache_base, (u64)jit->GetBlockCache()->iCacheVMEM.data());
|
||||
vmem = B();
|
||||
SetJumpTarget(not_vmem);
|
||||
|
||||
if (SConfig::GetInstance().bWii)
|
||||
{
|
||||
// Wii EX-RAM
|
||||
not_exram = TBZ(DISPATCHER_PC, IntLog2(JIT_ICACHE_EXRAM_BIT));
|
||||
ANDI2R(pc_masked, DISPATCHER_PC, JIT_ICACHEEX_MASK);
|
||||
MOVI2R(cache_base, (u64)jit->GetBlockCache()->iCacheEx.data());
|
||||
exram = B();
|
||||
SetJumpTarget(not_exram);
|
||||
}
|
||||
|
||||
// Common memory
|
||||
ANDI2R(pc_masked, DISPATCHER_PC, JIT_ICACHE_MASK);
|
||||
MOVI2R(cache_base, (u64)jit->GetBlockCache()->iCache.data());
|
||||
|
||||
SetJumpTarget(vmem);
|
||||
if (SConfig::GetInstance().bWii)
|
||||
SetJumpTarget(exram);
|
||||
|
||||
LDR(W27, cache_base, EncodeRegTo64(pc_masked));
|
||||
|
||||
FixupBranch JitBlock = TBNZ(W27, 7); // Test the 7th bit
|
||||
// Success, it is our Jitblock.
|
||||
|
@ -64,10 +90,11 @@ void JitArm64::GenerateAsm()
|
|||
SetJumpTarget(JitBlock);
|
||||
|
||||
STR(INDEX_UNSIGNED, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
|
||||
|
||||
MOVI2R(X30, (u64)&::Jit);
|
||||
BLR(X30);
|
||||
|
||||
LDR(INDEX_UNSIGNED, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
|
||||
|
||||
B(dispatcherNoCheck);
|
||||
|
||||
SetJumpTarget(bail);
|
||||
|
@ -84,7 +111,7 @@ void JitArm64::GenerateAsm()
|
|||
|
||||
// Check the state pointer to see if we are exiting
|
||||
// Gets checked on at the end of every slice
|
||||
MOVI2R(X0, (u64)PowerPC::GetStatePtr());
|
||||
MOVI2R(X0, (u64)CPU::GetStatePtr());
|
||||
LDR(INDEX_UNSIGNED, W0, X0, 0);
|
||||
|
||||
CMP(W0, 0);
|
||||
|
@ -95,10 +122,6 @@ void JitArm64::GenerateAsm()
|
|||
SetJumpTarget(Exit);
|
||||
STR(INDEX_UNSIGNED, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
|
||||
|
||||
// Let the waiting thread know we are done leaving
|
||||
MOVI2R(X0, (u64)&PowerPC::FinishStateMove);
|
||||
BLR(X0);
|
||||
|
||||
ABI_PopRegisters(regs_to_save);
|
||||
RET(X30);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
|
@ -67,7 +68,7 @@ void LogGeneratedX86(int size, PPCAnalyst::CodeBuffer *code_buffer, const u8 *no
|
|||
|
||||
bool JitBase::MergeAllowedNextInstructions(int count)
|
||||
{
|
||||
if (PowerPC::GetState() == PowerPC::CPU_STEPPING || js.instructionsLeft < count)
|
||||
if (CPU::GetState() == CPU::CPU_STEPPING || js.instructionsLeft < count)
|
||||
return false;
|
||||
// Be careful: a breakpoint kills flags in between instructions
|
||||
for (int i = 1; i <= count; i++)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/JitILCommon/JitILBase.h"
|
||||
|
||||
|
@ -57,7 +58,7 @@ void JitILBase::lXz(UGeckoInstruction inst)
|
|||
// or higher in PPCAnalyst
|
||||
// TODO: We shouldn't use debug reads here.
|
||||
if (SConfig::GetInstance().bSkipIdle &&
|
||||
PowerPC::GetState() != PowerPC::CPU_STEPPING &&
|
||||
CPU::GetState() != CPU::CPU_STEPPING &&
|
||||
inst.OPCD == 32 && // Lwx
|
||||
(inst.hex & 0xFFFF0000) == 0x800D0000 &&
|
||||
(PowerPC::HostRead_U32(js.compilerPC + 4) == 0x28000000 ||
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/FPURoundMode.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/PowerPC/CPUCoreBase.h"
|
||||
|
@ -21,18 +21,16 @@
|
|||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
|
||||
|
||||
CPUCoreBase *cpu_core_base;
|
||||
|
||||
namespace PowerPC
|
||||
{
|
||||
|
||||
// STATE_TO_SAVE
|
||||
PowerPCState ppcState;
|
||||
static volatile CPUState state = CPU_POWERDOWN;
|
||||
|
||||
Interpreter * const interpreter = Interpreter::getInstance();
|
||||
static CoreMode mode;
|
||||
static Common::Event s_state_change;
|
||||
static CPUCoreBase* s_cpu_core_base = nullptr;
|
||||
static bool s_cpu_core_base_is_injected = false;
|
||||
Interpreter* const s_interpreter = Interpreter::getInstance();
|
||||
static CoreMode s_mode = MODE_INTERPRETER;
|
||||
|
||||
Watches watches;
|
||||
BreakPoints breakpoints;
|
||||
|
@ -114,6 +112,8 @@ static void ResetRegisters()
|
|||
|
||||
void Init(int cpu_core)
|
||||
{
|
||||
// NOTE: This function runs on EmuThread, not the CPU Thread.
|
||||
// Changing the rounding mode has a limited effect.
|
||||
FPURoundMode::SetPrecisionMode(FPURoundMode::PREC_53);
|
||||
|
||||
memset(ppcState.sr, 0, sizeof(ppcState.sr));
|
||||
|
@ -139,33 +139,32 @@ void Init(int cpu_core)
|
|||
|
||||
// We initialize the interpreter because
|
||||
// it is used on boot and code window independently.
|
||||
interpreter->Init();
|
||||
s_interpreter->Init();
|
||||
|
||||
switch (cpu_core)
|
||||
{
|
||||
case PowerPC::CORE_INTERPRETER:
|
||||
cpu_core_base = interpreter;
|
||||
s_cpu_core_base = s_interpreter;
|
||||
break;
|
||||
|
||||
default:
|
||||
cpu_core_base = JitInterface::InitJitCore(cpu_core);
|
||||
if (!cpu_core_base) // Handle Situations where JIT core isn't available
|
||||
s_cpu_core_base = JitInterface::InitJitCore(cpu_core);
|
||||
if (!s_cpu_core_base) // Handle Situations where JIT core isn't available
|
||||
{
|
||||
WARN_LOG(POWERPC, "Jit core %d not available. Defaulting to interpreter.", cpu_core);
|
||||
cpu_core_base = interpreter;
|
||||
s_cpu_core_base = s_interpreter;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpu_core_base != interpreter)
|
||||
if (s_cpu_core_base != s_interpreter)
|
||||
{
|
||||
mode = MODE_JIT;
|
||||
s_mode = MODE_JIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = MODE_INTERPRETER;
|
||||
s_mode = MODE_INTERPRETER;
|
||||
}
|
||||
state = CPU_STEPPING;
|
||||
|
||||
ppcState.iCache.Init();
|
||||
|
||||
|
@ -175,94 +174,87 @@ void Init(int cpu_core)
|
|||
|
||||
void Shutdown()
|
||||
{
|
||||
InjectExternalCPUCore(nullptr);
|
||||
JitInterface::Shutdown();
|
||||
interpreter->Shutdown();
|
||||
cpu_core_base = nullptr;
|
||||
state = CPU_POWERDOWN;
|
||||
s_interpreter->Shutdown();
|
||||
s_cpu_core_base = nullptr;
|
||||
}
|
||||
|
||||
CoreMode GetMode()
|
||||
{
|
||||
return mode;
|
||||
return !s_cpu_core_base_is_injected ? s_mode : MODE_INTERPRETER;
|
||||
}
|
||||
|
||||
void SetMode(CoreMode new_mode)
|
||||
static void ApplyMode()
|
||||
{
|
||||
if (new_mode == mode)
|
||||
return; // We don't need to do anything.
|
||||
|
||||
mode = new_mode;
|
||||
|
||||
switch (mode)
|
||||
switch (s_mode)
|
||||
{
|
||||
case MODE_INTERPRETER: // Switching from JIT to interpreter
|
||||
cpu_core_base = interpreter;
|
||||
s_cpu_core_base = s_interpreter;
|
||||
break;
|
||||
|
||||
case MODE_JIT: // Switching from interpreter to JIT.
|
||||
// Don't really need to do much. It'll work, the cache will refill itself.
|
||||
cpu_core_base = JitInterface::GetCore();
|
||||
if (!cpu_core_base) // Has a chance to not get a working JIT core if one isn't active on host
|
||||
cpu_core_base = interpreter;
|
||||
s_cpu_core_base = JitInterface::GetCore();
|
||||
if (!s_cpu_core_base) // Has a chance to not get a working JIT core if one isn't active on host
|
||||
s_cpu_core_base = s_interpreter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetMode(CoreMode new_mode)
|
||||
{
|
||||
if (new_mode == s_mode)
|
||||
return; // We don't need to do anything.
|
||||
|
||||
s_mode = new_mode;
|
||||
|
||||
// If we're using an external CPU core implementation then don't do anything.
|
||||
if (s_cpu_core_base_is_injected)
|
||||
return;
|
||||
|
||||
ApplyMode();
|
||||
}
|
||||
|
||||
const char* GetCPUName()
|
||||
{
|
||||
return s_cpu_core_base->GetName();
|
||||
}
|
||||
|
||||
void InjectExternalCPUCore(CPUCoreBase* new_cpu)
|
||||
{
|
||||
// Previously injected.
|
||||
if (s_cpu_core_base_is_injected)
|
||||
s_cpu_core_base->Shutdown();
|
||||
|
||||
// nullptr means just remove
|
||||
if (!new_cpu)
|
||||
{
|
||||
if (s_cpu_core_base_is_injected)
|
||||
{
|
||||
s_cpu_core_base_is_injected = false;
|
||||
ApplyMode();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
new_cpu->Init();
|
||||
s_cpu_core_base = new_cpu;
|
||||
s_cpu_core_base_is_injected = true;
|
||||
}
|
||||
|
||||
void SingleStep()
|
||||
{
|
||||
cpu_core_base->SingleStep();
|
||||
s_cpu_core_base->SingleStep();
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
state = CPU_RUNNING;
|
||||
cpu_core_base->Run();
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
CPUState GetState()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
const volatile CPUState *GetStatePtr()
|
||||
{
|
||||
return &state;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
state = CPU_RUNNING;
|
||||
s_cpu_core_base->Run();
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
void Pause()
|
||||
{
|
||||
volatile CPUState old_state = state;
|
||||
state = CPU_STEPPING;
|
||||
|
||||
// Wait for the CPU core to leave
|
||||
if (old_state == CPU_RUNNING)
|
||||
s_state_change.WaitFor(std::chrono::seconds(1));
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
volatile CPUState old_state = state;
|
||||
state = CPU_POWERDOWN;
|
||||
|
||||
// Wait for the CPU core to leave
|
||||
if (old_state == CPU_RUNNING)
|
||||
s_state_change.WaitFor(std::chrono::seconds(1));
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
void FinishStateMove()
|
||||
{
|
||||
s_state_change.Set();
|
||||
}
|
||||
|
||||
void UpdatePerformanceMonitor(u32 cycles, u32 num_load_stores, u32 num_fp_inst)
|
||||
{
|
||||
switch (MMCR0.PMC1SELECT)
|
||||
|
@ -521,7 +513,7 @@ void CheckBreakPoints()
|
|||
{
|
||||
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
|
||||
{
|
||||
PowerPC::Pause();
|
||||
CPU::Break();
|
||||
if (PowerPC::breakpoints.IsTempBreakPoint(PC))
|
||||
PowerPC::breakpoints.Remove(PC);
|
||||
}
|
||||
|
|
|
@ -11,14 +11,12 @@
|
|||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/Debugger/PPCDebugInterface.h"
|
||||
#include "Core/PowerPC/CPUCoreBase.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/PPCCache.h"
|
||||
|
||||
class CPUCoreBase;
|
||||
class PointerWrap;
|
||||
|
||||
extern CPUCoreBase *cpu_core_base;
|
||||
|
||||
namespace PowerPC
|
||||
{
|
||||
|
||||
|
@ -129,13 +127,6 @@ struct PowerPCState
|
|||
static_assert(offsetof(PowerPC::PowerPCState, above_fits_in_first_0x100) <= 0x100, "top of PowerPCState too big");
|
||||
#endif
|
||||
|
||||
enum CPUState
|
||||
{
|
||||
CPU_RUNNING = 0,
|
||||
CPU_STEPPING = 2,
|
||||
CPU_POWERDOWN = 3,
|
||||
};
|
||||
|
||||
extern PowerPCState ppcState;
|
||||
|
||||
extern Watches watches;
|
||||
|
@ -148,19 +139,26 @@ void Shutdown();
|
|||
void DoState(PointerWrap &p);
|
||||
|
||||
CoreMode GetMode();
|
||||
// [NOT THREADSAFE] CPU Thread or CPU::PauseAndLock or CORE_UNINITIALIZED
|
||||
void SetMode(CoreMode _coreType);
|
||||
const char* GetCPUName();
|
||||
|
||||
// Set the current CPU Core to the given implementation until removed.
|
||||
// Remove the current injected CPU Core by passing nullptr.
|
||||
// While an external CPUCoreBase is injected, GetMode() will return MODE_INTERPRETER.
|
||||
// Init() will be called when added and Shutdown() when removed.
|
||||
// [Threadsafety: Same as SetMode(), except it cannot be called from inside the CPU
|
||||
// run loop on the CPU Thread - it doesn't make sense for a CPU to remove itself
|
||||
// while it is CPU_RUNNING]
|
||||
void InjectExternalCPUCore(CPUCoreBase* core);
|
||||
|
||||
// Stepping requires the CPU Execution lock (CPU::PauseAndLock or CPU Thread)
|
||||
// It's not threadsafe otherwise.
|
||||
void SingleStep();
|
||||
void CheckExceptions();
|
||||
void CheckExternalExceptions();
|
||||
void CheckBreakPoints();
|
||||
void RunLoop();
|
||||
void Start();
|
||||
void Pause();
|
||||
void Stop();
|
||||
void FinishStateMove();
|
||||
CPUState GetState();
|
||||
const volatile CPUState *GetStatePtr(); // this oddity is here instead of an extern declaration to easily be able to find all direct accesses throughout the code.
|
||||
|
||||
u32 CompactCR();
|
||||
void ExpandCR(u32 cr);
|
||||
|
|
|
@ -317,6 +317,7 @@ void CCodeWindow::StepOut()
|
|||
{
|
||||
if (CPU::IsStepping())
|
||||
{
|
||||
CPU::PauseAndLock(true, false);
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
|
||||
// Keep stepping until the next blr or timeout after one second
|
||||
|
@ -347,6 +348,7 @@ void CCodeWindow::StepOut()
|
|||
|
||||
PowerPC::SingleStep();
|
||||
PowerPC::SetMode(oldMode);
|
||||
CPU::PauseAndLock(false, false);
|
||||
|
||||
JumpToAddress(PC);
|
||||
Update();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <wx/menu.h>
|
||||
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DolphinWX/Frame.h"
|
||||
|
@ -87,7 +88,7 @@ static wxString GetValueByRowCol(int row, int col)
|
|||
}
|
||||
else if (row <= (int)PowerPC::watches.GetWatches().size())
|
||||
{
|
||||
if (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
|
@ -198,7 +199,7 @@ wxGridCellAttr* CWatchTable::GetAttr(int row, int col, wxGridCellAttr::wxAttrKin
|
|||
|
||||
attr->SetTextColour(red ? *wxRED : *wxBLACK);
|
||||
|
||||
if (row > (int)(PowerPC::watches.GetWatches().size() + 1) || (PowerPC::GetState() == PowerPC::CPU_POWERDOWN))
|
||||
if (row > (int)(PowerPC::watches.GetWatches().size() + 1) || !Core::IsRunning())
|
||||
{
|
||||
attr->SetReadOnly(true);
|
||||
attr->SetBackgroundColour(*wxLIGHT_GREY);
|
||||
|
@ -224,7 +225,7 @@ CWatchView::CWatchView(wxWindow* parent, wxWindowID id)
|
|||
|
||||
void CWatchView::Update()
|
||||
{
|
||||
if (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
m_watch_table->UpdateWatch();
|
||||
ForceRefresh();
|
||||
|
|
|
@ -407,7 +407,7 @@ void CGameListCtrl::UpdateItemAtColumn(long _Index, int column)
|
|||
break;
|
||||
case COLUMN_FILENAME:
|
||||
SetItem(_Index, COLUMN_FILENAME,
|
||||
wxFileNameFromPath(rISOFile.GetFileName()), -1);
|
||||
wxFileNameFromPath(StrToWxStr(rISOFile.GetFileName())), -1);
|
||||
break;
|
||||
case COLUMN_EMULATION_STATE:
|
||||
SetItemColumnImage(_Index, COLUMN_EMULATION_STATE,
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "DolphinWX/WxUtils.h"
|
||||
#include "DolphinWX/Debugger/CodeWindow.h"
|
||||
#include "DolphinWX/Debugger/JitWindow.h"
|
||||
#include "DolphinWX/NetPlay/NetWindow.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
|
@ -113,6 +114,7 @@ bool DolphinApp::OnInit()
|
|||
wxLog::SetLogLevel(0);
|
||||
Bind(wxEVT_QUERY_END_SESSION, &DolphinApp::OnEndSession, this);
|
||||
Bind(wxEVT_END_SESSION, &DolphinApp::OnEndSession, this);
|
||||
Bind(wxEVT_IDLE, &DolphinApp::OnIdle, this);
|
||||
|
||||
// Register message box and translation handlers
|
||||
RegisterMsgAlertHandler(&wxMsgAlert);
|
||||
|
@ -371,6 +373,12 @@ void DolphinApp::OnFatalException()
|
|||
WiimoteReal::Shutdown();
|
||||
}
|
||||
|
||||
void DolphinApp::OnIdle(wxIdleEvent& ev)
|
||||
{
|
||||
ev.Skip();
|
||||
Core::HostDispatchJobs();
|
||||
}
|
||||
|
||||
// ------------
|
||||
// Talk to GUI
|
||||
|
||||
|
@ -378,10 +386,21 @@ bool wxMsgAlert(const char* caption, const char* text, bool yes_no, int /*Style*
|
|||
{
|
||||
#ifdef __WXGTK__
|
||||
if (wxIsMainThread())
|
||||
{
|
||||
#endif
|
||||
return wxYES == wxMessageBox(StrToWxStr(text), StrToWxStr(caption),
|
||||
(yes_no) ? wxYES_NO : wxOK, wxWindow::FindFocus());
|
||||
NetPlayDialog*& npd = NetPlayDialog::GetInstance();
|
||||
if (npd == nullptr)
|
||||
{
|
||||
return wxYES == wxMessageBox(StrToWxStr(text), StrToWxStr(caption),
|
||||
(yes_no) ? wxYES_NO : wxOK, wxWindow::FindFocus());
|
||||
}
|
||||
else
|
||||
{
|
||||
npd->AppendChat("/!\\ " + std::string{text});
|
||||
return true;
|
||||
}
|
||||
#ifdef __WXGTK__
|
||||
}
|
||||
else
|
||||
{
|
||||
wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_PANIC);
|
||||
|
@ -407,6 +426,12 @@ CFrame* DolphinApp::GetCFrame()
|
|||
|
||||
void Host_Message(int Id)
|
||||
{
|
||||
if (Id == WM_USER_JOB_DISPATCH)
|
||||
{
|
||||
// Trigger a wxEVT_IDLE
|
||||
wxWakeUpIdle();
|
||||
return;
|
||||
}
|
||||
wxCommandEvent event(wxEVT_HOST_COMMAND, Id);
|
||||
main_frame->GetEventHandler()->AddPendingEvent(event);
|
||||
}
|
||||
|
@ -554,5 +579,4 @@ void Host_ShowVideoConfig(void* parent, const std::string& backend_name,
|
|||
VideoConfigDiag diag((wxWindow*)parent, backend_name, config_name);
|
||||
diag.ShowModal();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ private:
|
|||
void OnEndSession(wxCloseEvent& event);
|
||||
void InitLanguageSupport();
|
||||
void AfterInit();
|
||||
void OnIdle(wxIdleEvent&);
|
||||
|
||||
bool m_batch_mode = false;
|
||||
bool m_confirm_stop = false;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <cstring>
|
||||
#include <getopt.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -22,7 +23,6 @@
|
|||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h"
|
||||
#include "Core/IPC_HLE/WII_IPC_HLE_WiiMote.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
|
@ -37,7 +37,14 @@ class Platform
|
|||
public:
|
||||
virtual void Init() {}
|
||||
virtual void SetTitle(const std::string &title) {}
|
||||
virtual void MainLoop() { while(running) {} }
|
||||
virtual void MainLoop()
|
||||
{
|
||||
while (running)
|
||||
{
|
||||
Core::HostDispatchJobs();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
virtual void Shutdown() {}
|
||||
virtual ~Platform() {}
|
||||
};
|
||||
|
@ -51,7 +58,10 @@ static Common::Event updateMainFrameEvent;
|
|||
void Host_Message(int Id)
|
||||
{
|
||||
if (Id == WM_USER_STOP)
|
||||
{
|
||||
running = false;
|
||||
updateMainFrameEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
static void* s_window_handle = nullptr;
|
||||
|
@ -102,10 +112,13 @@ void Host_ConnectWiimote(int wm_idx, bool connect)
|
|||
{
|
||||
if (Core::IsRunning() && SConfig::GetInstance().bWii)
|
||||
{
|
||||
bool was_unpaused = Core::PauseAndLock(true);
|
||||
GetUsbPointer()->AccessWiiMote(wm_idx | 0x100)->Activate(connect);
|
||||
Host_UpdateMainFrame();
|
||||
Core::PauseAndLock(false, was_unpaused);
|
||||
Core::QueueHostJob([=]
|
||||
{
|
||||
bool was_unpaused = Core::PauseAndLock(true);
|
||||
GetUsbPointer()->AccessWiiMote(wm_idx | 0x100)->Activate(connect);
|
||||
Host_UpdateMainFrame();
|
||||
Core::PauseAndLock(false, was_unpaused);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,6 +284,7 @@ class PlatformX11 : public Platform
|
|||
&borderDummy, &depthDummy);
|
||||
rendererIsFullscreen = false;
|
||||
}
|
||||
Core::HostDispatchJobs();
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
@ -354,13 +368,15 @@ int main(int argc, char* argv[])
|
|||
return 1;
|
||||
}
|
||||
|
||||
while (!Core::IsRunning())
|
||||
while (!Core::IsRunning() && running)
|
||||
{
|
||||
Core::HostDispatchJobs();
|
||||
updateMainFrameEvent.Wait();
|
||||
}
|
||||
|
||||
platform->MainLoop();
|
||||
if (running)
|
||||
platform->MainLoop();
|
||||
Core::Stop();
|
||||
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
|
||||
updateMainFrameEvent.Wait();
|
||||
|
||||
Core::Shutdown();
|
||||
platform->Shutdown();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
wxDEFINE_EVENT(INVALIDATE_BUTTON_EVENT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(INVALIDATE_CONTROL_EVENT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(INVALIDATE_EXTENSION_EVENT, wxThreadEvent);
|
||||
|
||||
struct TASWiimoteReport
|
||||
{
|
||||
|
@ -63,11 +64,17 @@ void TASInputDlg::CreateBaseLayout()
|
|||
m_controls[1] = &m_main_stick.y_cont;
|
||||
|
||||
m_a = CreateButton("A");
|
||||
m_a.checkbox->SetClientData(&m_a);
|
||||
m_b = CreateButton("B");
|
||||
m_b.checkbox->SetClientData(&m_b);
|
||||
m_dpad_up = CreateButton("Up");
|
||||
m_dpad_up.checkbox->SetClientData(&m_dpad_up);
|
||||
m_dpad_right = CreateButton("Right");
|
||||
m_dpad_right.checkbox->SetClientData(&m_dpad_right);
|
||||
m_dpad_down = CreateButton("Down");
|
||||
m_dpad_down.checkbox->SetClientData(&m_dpad_down);
|
||||
m_dpad_left = CreateButton("Left");
|
||||
m_dpad_left.checkbox->SetClientData(&m_dpad_left);
|
||||
|
||||
m_buttons_dpad = new wxGridSizer(3);
|
||||
m_buttons_dpad->AddSpacer(20);
|
||||
|
@ -134,10 +141,15 @@ void TASInputDlg::CreateWiiLayout(int num)
|
|||
wxGridSizer* const m_buttons_grid = new wxGridSizer(4);
|
||||
|
||||
m_plus = CreateButton("+");
|
||||
m_plus.checkbox->SetClientData(&m_plus);
|
||||
m_minus = CreateButton("-");
|
||||
m_minus.checkbox->SetClientData(&m_minus);
|
||||
m_one = CreateButton("1");
|
||||
m_one.checkbox->SetClientData(&m_one);
|
||||
m_two = CreateButton("2");
|
||||
m_two.checkbox->SetClientData(&m_two);
|
||||
m_home = CreateButton("Home");
|
||||
m_home.checkbox->SetClientData(&m_home);
|
||||
|
||||
m_main_szr = new wxBoxSizer(wxVERTICAL);
|
||||
m_wiimote_szr = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
@ -178,7 +190,9 @@ void TASInputDlg::CreateWiiLayout(int num)
|
|||
wxStaticBoxSizer* const nunchukaxisBox = CreateAccelLayout(&m_nx_cont, &m_ny_cont, &m_nz_cont, _("Nunchuk orientation"));
|
||||
|
||||
m_c = CreateButton("C");
|
||||
m_c.checkbox->SetClientData(&m_c);
|
||||
m_z = CreateButton("Z");
|
||||
m_z.checkbox->SetClientData(&m_z);
|
||||
m_ext_szr->Add(m_c_stick_szr, 0, wxLEFT | wxBOTTOM | wxRIGHT, 5);
|
||||
m_ext_szr->Add(nunchukaxisBox);
|
||||
|
||||
|
@ -212,6 +226,7 @@ void TASInputDlg::FinishLayout()
|
|||
Bind(wxEVT_CLOSE_WINDOW, &TASInputDlg::OnCloseWindow, this);
|
||||
Bind(INVALIDATE_BUTTON_EVENT, &TASInputDlg::UpdateFromInvalidatedButton, this);
|
||||
Bind(INVALIDATE_CONTROL_EVENT, &TASInputDlg::UpdateFromInvalidatedControl, this);
|
||||
Bind(INVALIDATE_EXTENSION_EVENT, &TASInputDlg::UpdateFromInvalidatedExtension, this);
|
||||
m_has_layout = true;
|
||||
}
|
||||
|
||||
|
@ -220,7 +235,10 @@ wxBoxSizer* TASInputDlg::CreateCCLayout()
|
|||
wxBoxSizer* const szr = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
for (size_t i = 0; i < ArraySize(m_cc_buttons); ++i)
|
||||
{
|
||||
m_cc_buttons[i] = CreateButton(m_cc_button_names[i]);
|
||||
m_cc_buttons[i].checkbox->SetClientData(&m_cc_buttons[i]);
|
||||
}
|
||||
|
||||
m_cc_l_stick = CreateStick(ID_CC_L_STICK, 63, 63, WiimoteEmu::Classic::LEFT_STICK_CENTER_X, WiimoteEmu::Classic::LEFT_STICK_CENTER_Y, false, true);
|
||||
m_cc_r_stick = CreateStick(ID_CC_R_STICK, 31, 31, WiimoteEmu::Classic::RIGHT_STICK_CENTER_X, WiimoteEmu::Classic::RIGHT_STICK_CENTER_Y, false, true);
|
||||
|
@ -348,11 +366,17 @@ void TASInputDlg::CreateGCLayout()
|
|||
wxGridSizer* const m_buttons_grid = new wxGridSizer(4);
|
||||
|
||||
m_x = CreateButton("X");
|
||||
m_x.checkbox->SetClientData(&m_x);
|
||||
m_y = CreateButton("Y");
|
||||
m_y.checkbox->SetClientData(&m_y);
|
||||
m_l = CreateButton("L");
|
||||
m_l.checkbox->SetClientData(&m_l);
|
||||
m_r = CreateButton("R");
|
||||
m_r.checkbox->SetClientData(&m_r);
|
||||
m_z = CreateButton("Z");
|
||||
m_z.checkbox->SetClientData(&m_z);
|
||||
m_start = CreateButton("Start");
|
||||
m_start.checkbox->SetClientData(&m_start);
|
||||
|
||||
for (unsigned int i = 4; i < ArraySize(m_buttons); ++i)
|
||||
if (m_buttons[i] != nullptr)
|
||||
|
@ -449,11 +473,18 @@ TASInputDlg::Button TASInputDlg::CreateButton(const std::string& name)
|
|||
wxCheckBox* checkbox = new wxCheckBox(this, m_eleID++, name);
|
||||
checkbox->Bind(wxEVT_RIGHT_DOWN, &TASInputDlg::SetTurbo, this);
|
||||
checkbox->Bind(wxEVT_LEFT_DOWN, &TASInputDlg::SetTurbo, this);
|
||||
checkbox->Bind(wxEVT_CHECKBOX, &TASInputDlg::OnCheckboxToggle, this);
|
||||
temp.checkbox = checkbox;
|
||||
temp.id = m_eleID - 1;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void TASInputDlg::OnCheckboxToggle(wxCommandEvent& event)
|
||||
{
|
||||
auto cbox = static_cast<wxCheckBox*>(event.GetEventObject());
|
||||
static_cast<Button*>(cbox->GetClientData())->is_checked = event.IsChecked();
|
||||
}
|
||||
|
||||
void TASInputDlg::ResetValues()
|
||||
{
|
||||
for (Button* const button : m_buttons)
|
||||
|
@ -491,6 +522,7 @@ void TASInputDlg::ResetValues()
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::SetStickValue(Control* control, int CurrentValue, int center)
|
||||
{
|
||||
if (CurrentValue != center)
|
||||
|
@ -511,6 +543,7 @@ void TASInputDlg::SetStickValue(Control* control, int CurrentValue, int center)
|
|||
InvalidateControl(control);
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::SetSliderValue(Control* control, int CurrentValue)
|
||||
{
|
||||
if (CurrentValue != (int)control->default_value)
|
||||
|
@ -531,6 +564,7 @@ void TASInputDlg::SetSliderValue(Control* control, int CurrentValue)
|
|||
InvalidateControl(control);
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::SetButtonValue(Button* button, bool CurrentState)
|
||||
{
|
||||
if (CurrentState)
|
||||
|
@ -550,16 +584,18 @@ void TASInputDlg::SetButtonValue(Button* button, bool CurrentState)
|
|||
InvalidateButton(button);
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::SetWiiButtons(u16* butt)
|
||||
{
|
||||
for (unsigned int i = 0; i < 11; ++i)
|
||||
{
|
||||
if (m_buttons[i] != nullptr)
|
||||
*butt |= (m_buttons[i]->checkbox->IsChecked()) ? m_wii_buttons_bitmask[i] : 0;
|
||||
*butt |= (m_buttons[i]->is_checked) ? m_wii_buttons_bitmask[i] : 0;
|
||||
}
|
||||
ButtonTurbo();
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::GetKeyBoardInput(GCPadStatus* PadStatus)
|
||||
{
|
||||
SetStickValue(&m_main_stick.x_cont, PadStatus->stickX);
|
||||
|
@ -579,6 +615,7 @@ void TASInputDlg::GetKeyBoardInput(GCPadStatus* PadStatus)
|
|||
SetButtonValue(&m_r, ((PadStatus->triggerRight) == 255) || ((PadStatus->button & PAD_TRIGGER_R) != 0));
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::GetKeyBoardInput(u8* data, WiimoteEmu::ReportFeatures rptf, int ext, const wiimote_key key)
|
||||
{
|
||||
u8* const coreData = rptf.core ? (data + rptf.core) : nullptr;
|
||||
|
@ -651,6 +688,9 @@ void TASInputDlg::GetKeyBoardInput(u8* data, WiimoteEmu::ReportFeatures rptf, in
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
// Do not touch the GUI. Requires wxMutexGuiEnter which will deadlock against
|
||||
// the GUI when pausing/stopping.
|
||||
void TASInputDlg::GetValues(u8* data, WiimoteEmu::ReportFeatures rptf, int ext, const wiimote_key key)
|
||||
{
|
||||
if (!IsShown() || !m_has_layout)
|
||||
|
@ -743,7 +783,7 @@ void TASInputDlg::GetValues(u8* data, WiimoteEmu::ReportFeatures rptf, int ext,
|
|||
if (ext != m_ext)
|
||||
{
|
||||
m_ext = ext;
|
||||
HandleExtensionChange();
|
||||
InvalidateExtension();
|
||||
}
|
||||
else if (extData && ext == 1)
|
||||
{
|
||||
|
@ -759,8 +799,8 @@ void TASInputDlg::GetValues(u8* data, WiimoteEmu::ReportFeatures rptf, int ext,
|
|||
nunchuk.az = m_nz_cont.value >> 2;
|
||||
nunchuk.bt.acc_z_lsb = m_nz_cont.value & 0x3;
|
||||
|
||||
nunchuk.bt.hex |= (m_buttons[11]->checkbox->IsChecked()) ? WiimoteEmu::Nunchuk::BUTTON_C : 0;
|
||||
nunchuk.bt.hex |= (m_buttons[12]->checkbox->IsChecked()) ? WiimoteEmu::Nunchuk::BUTTON_Z : 0;
|
||||
nunchuk.bt.hex |= (m_buttons[11]->is_checked) ? WiimoteEmu::Nunchuk::BUTTON_C : 0;
|
||||
nunchuk.bt.hex |= (m_buttons[12]->is_checked) ? WiimoteEmu::Nunchuk::BUTTON_Z : 0;
|
||||
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
|
||||
WiimoteEncrypt(&key, (u8*)&nunchuk, 0, sizeof(wm_nc));
|
||||
}
|
||||
|
@ -772,7 +812,7 @@ void TASInputDlg::GetValues(u8* data, WiimoteEmu::ReportFeatures rptf, int ext,
|
|||
|
||||
for (unsigned int i = 0; i < ArraySize(m_cc_buttons); ++i)
|
||||
{
|
||||
cc.bt.hex |= (m_cc_buttons[i].checkbox->IsChecked()) ? m_cc_buttons_bitmask[i] : 0;
|
||||
cc.bt.hex |= (m_cc_buttons[i].is_checked) ? m_cc_buttons_bitmask[i] : 0;
|
||||
}
|
||||
cc.bt.hex ^= 0xFFFF;
|
||||
|
||||
|
@ -793,6 +833,7 @@ void TASInputDlg::GetValues(u8* data, WiimoteEmu::ReportFeatures rptf, int ext,
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::GetValues(GCPadStatus* PadStatus)
|
||||
{
|
||||
if (!IsShown() || !m_has_layout)
|
||||
|
@ -805,26 +846,26 @@ void TASInputDlg::GetValues(GCPadStatus* PadStatus)
|
|||
PadStatus->stickY = m_main_stick.y_cont.value;
|
||||
PadStatus->substickX = m_c_stick.x_cont.value;
|
||||
PadStatus->substickY = m_c_stick.y_cont.value;
|
||||
PadStatus->triggerLeft = m_l.checkbox->GetValue() ? 255 : m_l_cont.value;
|
||||
PadStatus->triggerRight = m_r.checkbox->GetValue() ? 255 : m_r_cont.value;
|
||||
PadStatus->triggerLeft = m_l.is_checked ? 255 : m_l_cont.value;
|
||||
PadStatus->triggerRight = m_r.is_checked ? 255 : m_r_cont.value;
|
||||
|
||||
for (unsigned int i = 0; i < ArraySize(m_buttons); ++i)
|
||||
{
|
||||
if (m_buttons[i] != nullptr)
|
||||
{
|
||||
if (m_buttons[i]->checkbox->IsChecked())
|
||||
if (m_buttons[i]->is_checked)
|
||||
PadStatus->button |= m_gc_pad_buttons_bitmask[i];
|
||||
else
|
||||
PadStatus->button &= ~m_gc_pad_buttons_bitmask[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (m_a.checkbox->IsChecked())
|
||||
if (m_a.is_checked)
|
||||
PadStatus->analogA = 0xFF;
|
||||
else
|
||||
PadStatus->analogA = 0x00;
|
||||
|
||||
if (m_b.checkbox->IsChecked())
|
||||
if (m_b.is_checked)
|
||||
PadStatus->analogB = 0xFF;
|
||||
else
|
||||
PadStatus->analogB = 0x00;
|
||||
|
@ -1035,6 +1076,7 @@ void TASInputDlg::SetTurbo(wxMouseEvent& event)
|
|||
event.Skip();
|
||||
}
|
||||
|
||||
// NOTE: Host / CPU Thread
|
||||
void TASInputDlg::ButtonTurbo()
|
||||
{
|
||||
static u64 frame = Movie::g_currentFrame;
|
||||
|
@ -1046,7 +1088,7 @@ void TASInputDlg::ButtonTurbo()
|
|||
{
|
||||
if (button != nullptr && button->turbo_on)
|
||||
{
|
||||
button->value = !button->checkbox->GetValue();
|
||||
button->value = !button->is_checked;
|
||||
InvalidateButton(button);
|
||||
}
|
||||
}
|
||||
|
@ -1056,7 +1098,7 @@ void TASInputDlg::ButtonTurbo()
|
|||
{
|
||||
if (button.turbo_on)
|
||||
{
|
||||
button.value = !button.checkbox->GetValue();
|
||||
button.value = !button.is_checked;
|
||||
InvalidateButton(&button);
|
||||
}
|
||||
}
|
||||
|
@ -1075,6 +1117,7 @@ void TASInputDlg::InvalidateButton(Button* button)
|
|||
}
|
||||
|
||||
button->checkbox->SetValue(button->value);
|
||||
button->is_checked = button->value;
|
||||
}
|
||||
|
||||
void TASInputDlg::InvalidateControl(Control* control)
|
||||
|
@ -1090,11 +1133,23 @@ void TASInputDlg::InvalidateControl(Control* control)
|
|||
control->text->SetValue(std::to_string(control->value));
|
||||
}
|
||||
|
||||
void TASInputDlg::InvalidateExtension()
|
||||
{
|
||||
if (!wxIsMainThread())
|
||||
{
|
||||
GetEventHandler()->QueueEvent(new wxThreadEvent(INVALIDATE_EXTENSION_EVENT));
|
||||
return;
|
||||
}
|
||||
|
||||
HandleExtensionChange();
|
||||
}
|
||||
|
||||
void TASInputDlg::UpdateFromInvalidatedButton(wxCommandEvent& event)
|
||||
{
|
||||
Button* button = static_cast<Button*>(event.GetClientData());
|
||||
_assert_msg_(PAD, button->id == button->checkbox->GetId(), "Button ids do not match: %i != %i", button->id, button->checkbox->GetId());
|
||||
button->checkbox->SetValue(button->value);
|
||||
button->is_checked = button->value;
|
||||
}
|
||||
|
||||
void TASInputDlg::UpdateFromInvalidatedControl(wxCommandEvent& event)
|
||||
|
@ -1104,6 +1159,11 @@ void TASInputDlg::UpdateFromInvalidatedControl(wxCommandEvent& event)
|
|||
control->text->SetValue(std::to_string(control->value));
|
||||
}
|
||||
|
||||
void TASInputDlg::UpdateFromInvalidatedExtension(wxThreadEvent&)
|
||||
{
|
||||
HandleExtensionChange();
|
||||
}
|
||||
|
||||
wxBitmap TASInputDlg::CreateStickBitmap(int x, int y)
|
||||
{
|
||||
x = x / 2;
|
||||
|
|
|
@ -70,6 +70,7 @@ class TASInputDlg : public wxDialog
|
|||
struct Button
|
||||
{
|
||||
wxCheckBox* checkbox;
|
||||
bool is_checked = false;
|
||||
bool value = false;
|
||||
bool set_by_keyboard = false;
|
||||
bool turbo_on = false;
|
||||
|
@ -92,8 +93,11 @@ class TASInputDlg : public wxDialog
|
|||
void UpdateStickBitmap(Stick stick);
|
||||
void InvalidateButton(Button* button);
|
||||
void InvalidateControl(Control* button);
|
||||
void InvalidateExtension();
|
||||
void UpdateFromInvalidatedButton(wxCommandEvent& event);
|
||||
void UpdateFromInvalidatedControl(wxCommandEvent& event);
|
||||
void UpdateFromInvalidatedExtension(wxThreadEvent& event);
|
||||
void OnCheckboxToggle(wxCommandEvent& event);
|
||||
Stick* FindStickByID(int id);
|
||||
Stick CreateStick(int id_stick, int xRange, int yRange, u32 defaultX, u32 defaultY, bool reverseX, bool reverseY);
|
||||
wxStaticBoxSizer* CreateStickLayout(Stick* tempStick, const wxString& title);
|
||||
|
|
|
@ -23,6 +23,9 @@ namespace GCAdapter
|
|||
{
|
||||
static bool CheckDeviceAccess(libusb_device* device);
|
||||
static void AddGCAdapter(libusb_device* device);
|
||||
static void ResetRumbleLockNeeded();
|
||||
static void Reset();
|
||||
static void Setup();
|
||||
|
||||
static bool s_detected = false;
|
||||
static libusb_device_handle* s_handle = nullptr;
|
||||
|
@ -38,6 +41,7 @@ static std::atomic<int> s_controller_payload_size = {0};
|
|||
static std::thread s_adapter_thread;
|
||||
static Common::Flag s_adapter_thread_running;
|
||||
|
||||
static std::mutex s_init_mutex;
|
||||
static std::thread s_adapter_detect_thread;
|
||||
static Common::Flag s_adapter_detect_thread_running;
|
||||
|
||||
|
@ -78,7 +82,10 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
|
|||
if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
|
||||
{
|
||||
if (s_handle == nullptr && CheckDeviceAccess(dev))
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(s_init_mutex);
|
||||
AddGCAdapter(dev);
|
||||
}
|
||||
}
|
||||
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
||||
{
|
||||
|
@ -116,6 +123,7 @@ static void ScanThreadFunc()
|
|||
{
|
||||
if (s_handle == nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(s_init_mutex);
|
||||
Setup();
|
||||
if (s_detected && s_detect_callback != nullptr)
|
||||
s_detect_callback();
|
||||
|
@ -178,7 +186,7 @@ void StopScanThread()
|
|||
}
|
||||
}
|
||||
|
||||
void Setup()
|
||||
static void Setup()
|
||||
{
|
||||
libusb_device** list;
|
||||
ssize_t cnt = libusb_get_device_list(s_libusb_context, &list);
|
||||
|
@ -307,7 +315,7 @@ static void AddGCAdapter(libusb_device* device)
|
|||
s_detected = true;
|
||||
if (s_detect_callback != nullptr)
|
||||
s_detect_callback();
|
||||
ResetRumble();
|
||||
ResetRumbleLockNeeded();
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
|
@ -328,8 +336,11 @@ void Shutdown()
|
|||
s_libusb_driver_not_supported = false;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
static void Reset()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(s_init_mutex, std::defer_lock);
|
||||
if (!lock.try_lock())
|
||||
return;
|
||||
if (!s_detected)
|
||||
return;
|
||||
|
||||
|
@ -443,10 +454,20 @@ bool UseAdapter()
|
|||
|
||||
void ResetRumble()
|
||||
{
|
||||
if (!UseAdapter())
|
||||
std::unique_lock<std::mutex> lock(s_init_mutex, std::defer_lock);
|
||||
if (!lock.try_lock())
|
||||
return;
|
||||
if (s_handle == nullptr || !s_detected)
|
||||
ResetRumbleLockNeeded();
|
||||
}
|
||||
|
||||
// Needs to be called when s_init_mutex is locked in order to avoid
|
||||
// being called while the libusb state is being reset
|
||||
static void ResetRumbleLockNeeded()
|
||||
{
|
||||
if (!UseAdapter() || (s_handle == nullptr || !s_detected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::fill(std::begin(s_controller_rumble), std::end(s_controller_rumble), 0);
|
||||
|
||||
|
|
|
@ -19,9 +19,7 @@ enum ControllerTypes
|
|||
CONTROLLER_WIRELESS = 2
|
||||
};
|
||||
void Init();
|
||||
void Reset();
|
||||
void ResetRumble();
|
||||
void Setup();
|
||||
void Shutdown();
|
||||
void SetAdapterCallback(std::function<void(void)> func);
|
||||
void StartScanThread();
|
||||
|
|
|
@ -24,6 +24,9 @@ extern JavaVM* g_java_vm;
|
|||
|
||||
namespace GCAdapter
|
||||
{
|
||||
static void Setup();
|
||||
static void Reset();
|
||||
|
||||
// Java classes
|
||||
static jclass s_adapter_class;
|
||||
|
||||
|
@ -207,7 +210,7 @@ void Init()
|
|||
StartScanThread();
|
||||
}
|
||||
|
||||
void Setup()
|
||||
static void Setup()
|
||||
{
|
||||
s_fd = 0;
|
||||
s_detected = true;
|
||||
|
@ -220,7 +223,7 @@ void Setup()
|
|||
s_read_adapter_thread = std::thread(Read);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
static void Reset()
|
||||
{
|
||||
if (!s_detected)
|
||||
return;
|
||||
|
|
|
@ -9,9 +9,7 @@ namespace GCAdapter
|
|||
{
|
||||
|
||||
void Init() {}
|
||||
void Reset() {}
|
||||
void ResetRumble() {}
|
||||
void Setup() {}
|
||||
void Shutdown() {}
|
||||
void SetAdapterCallback(std::function<void(void)> func) {}
|
||||
void StartScanThread() {}
|
||||
|
|
|
@ -124,8 +124,20 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
|
|||
{
|
||||
u32 level_width = std::max(config.width >> level, 1u);
|
||||
u32 level_height = std::max(config.height >> level, 1u);
|
||||
size_t level_pitch = ROUND_UP(level_width * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
|
||||
size_t required_readback_buffer_size = level_pitch * level_height;
|
||||
size_t level_pitch = level_width;
|
||||
size_t num_lines = level_height;
|
||||
if (this->compressed)
|
||||
{
|
||||
level_pitch = (level_pitch + 3) >> 2;
|
||||
level_pitch *= 16; // Size of the bc2 block
|
||||
num_lines = (num_lines + 3) >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
level_pitch *= sizeof(u32);
|
||||
}
|
||||
level_pitch = ROUND_UP(level_pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
|
||||
size_t required_readback_buffer_size = level_pitch * num_lines;
|
||||
|
||||
// Check if the current readback buffer is large enough
|
||||
if (required_readback_buffer_size > s_texture_cache_entry_readback_buffer_size)
|
||||
|
@ -150,7 +162,7 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
|
|||
dst_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
dst_location.PlacedFootprint.Offset = 0;
|
||||
dst_location.PlacedFootprint.Footprint.Depth = 1;
|
||||
dst_location.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
dst_location.PlacedFootprint.Footprint.Format = this->DXGI_format;
|
||||
dst_location.PlacedFootprint.Footprint.Width = level_width;
|
||||
dst_location.PlacedFootprint.Footprint.Height = level_height;
|
||||
dst_location.PlacedFootprint.Footprint.RowPitch = static_cast<UINT>(level_pitch);
|
||||
|
@ -166,13 +178,28 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
|
|||
D3D12_RANGE read_range = { 0, required_readback_buffer_size };
|
||||
CheckHR(s_texture_cache_entry_readback_buffer->Map(0, &read_range, &readback_texture_map));
|
||||
|
||||
bool saved = TextureToPng(
|
||||
static_cast<u8*>(readback_texture_map),
|
||||
dst_location.PlacedFootprint.Footprint.RowPitch,
|
||||
filename,
|
||||
dst_location.PlacedFootprint.Footprint.Width,
|
||||
dst_location.PlacedFootprint.Footprint.Height
|
||||
bool saved = false;
|
||||
if (this->compressed)
|
||||
{
|
||||
saved = TextureToDDS(
|
||||
static_cast<u8*>(readback_texture_map),
|
||||
dst_location.PlacedFootprint.Footprint.RowPitch,
|
||||
filename,
|
||||
dst_location.PlacedFootprint.Footprint.Width,
|
||||
dst_location.PlacedFootprint.Footprint.Height
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
saved = TextureToPng(
|
||||
static_cast<u8*>(readback_texture_map),
|
||||
dst_location.PlacedFootprint.Footprint.RowPitch,
|
||||
filename,
|
||||
dst_location.PlacedFootprint.Footprint.Width,
|
||||
dst_location.PlacedFootprint.Footprint.Height
|
||||
);
|
||||
}
|
||||
|
||||
D3D12_RANGE write_range = {};
|
||||
s_texture_cache_entry_readback_buffer->Unmap(0, &write_range);
|
||||
return saved;
|
||||
|
|
|
@ -49,47 +49,51 @@ void TextureCache::TCacheEntry::Bind(u32 stage, u32 last_texture)
|
|||
|
||||
bool TextureCache::TCacheEntry::Save(const std::string& filename, u32 level)
|
||||
{
|
||||
// TODO: Somehow implement this (D3DX11 doesn't support dumping individual LODs)
|
||||
static bool warn_once = true;
|
||||
if (level && warn_once)
|
||||
// Create a staging/readback texture with the dimensions of the specified mip level.
|
||||
u32 mip_width = std::max(config.width >> level, 1u);
|
||||
u32 mip_height = std::max(config.height >> level, 1u);
|
||||
CD3D11_TEXTURE2D_DESC staging_texture_desc(
|
||||
this->DXGI_format,
|
||||
mip_width, mip_height, 1, 1, 0,
|
||||
D3D11_USAGE_STAGING,
|
||||
D3D11_CPU_ACCESS_READ);
|
||||
|
||||
ID3D11Texture2D* staging_texture;
|
||||
HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &staging_texture);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WARN_LOG(VIDEO, "Dumping individual LOD not supported by D3D11 backend!");
|
||||
warn_once = false;
|
||||
WARN_LOG(VIDEO, "Failed to create texture dumping readback texture: %X", static_cast<u32>(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D11Texture2D* pNewTexture = nullptr;
|
||||
ID3D11Texture2D* pSurface = texture->GetTex();
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
pSurface->GetDesc(&desc);
|
||||
if (desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM)
|
||||
// Copy the selected mip level to the staging texture.
|
||||
CD3D11_BOX src_box(0, 0, 0, mip_width, mip_height, 1);
|
||||
D3D::context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0,
|
||||
texture->GetTex(), D3D11CalcSubresource(level, 0, config.levels), &src_box);
|
||||
|
||||
// Map the staging texture to client memory, and encode it as a .png image.
|
||||
D3D11_MAPPED_SUBRESOURCE map;
|
||||
hr = D3D::context->Map(staging_texture, 0, D3D11_MAP_READ, 0, &map);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// Do not support compressed texture dump right now
|
||||
WARN_LOG(VIDEO, "Failed to map texture dumping readback texture: %X", static_cast<u32>(hr));
|
||||
staging_texture->Release();
|
||||
return false;
|
||||
}
|
||||
desc.BindFlags = 0;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
|
||||
HRESULT hr = D3D::device->CreateTexture2D(&desc, nullptr, &pNewTexture);
|
||||
|
||||
bool saved_png = false;
|
||||
|
||||
if (SUCCEEDED(hr) && pNewTexture)
|
||||
bool encode_result = false;
|
||||
if (this->compressed)
|
||||
{
|
||||
D3D::context->CopyResource(pNewTexture, pSurface);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE map;
|
||||
hr = D3D::context->Map(pNewTexture, 0, D3D11_MAP_READ_WRITE, 0, &map);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
saved_png = TextureToPng((u8*)map.pData, map.RowPitch, filename, desc.Width, desc.Height);
|
||||
D3D::context->Unmap(pNewTexture, 0);
|
||||
}
|
||||
SAFE_RELEASE(pNewTexture);
|
||||
encode_result = TextureToDDS(reinterpret_cast<u8*>(map.pData), map.RowPitch, filename, mip_width, mip_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
encode_result = TextureToPng(reinterpret_cast<u8*>(map.pData), map.RowPitch, filename, mip_width, mip_height);
|
||||
}
|
||||
D3D::context->Unmap(staging_texture, 0);
|
||||
staging_texture->Release();
|
||||
|
||||
return saved_png;
|
||||
return encode_result;
|
||||
}
|
||||
|
||||
void TextureCache::LoadLut(u32 lutFmt, void* addr, u32 size) {
|
||||
|
|
|
@ -93,7 +93,7 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, u32 level)
|
|||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = PD3DXSaveSurfaceToFileA(filename.c_str(), D3DXIFF_PNG, surface, NULL, NULL);
|
||||
hr = PD3DXSaveSurfaceToFileA(filename.c_str(), this->compressed ? D3DXIFF_DDS : D3DXIFF_PNG, surface, NULL, NULL);
|
||||
surface->Release();
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
|
|
|
@ -74,19 +74,29 @@ static std::unique_ptr<Depalettizer> s_depaletizer;
|
|||
static std::unique_ptr<TextureScaler> s_scaler;
|
||||
static std::pair<u8*, u32> s_last_pallet_Buffer;
|
||||
static TlutFormat s_last_TlutFormat = TlutFormat::GX_TL_IA8;
|
||||
bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width, int virtual_height, u32 level)
|
||||
bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width, int virtual_height, u32 level, bool compressed = false)
|
||||
{
|
||||
if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL)
|
||||
return false;
|
||||
int width = std::max(virtual_width >> level, 1);
|
||||
int height = std::max(virtual_height >> level, 1);
|
||||
std::vector<u8> data(width * height * 4);
|
||||
int size = compressed ? (((width + 3) >> 2) * ((height + 3) >> 2) * 16) : (width * height * 4);
|
||||
std::vector<u8> data(size);
|
||||
glActiveTexture(GL_TEXTURE9);
|
||||
glBindTexture(textarget, tex);
|
||||
glGetTexImage(textarget, level, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
|
||||
bool saved = false;
|
||||
if (compressed)
|
||||
{
|
||||
glGetCompressedTexImage(textarget, level, data.data());
|
||||
saved = TextureToDDS(data.data(), width * 4, filename, width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
glGetTexImage(textarget, level, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
|
||||
saved = TextureToPng(data.data(), width * 4, filename, width, height);
|
||||
}
|
||||
TextureCache::SetStage();
|
||||
|
||||
return TextureToPng(data.data(), width * 4, filename, width, height, true);
|
||||
return saved;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntry::~TCacheEntry()
|
||||
|
@ -144,7 +154,7 @@ void TextureCache::TCacheEntry::Bind(u32 stage, u32 last_texture)
|
|||
|
||||
bool TextureCache::TCacheEntry::Save(const std::string& filename, u32 level)
|
||||
{
|
||||
return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, texture, config.width, config.height, level);
|
||||
return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, texture, config.width, config.height, level, this->compressed);
|
||||
}
|
||||
|
||||
PC_TexFormat TextureCache::GetNativeTextureFormat(const s32 texformat, const TlutFormat tlutfmt, u32 width, u32 height)
|
||||
|
|
|
@ -31,17 +31,13 @@
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "ImageLoader.h"
|
||||
#include "Common/Common.h"
|
||||
#ifndef _WIN32
|
||||
#define BYTE u8
|
||||
#define DWORD u32
|
||||
#endif
|
||||
#include "ImageWrite.h"
|
||||
|
||||
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
|
||||
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
|
||||
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
|
||||
#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((u32)(u8)(ch0) | ((u32)(u8)(ch1) << 8) | ((u32)(u8)(ch2) << 16) | ((u32)(u8)(ch3) << 24 ))
|
||||
/*
|
||||
* FOURCC codes for DX compressed-texture pixel formats
|
||||
*/
|
||||
#define DDS_SIGNARURE (MAKEFOURCC('D','D','S',' '))
|
||||
#define FOURCC_DXT1 (MAKEFOURCC('D','X','T','1'))
|
||||
#define FOURCC_DXT3 (MAKEFOURCC('D','X','T','3'))
|
||||
#define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
|
||||
|
@ -63,7 +59,6 @@
|
|||
#define DDPF_ALPHAPIXELS 0x00000001
|
||||
#define DDPF_FOURCC 0x00000004
|
||||
#define DDPF_RGB 0x00000040
|
||||
const u32 DDSSignature = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24);
|
||||
|
||||
typedef struct _DDPIXELFORMAT
|
||||
{
|
||||
|
@ -194,7 +189,7 @@ DDSCompression ImageLoader::ReadDDS(ImageLoaderParams& loader_params)
|
|||
|
||||
// Get the surface descriptor
|
||||
u32 readedsize = (u32)fread(&ddsd, sizeof(ddsd), 1, pFile);
|
||||
if (ddsd.dwSignature != DDSSignature || ddsd.dwSize != 124)
|
||||
if (ddsd.dwSignature != DDS_SIGNARURE || ddsd.dwSize != 124)
|
||||
{
|
||||
fclose(pFile);
|
||||
return Result;
|
||||
|
@ -306,3 +301,46 @@ DDSCompression ImageLoader::ReadDDS(ImageLoaderParams& loader_params)
|
|||
loader_params.nummipmaps = ddsd.dwMipMapCount;
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool TextureToDDS(u8* data, int row_stride, const std::string& filename, int width, int height, DDSCompression format)
|
||||
{
|
||||
DDSHeader header = {0};
|
||||
header.dwSignature = DDS_SIGNARURE;
|
||||
header.dwSize = 124;
|
||||
header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
|
||||
header.ddpfPixelFormat.dwFlags = DDPF_FOURCC | DDPF_RGB;
|
||||
header.ddpfPixelFormat.dwSize = 32;
|
||||
header.dwWidth = width;
|
||||
header.dwHeight = height;
|
||||
switch (format)
|
||||
{
|
||||
case DDSC_DXT1:
|
||||
header.ddpfPixelFormat.dwFourCC = FOURCC_DXT1;
|
||||
break;
|
||||
case DDSC_DXT3:
|
||||
header.ddpfPixelFormat.dwFourCC = FOURCC_DXT3;
|
||||
break;
|
||||
case DDSC_DXT5:
|
||||
header.ddpfPixelFormat.dwFourCC = FOURCC_DXT5;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
header.dwLinearSize = ((header.dwWidth + 3) >> 2)*((header.dwHeight + 3) >> 2) * 16;
|
||||
header.dwMipMapCount = 1;
|
||||
File::IOFile fp(filename, "wb");
|
||||
if(!fp.IsOpen())
|
||||
{
|
||||
PanicAlertT("Screenshot failed: Could not open file %s %d", filename.c_str(), errno);
|
||||
return false;
|
||||
}
|
||||
fp.WriteBytes(&header, sizeof(DDSHeader));
|
||||
u32 ddstride = ((header.dwWidth + 3) >> 2) * 16;
|
||||
u32 lines = ((header.dwHeight + 3) >> 2);
|
||||
for (size_t i = 0; i < lines; i++)
|
||||
{
|
||||
fp.WriteBytes(data, ddstride);
|
||||
data += row_stride;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include <string>
|
||||
#include "Common/Common.h"
|
||||
#include "VideoCommon/ImageLoader.h"
|
||||
|
||||
bool SaveData(const std::string& filename, const std::string& data);
|
||||
bool TextureToPng(u8* data, int row_stride, const std::string& filename, int width, int height, bool saveAlpha = true);
|
||||
bool TextureToDDS(u8* data, int row_stride, const std::string& filename, int width, int height, DDSCompression format = DDSCompression::DDSC_DXT3);
|
||||
|
|
|
@ -455,7 +455,7 @@ void TextureCacheBase::DumpTexture(TCacheEntryBase* entry, std::string basename,
|
|||
{
|
||||
basename += StringFromFormat("_mip%i", level);
|
||||
}
|
||||
std::string filename = szDir + "/" + basename + ".png";
|
||||
std::string filename = szDir + "/" + basename + ((entry->config.pcformat >= PC_TEX_FMT_DXT1) ? ".dds" : ".png");
|
||||
|
||||
if (!File::Exists(filename))
|
||||
entry->Save(filename, level);
|
||||
|
|
Loading…
Reference in a new issue