Add Yield/SwitchToThread in SNES IPCRingBuffer. Improves performance on Core 2 Duo, about the same on i5, and will prevent it from going < 1 fps on a single core machine :).

This commit is contained in:
jdpurcell 2015-02-04 04:31:41 +00:00
parent 60ed815b68
commit 2f5a7543ca
4 changed files with 100 additions and 95 deletions

View File

@ -4,13 +4,14 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
namespace BizHawk.Common
{
/// <summary>
/// a ring buffer suitable for IPC. It uses a spinlock to control access, so overhead can be kept to a minimum.
/// you'll probably need to use this in pairs, so it will occupy two threads and degrade entirely if there is less than one processor.
/// you'll probably need to use this in pairs, so it will occupy two threads.
/// </summary>
public unsafe class IPCRingBuffer : IDisposable
{
@ -80,6 +81,7 @@ namespace BizHawk.Common
if (Available > amt)
return;
//this is a greedy spinlock.
Thread.Yield();
}
}
@ -119,6 +121,7 @@ namespace BizHawk.Common
if (available > 0)
return available;
//this is a greedy spinlock.
Thread.Yield();
}
}

View File

@ -231,6 +231,7 @@ private:
if (Available() > amt)
return;
//this is a greedy spinlock.
SwitchToThread();
}
}
@ -284,6 +285,7 @@ public:
if (available > 0)
return available;
//this is a greedy spinlock.
SwitchToThread();
//NOTE: it's annoying right now because libsnes processes die and eat a whole core.
//we need to gracefully exit somehow
}
@ -504,71 +506,71 @@ Blob ReadPipeBlob()
return ret;
}
void snes_video_refresh(const uint32_t *data, unsigned width, unsigned height)
{
void snes_video_refresh(const uint32_t *data, unsigned width, unsigned height)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_video_refresh;
s_EmulationControl.cb_video_refresh_params.data = data;
s_EmulationControl.cb_video_refresh_params.width = width;
s_EmulationControl.cb_video_refresh_params.height = height;
SETCONTROL;
}
bool audio_en = false;
static const int AUDIOBUFFER_SIZE = 44100*2;
uint16_t audiobuffer[AUDIOBUFFER_SIZE];
int audiobuffer_idx = 0;
void SIG_FlushAudio()
{
SETCONTROL;
}
bool audio_en = false;
static const int AUDIOBUFFER_SIZE = 44100*2;
uint16_t audiobuffer[AUDIOBUFFER_SIZE];
int audiobuffer_idx = 0;
void SIG_FlushAudio()
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_audio_flush;
SETCONTROL;
}
//this is the raw callback from the emulator internals when a new audio sample is available
void snes_audio_sample(uint16_t left, uint16_t right)
{
if(!audio_en) return;
//if theres no room in the audio buffer, we need to send a flush signal
if(audiobuffer_idx == AUDIOBUFFER_SIZE)
SIG_FlushAudio();
audiobuffer[audiobuffer_idx++] = left;
}
//this is the raw callback from the emulator internals when a new audio sample is available
void snes_audio_sample(uint16_t left, uint16_t right)
{
if(!audio_en) return;
//if theres no room in the audio buffer, we need to send a flush signal
if(audiobuffer_idx == AUDIOBUFFER_SIZE)
SIG_FlushAudio();
audiobuffer[audiobuffer_idx++] = left;
audiobuffer[audiobuffer_idx++] = right;
}
void snes_input_poll(void)
{
}
void snes_input_poll(void)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_poll;
SETCONTROL;
}
int16_t snes_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
{
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_poll;
SETCONTROL;
}
int16_t snes_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_state;
s_EmulationControl.cb_input_state_params.port = port;
s_EmulationControl.cb_input_state_params.device = device;
s_EmulationControl.cb_input_state_params.index = index;
s_EmulationControl.cb_input_state_params.id = id;
SETCONTROL;
return s_EmulationControl.cb_input_state_params.result;
}
void snes_input_notify(int index)
{
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_state;
s_EmulationControl.cb_input_state_params.port = port;
s_EmulationControl.cb_input_state_params.device = device;
s_EmulationControl.cb_input_state_params.index = index;
s_EmulationControl.cb_input_state_params.id = id;
SETCONTROL;
return s_EmulationControl.cb_input_state_params.result;
}
void snes_input_notify(int index)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_notify;
s_EmulationControl.cb_input_notify_params.index = index;
SETCONTROL;
}
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_notify;
s_EmulationControl.cb_input_notify_params.index = index;
SETCONTROL;
}
void snes_trace(const char *msg)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_trace;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_trace;
s_EmulationControl.cb_trace_params.msg = msg;
SETCONTROL;
}
@ -606,24 +608,24 @@ public:
static std::map<void*,SharedMemoryBlock*> memHandleTable;
void* implementation_snes_allocSharedMemory()
{
const char* memtype = s_EmulationControl.cb_allocSharedMemory_params.memtype;
size_t amt = s_EmulationControl.cb_allocSharedMemory_params.amt;
if(!running)
{
s_EmulationControl.cb_allocSharedMemory_params.result = NULL;
return NULL;
}
//printf("WritePipe(eMessage_SIG_allocSharedMemory)\n");
WritePipe(eMessage_SIG_allocSharedMemory);
WritePipeString(memtype);
WritePipe(amt);
std::string blockname = ReadPipeString();
void* implementation_snes_allocSharedMemory()
{
const char* memtype = s_EmulationControl.cb_allocSharedMemory_params.memtype;
size_t amt = s_EmulationControl.cb_allocSharedMemory_params.amt;
if(!running)
{
s_EmulationControl.cb_allocSharedMemory_params.result = NULL;
return NULL;
}
//printf("WritePipe(eMessage_SIG_allocSharedMemory)\n");
WritePipe(eMessage_SIG_allocSharedMemory);
WritePipeString(memtype);
WritePipe(amt);
std::string blockname = ReadPipeString();
auto mapfile = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, blockname.c_str());
if(mapfile == INVALID_HANDLE_VALUE)
@ -636,36 +638,36 @@ void* implementation_snes_allocSharedMemory()
smb->handle = mapfile;
memHandleTable[ptr] = smb;
s_EmulationControl.cb_allocSharedMemory_params.result = ptr;
s_EmulationControl.cb_allocSharedMemory_params.result = ptr;
}
void* snes_allocSharedMemory(const char* memtype, size_t amt)
{
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
if(!running) return NULL;
void* snes_allocSharedMemory(const char* memtype, size_t amt)
{
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
if(!running) return NULL;
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_allocSharedMemory;
s_EmulationControl.cb_allocSharedMemory_params.memtype = memtype;
s_EmulationControl.cb_allocSharedMemory_params.amt = amt;
SETCONTROL;
return s_EmulationControl.cb_allocSharedMemory_params.result;
}
void implementation_snes_freeSharedMemory()
{
void* ptr = s_EmulationControl.cb_freeSharedMemory_params.ptr;
return s_EmulationControl.cb_allocSharedMemory_params.result;
}
void implementation_snes_freeSharedMemory()
{
void* ptr = s_EmulationControl.cb_freeSharedMemory_params.ptr;
if(!ptr) return;
auto smb = memHandleTable.find(ptr)->second;
UnmapViewOfFile(ptr);
CloseHandle(smb->handle);
//printf("WritePipe(eMessage_SIG_freeSharedMemory);\n");
WritePipe(eMessage_SIG_freeSharedMemory);
WritePipeString(smb->memtype.c_str());
}
WritePipe(eMessage_SIG_freeSharedMemory);
WritePipeString(smb->memtype.c_str());
}
void snes_freeSharedMemory(void* ptr)
{
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
@ -997,7 +999,7 @@ TOP:
break;
}
case eEmulationCallback_snes_audio_flush:
Handle_SIG_audio_flush();
Handle_SIG_audio_flush();
break;
case eEmulationCallback_snes_input_poll:
WritePipe(eMessage_SIG_input_poll);
@ -1203,13 +1205,13 @@ void emuthread()
//EDIT - well, we changed that, but.. we still want this probably, for debugging and stuff
for(;;)
{
SNES::scheduler.sync = SNES::Scheduler::SynchronizeMode::None;
SNES::scheduler.clearExitReason();
SNES::scheduler.enter();
if(SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::FrameEvent)
{
SNES::video.update();
break;
SNES::scheduler.sync = SNES::Scheduler::SynchronizeMode::None;
SNES::scheduler.clearExitReason();
SNES::scheduler.enter();
if(SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::FrameEvent)
{
SNES::video.update();
break;
}
//not used yet
if(SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::DebuggerEvent)
@ -1273,7 +1275,7 @@ int xmain(int argc, char** argv)
printf("running\n");
RunControlMessageLoop();
return 0;
}