summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/Fifo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/VideoCommon/Fifo.cpp')
-rw-r--r--Source/Core/VideoCommon/Fifo.cpp340
1 files changed, 274 insertions, 66 deletions
diff --git a/Source/Core/VideoCommon/Fifo.cpp b/Source/Core/VideoCommon/Fifo.cpp
index 4e1b52dfa1..44322d4d3b 100644
--- a/Source/Core/VideoCommon/Fifo.cpp
+++ b/Source/Core/VideoCommon/Fifo.cpp
@@ -11,32 +11,63 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
+#include "Core/NetPlayProto.h"
#include "Core/HW/Memmap.h"
#include "VideoCommon/CommandProcessor.h"
+#include "VideoCommon/CPMemory.h"
#include "VideoCommon/DataReader.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/OpcodeDecoding.h"
#include "VideoCommon/PixelEngine.h"
+#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VideoConfig.h"
bool g_bSkipCurrentFrame = false;
-namespace
-{
static volatile bool GpuRunningState = false;
static volatile bool EmuRunningState = false;
static std::mutex m_csHWVidOccupied;
+
+// Most of this array is unlikely to be faulted in...
+static u8 s_fifo_aux_data[FIFO_SIZE];
+static u8* s_fifo_aux_write_ptr;
+static u8* s_fifo_aux_read_ptr;
+
+bool g_use_deterministic_gpu_thread;
+
// STATE_TO_SAVE
-static u8 *videoBuffer;
-static int size = 0;
-} // namespace
+static std::mutex s_video_buffer_lock;
+static std::condition_variable s_video_buffer_cond;
+static u8* s_video_buffer;
+u8* g_video_buffer_read_ptr;
+static std::atomic<u8*> s_video_buffer_write_ptr;
+static std::atomic<u8*> s_video_buffer_seen_ptr;
+u8* g_video_buffer_pp_read_ptr;
+// The read_ptr is always owned by the GPU thread. In normal mode, so is the
+// write_ptr, despite it being atomic. In g_use_deterministic_gpu_thread mode,
+// things get a bit more complicated:
+// - The seen_ptr is written by the GPU thread, and points to what it's already
+// processed as much of as possible - in the case of a partial command which
+// caused it to stop, not the same as the read ptr. It's written by the GPU,
+// under the lock, and updating the cond.
+// - The write_ptr is written by the CPU thread after it copies data from the
+// FIFO. Maybe someday it will be under the lock. For now, because RunGpuLoop
+// polls, it's just atomic.
+// - The pp_read_ptr is the CPU preprocessing version of the read_ptr.
void Fifo_DoState(PointerWrap &p)
{
- p.DoArray(videoBuffer, FIFO_SIZE);
- p.Do(size);
- p.DoPointer(g_pVideoData, videoBuffer);
+ p.DoArray(s_video_buffer, FIFO_SIZE);
+ u8* write_ptr = s_video_buffer_write_ptr;
+ p.DoPointer(write_ptr, s_video_buffer);
+ s_video_buffer_write_ptr = write_ptr;
+ p.DoPointer(g_video_buffer_read_ptr, s_video_buffer);
+ if (p.mode == PointerWrap::MODE_READ && g_use_deterministic_gpu_thread)
+ {
+ // We're good and paused, right?
+ s_video_buffer_seen_ptr = g_video_buffer_pp_read_ptr = g_video_buffer_read_ptr;
+ }
p.Do(g_bSkipCurrentFrame);
}
@@ -44,6 +75,7 @@ void Fifo_PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock)
{
+ SyncGPU(SYNC_GPU_OTHER);
EmulatorState(false);
if (!Core::IsGPUThread())
m_csHWVidOccupied.lock();
@@ -61,8 +93,8 @@ void Fifo_PauseAndLock(bool doLock, bool unpauseOnUnlock)
void Fifo_Init()
{
- videoBuffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
- size = 0;
+ s_video_buffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
+ ResetVideoBuffer();
GpuRunningState = false;
Common::AtomicStore(CommandProcessor::VITicks, CommandProcessor::m_cpClockOrigin);
}
@@ -70,18 +102,24 @@ void Fifo_Init()
void Fifo_Shutdown()
{
if (GpuRunningState) PanicAlert("Fifo shutting down while active");
- FreeMemoryPages(videoBuffer, FIFO_SIZE);
- videoBuffer = nullptr;
+ FreeMemoryPages(s_video_buffer, FIFO_SIZE);
+ s_video_buffer = nullptr;
+ s_video_buffer_write_ptr = nullptr;
+ g_video_buffer_pp_read_ptr = nullptr;
+ g_video_buffer_read_ptr = nullptr;
+ s_video_buffer_seen_ptr = nullptr;
+ s_fifo_aux_write_ptr = nullptr;
+ s_fifo_aux_read_ptr = nullptr;
}
u8* GetVideoBufferStartPtr()
{
- return videoBuffer;
+ return s_video_buffer;
}
u8* GetVideoBufferEndPtr()
{
- return &videoBuffer[size];
+ return s_video_buffer_write_ptr;
}
void Fifo_SetRendering(bool enabled)
@@ -107,30 +145,123 @@ void EmulatorState(bool running)
EmuRunningState = running;
}
+void SyncGPU(SyncGPUReason reason, bool may_move_read_ptr)
+{
+ if (g_use_deterministic_gpu_thread && GpuRunningState)
+ {
+ std::unique_lock<std::mutex> lk(s_video_buffer_lock);
+ u8* write_ptr = s_video_buffer_write_ptr;
+ s_video_buffer_cond.wait(lk, [&]() {
+ return !GpuRunningState || s_video_buffer_seen_ptr == write_ptr;
+ });
+ if (!GpuRunningState)
+ return;
+
+ // Opportunistically reset FIFOs so we don't wrap around.
+ if (may_move_read_ptr && s_fifo_aux_write_ptr != s_fifo_aux_read_ptr)
+ PanicAlert("aux fifo not synced (%p, %p)", s_fifo_aux_write_ptr, s_fifo_aux_read_ptr);
+
+ memmove(s_fifo_aux_data, s_fifo_aux_read_ptr, s_fifo_aux_write_ptr - s_fifo_aux_read_ptr);
+ s_fifo_aux_write_ptr -= (s_fifo_aux_read_ptr - s_fifo_aux_data);
+ s_fifo_aux_read_ptr = s_fifo_aux_data;
+
+ if (may_move_read_ptr)
+ {
+ // what's left over in the buffer
+ size_t size = write_ptr - g_video_buffer_pp_read_ptr;
+
+ memmove(s_video_buffer, g_video_buffer_pp_read_ptr, size);
+ // This change always decreases the pointers. We write seen_ptr
+ // after write_ptr here, and read it before in RunGpuLoop, so
+ // 'write_ptr > seen_ptr' there cannot become spuriously true.
+ s_video_buffer_write_ptr = write_ptr = s_video_buffer + size;
+ g_video_buffer_pp_read_ptr = s_video_buffer;
+ g_video_buffer_read_ptr = s_video_buffer;
+ s_video_buffer_seen_ptr = write_ptr;
+ }
+ }
+}
+
+void PushFifoAuxBuffer(void* ptr, size_t size)
+{
+ if (size > (size_t) (s_fifo_aux_data + FIFO_SIZE - s_fifo_aux_write_ptr))
+ {
+ SyncGPU(SYNC_GPU_AUX_SPACE, /* may_move_read_ptr */ false);
+ if (size > (size_t) (s_fifo_aux_data + FIFO_SIZE - s_fifo_aux_write_ptr))
+ {
+ // That will sync us up to the last 32 bytes, so this short region
+ // of FIFO would have to point to a 2MB display list or something.
+ PanicAlert("absurdly large aux buffer");
+ return;
+ }
+ }
+ memcpy(s_fifo_aux_write_ptr, ptr, size);
+ s_fifo_aux_write_ptr += size;
+}
+
+void* PopFifoAuxBuffer(size_t size)
+{
+ void* ret = s_fifo_aux_read_ptr;
+ s_fifo_aux_read_ptr += size;
+ return ret;
+}
// Description: RunGpuLoop() sends data through this function.
-void ReadDataFromFifo(u8* _uData, u32 len)
+static void ReadDataFromFifo(u8* _uData, u32 len)
+{
+ if (len > (s_video_buffer + FIFO_SIZE - s_video_buffer_write_ptr))
+ {
+ size_t size = s_video_buffer_write_ptr - g_video_buffer_read_ptr;
+ if (len > FIFO_SIZE - size)
+ {
+ PanicAlert("FIFO out of bounds (existing %lu + new %lu > %lu)", (unsigned long) size, (unsigned long) len, (unsigned long) FIFO_SIZE);
+ return;
+ }
+ memmove(s_video_buffer, g_video_buffer_read_ptr, size);
+ s_video_buffer_write_ptr = s_video_buffer + size;
+ g_video_buffer_read_ptr = s_video_buffer;
+ }
+ // Copy new video instructions to s_video_buffer for future use in rendering the new picture
+ memcpy(s_video_buffer_write_ptr, _uData, len);
+ s_video_buffer_write_ptr += len;
+}
+
+// The deterministic_gpu_thread version.
+static void ReadDataFromFifoOnCPU(u8* _uData, u32 len)
{
- if (size + len >= FIFO_SIZE)
+ u8 *write_ptr = s_video_buffer_write_ptr;
+ if (len > (s_video_buffer + FIFO_SIZE - write_ptr))
{
- int pos = (int)(g_pVideoData - videoBuffer);
- size -= pos;
- if (size + len > FIFO_SIZE)
+ // We can't wrap around while the GPU is working on the data.
+ // This should be very rare due to the reset in SyncGPU.
+ SyncGPU(SYNC_GPU_WRAPAROUND);
+ if (g_video_buffer_pp_read_ptr != g_video_buffer_read_ptr)
{
- PanicAlert("FIFO out of bounds (size = %i, len = %i at %08x)", size, len, pos);
+ PanicAlert("desynced read pointers");
+ return;
+ }
+ write_ptr = s_video_buffer_write_ptr;
+ size_t size = write_ptr - g_video_buffer_pp_read_ptr;
+ if (len > FIFO_SIZE - size)
+ {
+ PanicAlert("FIFO out of bounds (existing %lu + new %lu > %lu)", (unsigned long) size, (unsigned long) len, (unsigned long) FIFO_SIZE);
+ return;
}
- memmove(&videoBuffer[0], &videoBuffer[pos], size);
- g_pVideoData = videoBuffer;
}
- // Copy new video instructions to videoBuffer for future use in rendering the new picture
- memcpy(videoBuffer + size, _uData, len);
- size += len;
+ memcpy(write_ptr, _uData, len);
+ OpcodeDecoder_Preprocess(write_ptr + len);
+ // This would have to be locked if the GPU thread didn't spin.
+ s_video_buffer_write_ptr = write_ptr + len;
}
void ResetVideoBuffer()
{
- g_pVideoData = videoBuffer;
- size = 0;
+ g_video_buffer_read_ptr = s_video_buffer;
+ s_video_buffer_write_ptr = s_video_buffer;
+ s_video_buffer_seen_ptr = s_video_buffer;
+ g_video_buffer_pp_read_ptr = s_video_buffer;
+ s_fifo_aux_write_ptr = s_fifo_aux_data;
+ s_fifo_aux_read_ptr = s_fifo_aux_data;
}
@@ -148,53 +279,75 @@ void RunGpuLoop()
g_video_backend->PeekMessages();
VideoFifo_CheckAsyncRequest();
+ if (g_use_deterministic_gpu_thread)
+ {
+ // All the fifo/CP stuff is on the CPU. We just need to run the opcode decoder.
+ u8* seen_ptr = s_video_buffer_seen_ptr;
+ u8* write_ptr = s_video_buffer_write_ptr;
+ // See comment in SyncGPU
+ if (write_ptr > seen_ptr)
+ {
+ OpcodeDecoder_Run(write_ptr);
- CommandProcessor::SetCPStatusFromGPU();
-
- Common::AtomicStore(CommandProcessor::VITicks, CommandProcessor::m_cpClockOrigin);
-
- // check if we are able to run this buffer
- while (GpuRunningState && EmuRunningState && !CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint())
+ {
+ std::lock_guard<std::mutex> vblk(s_video_buffer_lock);
+ s_video_buffer_seen_ptr = write_ptr;
+ s_video_buffer_cond.notify_all();
+ }
+ }
+ }
+ else
{
- fifo.isGpuReadingData = true;
- CommandProcessor::isPossibleWaitingSetDrawDone = fifo.bFF_GPLinkEnable ? true : false;
+ CommandProcessor::SetCPStatusFromGPU();
- if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU || Common::AtomicLoad(CommandProcessor::VITicks) > CommandProcessor::m_cpClockOrigin)
+ Common::AtomicStore(CommandProcessor::VITicks, CommandProcessor::m_cpClockOrigin);
+
+ // check if we are able to run this buffer
+ while (GpuRunningState && EmuRunningState && !CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint())
{
- u32 readPtr = fifo.CPReadPointer;
- u8 *uData = Memory::GetPointer(readPtr);
+ fifo.isGpuReadingData = true;
+ CommandProcessor::isPossibleWaitingSetDrawDone = fifo.bFF_GPLinkEnable ? true : false;
- if (readPtr == fifo.CPEnd)
- readPtr = fifo.CPBase;
- else
- readPtr += 32;
+ if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU || Common::AtomicLoad(CommandProcessor::VITicks) > CommandProcessor::m_cpClockOrigin)
+ {
+ u32 readPtr = fifo.CPReadPointer;
+ u8 *uData = Memory::GetPointer(readPtr);
- _assert_msg_(COMMANDPROCESSOR, (s32)fifo.CPReadWriteDistance - 32 >= 0 ,
- "Negative fifo.CPReadWriteDistance = %i in FIFO Loop !\nThat can produce instability in the game. Please report it.", fifo.CPReadWriteDistance - 32);
+ if (readPtr == fifo.CPEnd)
+ readPtr = fifo.CPBase;
+ else
+ readPtr += 32;
- ReadDataFromFifo(uData, 32);
+ _assert_msg_(COMMANDPROCESSOR, (s32)fifo.CPReadWriteDistance - 32 >= 0 ,
+ "Negative fifo.CPReadWriteDistance = %i in FIFO Loop !\nThat can produce instability in the game. Please report it.", fifo.CPReadWriteDistance - 32);
- cyclesExecuted = OpcodeDecoder_Run(GetVideoBufferEndPtr());
+ ReadDataFromFifo(uData, 32);
- if (SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU && Common::AtomicLoad(CommandProcessor::VITicks) >= cyclesExecuted)
- Common::AtomicAdd(CommandProcessor::VITicks, -(s32)cyclesExecuted);
+ u8* write_ptr = s_video_buffer_write_ptr;
- Common::AtomicStore(fifo.CPReadPointer, readPtr);
- Common::AtomicAdd(fifo.CPReadWriteDistance, -32);
- if ((GetVideoBufferEndPtr() - g_pVideoData) == 0)
- Common::AtomicStore(fifo.SafeCPReadPointer, fifo.CPReadPointer);
- }
+ cyclesExecuted = OpcodeDecoder_Run(write_ptr);
- CommandProcessor::SetCPStatusFromGPU();
- // This call is pretty important in DualCore mode and must be called in the FIFO Loop.
- // If we don't, s_swapRequested or s_efbAccessRequested won't be set to false
- // leading the CPU thread to wait in Video_BeginField or Video_AccessEFB thus slowing things down.
- VideoFifo_CheckAsyncRequest();
- CommandProcessor::isPossibleWaitingSetDrawDone = false;
- }
+ if (SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU && Common::AtomicLoad(CommandProcessor::VITicks) >= cyclesExecuted)
+ Common::AtomicAdd(CommandProcessor::VITicks, -(s32)cyclesExecuted);
+
+ Common::AtomicStore(fifo.CPReadPointer, readPtr);
+ Common::AtomicAdd(fifo.CPReadWriteDistance, -32);
+ if ((write_ptr - g_video_buffer_read_ptr) == 0)
+ Common::AtomicStore(fifo.SafeCPReadPointer, fifo.CPReadPointer);
+ }
+
+ CommandProcessor::SetCPStatusFromGPU();
- fifo.isGpuReadingData = false;
+ // This call is pretty important in DualCore mode and must be called in the FIFO Loop.
+ // If we don't, s_swapRequested or s_efbAccessRequested won't be set to false
+ // leading the CPU thread to wait in Video_BeginField or Video_AccessEFB thus slowing things down.
+ VideoFifo_CheckAsyncRequest();
+ CommandProcessor::isPossibleWaitingSetDrawDone = false;
+ }
+
+ fifo.isGpuReadingData = false;
+ }
if (EmuRunningState)
{
@@ -217,6 +370,8 @@ void RunGpuLoop()
}
}
}
+ // wake up SyncGPU if we were interrupted
+ s_video_buffer_cond.notify_all();
}
@@ -228,16 +383,27 @@ bool AtBreakpoint()
void RunGpu()
{
+ if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread &&
+ !g_use_deterministic_gpu_thread)
+ return;
+
SCPFifoStruct &fifo = CommandProcessor::fifo;
while (fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint() )
{
u8 *uData = Memory::GetPointer(fifo.CPReadPointer);
- FPURoundMode::SaveSIMDState();
- FPURoundMode::LoadDefaultSIMDState();
- ReadDataFromFifo(uData, 32);
- OpcodeDecoder_Run(GetVideoBufferEndPtr());
- FPURoundMode::LoadSIMDState();
+ if (g_use_deterministic_gpu_thread)
+ {
+ ReadDataFromFifoOnCPU(uData, 32);
+ }
+ else
+ {
+ FPURoundMode::SaveSIMDState();
+ FPURoundMode::LoadDefaultSIMDState();
+ ReadDataFromFifo(uData, 32);
+ OpcodeDecoder_Run(s_video_buffer_write_ptr);
+ FPURoundMode::LoadSIMDState();
+ }
//DEBUG_LOG(COMMANDPROCESSOR, "Fifo wraps to base");
@@ -250,3 +416,45 @@ void RunGpu()
}
CommandProcessor::SetCPStatusFromGPU();
}
+
+void Fifo_UpdateWantDeterminism(bool want)
+{
+ // We are paused (or not running at all yet) and have m_csHWVidOccupied, so
+ // it should be safe to change this.
+ const SCoreStartupParameter& param = SConfig::GetInstance().m_LocalCoreStartupParameter;
+ bool gpu_thread;
+ switch (param.m_GPUDeterminismMode)
+ {
+ case GPU_DETERMINISM_AUTO:
+ gpu_thread = want;
+
+ // Hack: For now movies are an exception to this being on (but not
+ // to wanting determinism in general). Once vertex arrays are
+ // fixed, there should be no reason to want this off for movies by
+ // default, so this can be removed.
+ if (!NetPlay::IsNetPlayRunning())
+ gpu_thread = false;
+
+ break;
+ case GPU_DETERMINISM_NONE:
+ gpu_thread = false;
+ break;
+ case GPU_DETERMINISM_FAKE_COMPLETION:
+ gpu_thread = true;
+ break;
+ }
+
+ gpu_thread = gpu_thread && SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread;
+
+ if (g_use_deterministic_gpu_thread != gpu_thread)
+ {
+ g_use_deterministic_gpu_thread = gpu_thread;
+ if (gpu_thread)
+ {
+ // These haven't been updated in non-deterministic mode.
+ s_video_buffer_seen_ptr = g_video_buffer_pp_read_ptr = g_video_buffer_read_ptr;
+ CopyPreprocessCPStateFromMain();
+ VertexLoaderManager::MarkAllDirty();
+ }
+ }
+}