for evaluation:

overhaul snes core -- radically simplify IPC.

There were two goals
1. speedup (failed)
2. not break in the old way of becoming unstable when GC happens (uncertain)
3. lay framework for simplified libretro interface (success, provisional on )

the speedup failed, I think, because a slower libco is needed to coexist with .net. I think I will fix this be incorporating both libcos, one for bsnes internal use, and one for .net<->c++ switching
This commit is contained in:
zeromus 2017-03-06 03:21:10 -06:00
parent 3bb1d0849d
commit fbd7601b77
19 changed files with 1006 additions and 1958 deletions

View File

@ -35,8 +35,6 @@
this.label2 = new System.Windows.Forms.Label();
this.rbAccuracy = new System.Windows.Forms.RadioButton();
this.rbPerformance = new System.Windows.Forms.RadioButton();
this.cbRingbuf = new System.Windows.Forms.CheckBox();
this.label1 = new System.Windows.Forms.Label();
this.cbDoubleSize = new System.Windows.Forms.CheckBox();
this.lblDoubleSize = new System.Windows.Forms.Label();
this.cbForceDeterminism = new System.Windows.Forms.CheckBox();
@ -47,7 +45,7 @@
// btnOk
//
this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnOk.Location = new System.Drawing.Point(136, 344);
this.btnOk.Location = new System.Drawing.Point(136, 286);
this.btnOk.Name = "btnOk";
this.btnOk.Size = new System.Drawing.Size(75, 23);
this.btnOk.TabIndex = 0;
@ -59,7 +57,7 @@
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(217, 344);
this.btnCancel.Location = new System.Drawing.Point(217, 286);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 1;
@ -123,28 +121,10 @@
this.rbPerformance.Text = "Performance (only for casual gaming!)";
this.rbPerformance.UseVisualStyleBackColor = true;
//
// cbRingbuf
//
this.cbRingbuf.AutoSize = true;
this.cbRingbuf.Location = new System.Drawing.Point(19, 134);
this.cbRingbuf.Name = "cbRingbuf";
this.cbRingbuf.Size = new System.Drawing.Size(115, 17);
this.cbRingbuf.TabIndex = 4;
this.cbRingbuf.Text = "Use Ring Buffer IO";
this.cbRingbuf.UseVisualStyleBackColor = true;
//
// label1
//
this.label1.Location = new System.Drawing.Point(35, 154);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(254, 45);
this.label1.TabIndex = 5;
this.label1.Text = "Performance-affecting option; results differ for different users\' systems.";
//
// cbDoubleSize
//
this.cbDoubleSize.AutoSize = true;
this.cbDoubleSize.Location = new System.Drawing.Point(18, 189);
this.cbDoubleSize.Location = new System.Drawing.Point(18, 130);
this.cbDoubleSize.Name = "cbDoubleSize";
this.cbDoubleSize.Size = new System.Drawing.Size(178, 17);
this.cbDoubleSize.TabIndex = 6;
@ -154,7 +134,7 @@
//
// lblDoubleSize
//
this.lblDoubleSize.Location = new System.Drawing.Point(36, 210);
this.lblDoubleSize.Location = new System.Drawing.Point(36, 151);
this.lblDoubleSize.Name = "lblDoubleSize";
this.lblDoubleSize.Size = new System.Drawing.Size(254, 57);
this.lblDoubleSize.TabIndex = 7;
@ -165,7 +145,7 @@
// cbForceDeterminism
//
this.cbForceDeterminism.AutoSize = true;
this.cbForceDeterminism.Location = new System.Drawing.Point(19, 271);
this.cbForceDeterminism.Location = new System.Drawing.Point(19, 212);
this.cbForceDeterminism.Name = "cbForceDeterminism";
this.cbForceDeterminism.Size = new System.Drawing.Size(113, 17);
this.cbForceDeterminism.TabIndex = 8;
@ -175,7 +155,7 @@
//
// label3
//
this.label3.Location = new System.Drawing.Point(38, 295);
this.label3.Location = new System.Drawing.Point(38, 236);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(251, 41);
this.label3.TabIndex = 9;
@ -188,13 +168,11 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(304, 379);
this.ClientSize = new System.Drawing.Size(304, 321);
this.Controls.Add(this.label3);
this.Controls.Add(this.cbForceDeterminism);
this.Controls.Add(this.lblDoubleSize);
this.Controls.Add(this.cbDoubleSize);
this.Controls.Add(this.label1);
this.Controls.Add(this.cbRingbuf);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnOk);
@ -220,8 +198,6 @@
private System.Windows.Forms.RadioButton rbCompatibility;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.RadioButton rbPerformance;
private System.Windows.Forms.CheckBox cbRingbuf;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox cbDoubleSize;
private System.Windows.Forms.Label lblDoubleSize;
private System.Windows.Forms.RadioButton rbAccuracy;

View File

@ -21,7 +21,6 @@ namespace BizHawk.Client.EmuHawk
var ss = ((LibsnesCore)Global.Emulator).GetSyncSettings();
var dlg = new SNESOptions
{
UseRingBuffer = s.UseRingBuffer,
AlwaysDoubleSize = s.AlwaysDoubleSize,
ForceDeterminism = s.ForceDeterminism,
Profile = ss.Profile
@ -30,7 +29,6 @@ namespace BizHawk.Client.EmuHawk
var result = dlg.ShowDialog(owner);
if (result == DialogResult.OK)
{
s.UseRingBuffer = dlg.UseRingBuffer;
s.AlwaysDoubleSize = dlg.AlwaysDoubleSize;
s.ForceDeterminism = dlg.ForceDeterminism;
ss.Profile = dlg.Profile;
@ -62,12 +60,6 @@ namespace BizHawk.Client.EmuHawk
}
}
public bool UseRingBuffer
{
get { return cbRingbuf.Checked; }
set { cbRingbuf.Checked = value; }
}
public bool AlwaysDoubleSize
{
get { return UserDoubleSizeOption; }

View File

@ -1,286 +0,0 @@
using System;
using System.Runtime.InteropServices; //I know.... the p/invoke in here. Lets get rid of it
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.
/// </summary>
public unsafe class IPCRingBuffer : IDisposable
{
MemoryMappedFile mmf;
MemoryMappedViewAccessor mmva;
byte* mmvaPtr;
volatile byte* begin;
volatile int* head, tail;
int bufsize;
public string Id;
public bool Owner;
/// <summary>
/// note that a few bytes of the size will be used for a management area
/// </summary>
/// <param name="size"></param>
public void Allocate(int size)
{
Owner = true;
Id = SuperGloballyUniqueID.Next();
mmf = MemoryMappedFile.CreateNew(Id, size);
Setup(size);
}
public void Open(string id)
{
Id = id;
mmf = MemoryMappedFile.OpenExisting(id);
Setup(-1);
}
void Setup(int size)
{
bool init = size != -1;
mmva = mmf.CreateViewAccessor();
byte* tempPtr = null;
mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref tempPtr);
mmvaPtr = tempPtr;
//setup management area
head = (int*)mmvaPtr;
tail = (int*)mmvaPtr + 1;
int* bufsizeptr = (int*)mmvaPtr + 2;
begin = mmvaPtr + 12;
if (init)
*bufsizeptr = bufsize = size - 12;
else bufsize = *bufsizeptr;
}
public void Dispose()
{
if (mmf == null) return;
mmva.Dispose();
mmf.Dispose();
mmf = null;
}
void WaitForWriteCapacity(int amt)
{
for (; ; )
{
//dont return when available == amt because then we would consume the buffer and be unable to distinguish between full and empty
if (Available > amt)
return;
//this is a greedy spinlock.
Thread.Yield();
}
}
public int Available
{
get
{
return bufsize - Size;
}
}
public int Size
{
get
{
int h = *head;
int t = *tail;
int size = h - t;
if (size < 0) size += bufsize;
else if (size >= bufsize)
{
//shouldnt be possible for size to be anything but bufsize here, but just in case...
if (size > bufsize)
throw new InvalidOperationException("Critical error code pickpocket panda! This MUST be reported to the developers!");
size = 0;
}
return size;
}
}
int WaitForSomethingToRead()
{
for (; ; )
{
int available = Size;
if (available > 0)
return available;
//this is a greedy spinlock.
Thread.Yield();
}
}
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
public void Write(IntPtr ptr, int amt)
{
byte* bptr = (byte*)ptr;
int ofs = 0;
while (amt > 0)
{
int todo = amt;
//make sure we don't write a big chunk beyond the end of the buffer
int remain = bufsize - *head;
if (todo > remain) todo = remain;
//dont request the entire buffer. we would never get that much available, because we never completely fill up the buffer
if (todo > bufsize - 1) todo = bufsize - 1;
//a super efficient approach would chunk this several times maybe instead of waiting for the buffer to be emptied before writing again. but who cares
WaitForWriteCapacity(todo);
//messages are likely to be small. we should probably just loop to copy in here. but for now..
CopyMemory(begin + *head, bptr + ofs, (ulong)todo);
amt -= todo;
ofs += todo;
*head += todo;
if (*head >= bufsize) *head -= bufsize;
}
}
public void Read(IntPtr ptr, int amt)
{
byte* bptr = (byte*)ptr;
int ofs = 0;
while (amt > 0)
{
int available = WaitForSomethingToRead();
int todo = amt;
if (todo > available) todo = available;
//make sure we don't read a big chunk beyond the end of the buffer
int remain = bufsize - *tail;
if (todo > remain) todo = remain;
//messages are likely to be small. we should probably just loop to copy in here. but for now..
CopyMemory(bptr + ofs, begin + *tail, (ulong)todo);
amt -= todo;
ofs += todo;
*tail += todo;
if (*tail >= bufsize) *tail -= bufsize;
}
}
public class Tester
{
private readonly Queue<byte> shazam = new Queue<byte>();
string bufid;
unsafe void a()
{
var buf = new IPCRingBuffer();
buf.Allocate(1024);
bufid = buf.Id;
int ctr = 0;
for (; ; )
{
Random r = new Random(ctr);
ctr++;
Console.WriteLine("Writing: {0}", ctr);
byte[] temp = new byte[r.Next(2048) + 1];
r.NextBytes(temp);
for (int i = 0; i < temp.Length; i++)
lock (shazam) shazam.Enqueue(temp[i]);
fixed (byte* tempptr = &temp[0])
buf.Write((IntPtr)tempptr, temp.Length);
//Console.WriteLine("wrote {0}; ringbufsize={1}", temp.Length, buf.Size);
}
}
unsafe void b()
{
var buf = new IPCRingBuffer();
buf.Open(bufid);
int ctr = 0;
for (; ; )
{
Random r = new Random(ctr + 1000);
ctr++;
Console.WriteLine("Reading : {0}", ctr);
int tryRead = r.Next(2048) + 1;
byte[] temp = new byte[tryRead];
fixed (byte* tempptr = &temp[0])
buf.Read((IntPtr)tempptr, tryRead);
//Console.WriteLine("read {0}; ringbufsize={1}", temp.Length, buf.Size);
for (int i = 0; i < temp.Length; i++)
{
byte b;
lock (shazam) b = shazam.Dequeue();
Debug.Assert(b == temp[i]);
}
}
}
public void Test()
{
var ta = new System.Threading.Thread(a);
var tb = new System.Threading.Thread(b);
ta.Start();
while (bufid == null) { }
tb.Start();
}
}
} //class IPCRingBuffer
/// <summary>
/// A stream on top of an IPCRingBuffer
/// </summary>
public unsafe class IPCRingBufferStream : Stream
{
private readonly IPCRingBuffer buf;
public IPCRingBufferStream(IPCRingBuffer buf)
{
this.buf = buf;
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return true; } }
public override void Flush() { }
public override long Length { get { throw new NotImplementedException(); } }
public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
public override void SetLength(long value) { throw new NotImplementedException(); }
public override int Read(byte[] buffer, int offset, int count)
{
if (buffer.Length < offset + count) throw new IndexOutOfRangeException();
if (buffer.Length != 0)
fixed (byte* pbuffer = &buffer[offset])
buf.Read((IntPtr)pbuffer, count);
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer.Length < offset + count) throw new IndexOutOfRangeException();
if (buffer.Length != 0)
fixed (byte* pbuffer = &buffer[offset])
buf.Write((IntPtr)pbuffer, count);
}
} //class IPCRingBufferStream
} //namespace BizHawk.Common

View File

@ -22,34 +22,33 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
// this is a lot of work, but it will be some decent speedups. who wouldve ever thought to make an emulator this way? I will, from now on...
//todo - use a reader/writer ring buffer for communication instead of pipe
//todo - when exe wrapper is fully baked, put it into mingw so we can just have libsneshawk.exe without a separate dll. it hardly needs any debugging presently, it should be easy to maintain.
//space optimizations to deploy later (only if people complain about so many files)
//todo - put executables in zipfiles and search for them there; dearchive to a .cache folder. check timestamps to know when to freshen. this is weird.....
//speedups to deploy later:
//todo - convey rom data faster than pipe blob (use shared memory) (WARNING: right now our general purpose shared memory is only 1MB. maybe wait until ring buffer IPC)
//todo - collapse input messages to one IPC operation. right now theres like 30 of them
//todo - collect all memory block names whenever a memory block is alloc/dealloced. that way we avoid the overhead when using them for gui stuff (gfx debugger, hex editor)
InstanceDll instanceDll;
string InstanceName;
NamedPipeServerStream pipe;
BinaryWriter bwPipe;
BinaryReader brPipe;
MemoryMappedFile mmf;
MemoryMappedViewAccessor mmva;
byte* mmvaPtr;
IPCRingBuffer rbuf, wbuf;
IPCRingBufferStream rbufstr, wbufstr;
SwitcherStream rstream, wstream;
bool bufio;
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void DllInit(string ipcname);
delegate CommStruct* DllInit();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void MessageApi(eMessage msg);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void BufferApi(int id, void* ptr, int size);
CommStruct* comm;
MessageApi Message;
BufferApi CopyBuffer; //TODO: consider making private and wrapping
BufferApi SetBuffer; //TODO: consider making private and wrapping
public LibsnesApi(string dllPath)
{
@ -57,147 +56,53 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
var pipeName = InstanceName;
mmf = MemoryMappedFile.CreateNew(pipeName, 1024 * 1024);
mmva = mmf.CreateViewAccessor();
mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref mmvaPtr);
pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 1024 * 1024, 1024);
instanceDll = new InstanceDll(dllPath);
var dllinit = (DllInit)Marshal.GetDelegateForFunctionPointer(instanceDll.GetProcAddress("DllInit"), typeof(DllInit));
dllinit(pipeName);
Message = (MessageApi)Marshal.GetDelegateForFunctionPointer(instanceDll.GetProcAddress("Message"), typeof(MessageApi));
CopyBuffer = (BufferApi)Marshal.GetDelegateForFunctionPointer(instanceDll.GetProcAddress("CopyBuffer"), typeof(BufferApi));
SetBuffer = (BufferApi)Marshal.GetDelegateForFunctionPointer(instanceDll.GetProcAddress("SetBuffer"), typeof(BufferApi));
//TODO - start a thread to wait for process to exit and gracefully handle errors? how about the pipe?
pipe.WaitForConnection();
rbuf = new IPCRingBuffer();
wbuf = new IPCRingBuffer();
rbuf.Allocate(1024);
wbuf.Allocate(1024);
rbufstr = new IPCRingBufferStream(rbuf);
wbufstr = new IPCRingBufferStream(wbuf);
rstream = new SwitcherStream();
wstream = new SwitcherStream();
rstream.SetCurrStream(pipe);
wstream.SetCurrStream(pipe);
brPipe = new BinaryReader(rstream);
bwPipe = new BinaryWriter(wstream);
WritePipeMessage(eMessage.eMessage_SetBuffer);
bwPipe.Write(1);
WritePipeString(rbuf.Id);
WritePipeMessage(eMessage.eMessage_SetBuffer);
bwPipe.Write(0);
WritePipeString(wbuf.Id);
bwPipe.Flush();
comm = dllinit();
}
public void Dispose()
{
WritePipeMessage(eMessage.eMessage_Shutdown);
WaitForCompletion();
instanceDll.Dispose();
pipe.Dispose();
mmva.Dispose();
mmf.Dispose();
rbuf.Dispose();
wbuf.Dispose();
foreach (var smb in DeallocatedMemoryBlocks.Values)
smb.Dispose();
DeallocatedMemoryBlocks.Clear();
}
public void BeginBufferIO()
public void CopyString(string str)
{
bufio = true;
WritePipeMessage(eMessage.eMessage_BeginBufferIO);
rstream.SetCurrStream(rbufstr);
wstream.SetCurrStream(wbufstr);
fixed (char* cp = str)
CopyBuffer(0, cp, str.Length + 1);
}
public void EndBufferIO()
public void CopyBytes(byte[] bytes)
{
if(!bufio) return;
bufio = false;
WritePipeMessage(eMessage.eMessage_EndBufferIO);
rstream.SetCurrStream(pipe);
wstream.SetCurrStream(pipe);
fixed (byte* bp = bytes)
CopyBuffer(0, bp, bytes.Length);
}
void WritePipeString(string str)
public void SetAscii(string str)
{
WritePipeBlob(System.Text.Encoding.ASCII.GetBytes(str));
fixed (char* cp = str)
SetBuffer(0, cp, str.Length + 1);
}
byte[] ReadPipeBlob()
public void SetBytes(byte[] bytes)
{
int len = brPipe.ReadInt32();
var ret = new byte[len];
brPipe.Read(ret, 0, len);
return ret;
fixed (byte* bp = bytes)
SetBuffer(0, bp, bytes.Length);
}
void WritePipeBlob(byte[] blob)
public void SetBytes2(byte[] bytes)
{
bwPipe.Write(blob.Length);
bwPipe.Write(blob);
bwPipe.Flush();
fixed (byte* bp = bytes)
SetBuffer(1, bp, bytes.Length);
}
public int MessageCounter;
void WritePipeInt(int n)
{
}
void WritePipePointer(IntPtr ptr, bool flush = true)
{
bwPipe.Write(ptr.ToInt32());
if(flush) bwPipe.Flush();
}
void WritePipeMessage(eMessage msg)
{
if(!bufio) MessageCounter++;
//Console.WriteLine("write pipe message: " + msg);
bwPipe.Write((int)msg);
bwPipe.Flush();
}
eMessage ReadPipeMessage()
{
return (eMessage)brPipe.ReadInt32();
}
string ReadPipeString()
{
int len = brPipe.ReadInt32();
var bytes = brPipe.ReadBytes(len);
return System.Text.ASCIIEncoding.ASCII.GetString(bytes);
}
void WaitForCompletion()
{
for (; ; )
{
var msg = ReadPipeMessage();
if (!bufio) MessageCounter++;
//Console.WriteLine("read pipe message: " + msg);
if (msg == eMessage.eMessage_BRK_Complete)
return;
//this approach is slower than having one big case. but, its easier to manage. once the code is stable, someone could clean it up (probably creating a delegate table would be best)
if (Handle_SIG(msg)) continue;
if (Handle_BRK(msg)) continue;
}
}
public Action<uint> ReadHook, ExecHook;
public Action<uint, byte> WriteHook;
@ -243,9 +148,98 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public delegate string snes_path_request_t(int slot, string hint);
public delegate void snes_trace_t(string msg);
public void SPECIAL_Resume()
[StructLayout(LayoutKind.Sequential)]
public struct CPURegs
{
WritePipeMessage(eMessage.eMessage_ResumeAfterBRK);
public uint pc;
public ushort a, x, y, z, s, d, vector; //7x
public byte p, nothing;
public uint aa, rd;
public byte sp, dp, db, mdr;
}
[StructLayout(LayoutKind.Sequential)]
public struct LayerEnables
{
byte _BG1_Prio0, _BG1_Prio1;
byte _BG2_Prio0, _BG2_Prio1;
byte _BG3_Prio0, _BG3_Prio1;
byte _BG4_Prio0, _BG4_Prio1;
byte _Obj_Prio0, _Obj_Prio1, _Obj_Prio2, _Obj_Prio3;
public bool BG1_Prio0 { get { return _BG1_Prio0 != 0; } set { _BG1_Prio0 = (byte)(value ? 1 : 0); } }
public bool BG1_Prio1 { get { return _BG1_Prio1 != 0; } set { _BG1_Prio1 = (byte)(value ? 1 : 0); } }
public bool BG2_Prio0 { get { return _BG2_Prio0 != 0; } set { _BG2_Prio0 = (byte)(value ? 1 : 0); } }
public bool BG2_Prio1 { get { return _BG2_Prio1 != 0; } set { _BG2_Prio1 = (byte)(value ? 1 : 0); } }
public bool BG3_Prio0 { get { return _BG3_Prio0 != 0; } set { _BG3_Prio0 = (byte)(value ? 1 : 0); } }
public bool BG3_Prio1 { get { return _BG3_Prio1 != 0; } set { _BG3_Prio1 = (byte)(value ? 1 : 0); } }
public bool BG4_Prio0 { get { return _BG4_Prio0 != 0; } set { _BG4_Prio0 = (byte)(value ? 1 : 0); } }
public bool BG4_Prio1 { get { return _BG4_Prio1 != 0; } set { _BG4_Prio1 = (byte)(value ? 1 : 0); } }
public bool Obj_Prio0 { get { return _Obj_Prio0 != 0; } set { _Obj_Prio0 = (byte)(value ? 1 : 0); } }
public bool Obj_Prio1 { get { return _Obj_Prio1 != 0; } set { _Obj_Prio1 = (byte)(value ? 1 : 0); } }
public bool Obj_Prio2 { get { return _Obj_Prio2 != 0; } set { _Obj_Prio2 = (byte)(value ? 1 : 0); } }
public bool Obj_Prio3 { get { return _Obj_Prio3 != 0; } set { _Obj_Prio3 = (byte)(value ? 1 : 0); } }
}
[StructLayout(LayoutKind.Sequential)]
struct CommStruct
{
//the cmd being executed
public eMessage cmd;
//the status of the core
public eStatus status;
//the SIG or BRK that the core is halted in
public eMessage reason;
//flexible in/out parameters
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
public sbyte* str;
public void* ptr;
public uint id, addr, value, size;
public int port, device, index, slot;
public uint width, height;
public int scanline;
//this should always be used in pairs
public void* buf0, buf1;
public int buf_size0, buf_size1;
//bleck. this is a long so that it can be a 32/64bit pointer
public fixed long cdl_ptr[4];
public fixed uint cdl_size[4];
public CPURegs cpuregs;
public LayerEnables layerEnables;
//static configuration-type information which can be grabbed off the core at any time without even needing a QUERY command
public SNES_REGION region;
public SNES_MAPPER mapper;
//utilities
//TODO: make internal, wrap on the API instead of the comm
public unsafe string GetAscii() { return _getAscii(str); }
public bool GetBool() { return value != 0; }
private unsafe string _getAscii(sbyte* ptr) {
int len = 0;
sbyte* junko = (sbyte*)ptr;
while(junko[len] != 0) len++;
return new string((sbyte*)str, 0, len, System.Text.Encoding.ASCII);
}
}
public SNES_REGION Region { get { return comm->region; } }
public SNES_MAPPER Mapper { get { return comm->mapper; } }
public void SetLayerEnables(ref LayerEnables enables)
{
comm->layerEnables = enables;
QUERY_set_layer_enable();
}
}

View File

@ -15,21 +15,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
case eMessage.eMessage_BRK_hook_exec:
{
var addr = brPipe.ReadInt32();
ExecHook((uint)addr);
ExecHook(comm->addr);
break;
}
case eMessage.eMessage_BRK_hook_read:
{
var addr = brPipe.ReadInt32();
ReadHook((uint)addr);
ReadHook(comm->addr);
break;
}
case eMessage.eMessage_BRK_hook_write:
{
var addr = brPipe.ReadInt32();
var value = brPipe.ReadByte();
WriteHook((uint)addr, value);
WriteHook(comm->addr, (byte)comm->value);
break;
}
@ -40,13 +36,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
break;
case eMessage.eMessage_BRK_scanlineStart:
int line = brPipe.ReadInt32();
if (scanlineStart != null)
scanlineStart(line);
SPECIAL_Resume();
scanlineStart(comm->scanline);
break;
} //switch(msg)
Message(eMessage.eMessage_ResumeAfterBRK);
return true;
}
}

View File

@ -8,90 +8,92 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
public bool CMD_serialize(IntPtr data, int size)
{
WritePipeMessage(eMessage.eMessage_CMD_serialize);
bwPipe.Write(size);
bwPipe.Write(0); //mapped memory location to serialize to
bwPipe.Flush();
WaitForCompletion(); //serialize/unserialize can cause traces to get called (because serialize can cause execution?)
bool ret = brPipe.ReadInt32() != 0;
if (ret)
{
CopyMemory(data.ToPointer(), mmvaPtr, (ulong)size);
}
comm->buf0 = data.ToPointer();
comm->buf_size0 = size;
Message(eMessage.eMessage_CMD_serialize);
WaitForCMD();
bool ret = comm->GetBool();
return ret;
}
void WaitForCMD()
{
for (; ; )
{
if (comm->status == eStatus.eStatus_Idle)
break;
if (Handle_SIG(comm->reason)) continue;
if (Handle_BRK(comm->reason)) continue;
}
}
public bool CMD_unserialize(IntPtr data, int size)
{
WritePipeMessage(eMessage.eMessage_CMD_unserialize);
CopyMemory(mmvaPtr, data.ToPointer(), (ulong)size);
bwPipe.Write(size);
bwPipe.Write(0); //mapped memory location to serialize from
bwPipe.Flush();
WaitForCompletion(); //serialize/unserialize can cause traces to get called (because serialize can cause execution?)
bool ret = brPipe.ReadInt32() != 0;
comm->buf0 = data.ToPointer();
comm->buf_size0 = size;
Message(eMessage.eMessage_CMD_unserialize);
WaitForCMD();
bool ret = comm->GetBool();
return ret;
}
public void CMD_init()
{
WritePipeMessage(eMessage.eMessage_CMD_init);
WaitForCompletion();
Message(eMessage.eMessage_CMD_init);
WaitForCMD();
}
public void CMD_power()
{
WritePipeMessage(eMessage.eMessage_CMD_power);
WaitForCompletion();
Message(eMessage.eMessage_CMD_power);
WaitForCMD();
}
public void CMD_reset()
{
WritePipeMessage(eMessage.eMessage_CMD_reset);
WaitForCompletion();
Message(eMessage.eMessage_CMD_reset);
WaitForCMD();
}
/// <summary>
/// This is a high-level run command. It runs for one frame (right now) and blocks until the frame is done.
/// If any BRK is received, it will be handled before returning from this function.
/// </summary>
public void CMD_run()
{
WritePipeMessage(eMessage.eMessage_CMD_run);
WaitForCompletion();
Message(eMessage.eMessage_CMD_run);
WaitForCMD();
}
public bool CMD_load_cartridge_super_game_boy(string rom_xml, byte[] rom_data, uint rom_size, string dmg_xml, byte[] dmg_data, uint dmg_size)
public bool CMD_load_cartridge_super_game_boy(string rom_xml, byte[] rom_data, uint rom_size, byte[] dmg_data)
{
WritePipeMessage(eMessage.eMessage_CMD_load_cartridge_super_game_boy);
WritePipeString(rom_xml ?? "");
WritePipeBlob(rom_data);
WritePipeString(rom_xml ?? "");
WritePipeBlob(dmg_data);
//not a very obvious order.. because we do tons of work immediately after the last param goes down and need to answer messages
WaitForCompletion();
bool ret = brPipe.ReadInt32() != 0;
return ret;
fixed (char* xmlcp = rom_xml)
{
comm->str = (sbyte*)xmlcp;
SetBytes(rom_data);
SetBytes2(dmg_data);
Message(eMessage.eMessage_CMD_load_cartridge_sgb);
WaitForCMD();
return comm->GetBool();
}
}
public bool CMD_load_cartridge_normal(byte[] rom_xml, byte[] rom_data)
{
WritePipeMessage(eMessage.eMessage_CMD_load_cartridge_normal);
WritePipeBlob(rom_xml ?? new byte[0]);
WritePipeBlob(rom_data ?? new byte[0]);
//not a very obvious order.. because we do tons of work immediately after the last param goes down and need to answer messages
WaitForCompletion();
bool ret = brPipe.ReadInt32() != 0;
return ret;
string xml = rom_xml==null?null:System.Text.Encoding.ASCII.GetString(rom_xml);
fixed (char* xmlcp = xml)
{
comm->str = (sbyte*)xmlcp;
SetBytes(rom_data);
Message(eMessage.eMessage_CMD_load_cartridge_normal);
WaitForCMD();
return comm->GetBool();
}
}
public void CMD_term()
{
WritePipeMessage(eMessage.eMessage_CMD_term);
WaitForCompletion();
Message(eMessage.eMessage_CMD_term);
WaitForCMD();
}
public void CMD_unload_cartridge()
{
WritePipeMessage(eMessage.eMessage_CMD_unload_cartridge);
WaitForCompletion();
Message(eMessage.eMessage_CMD_unload_cartridge);
WaitForCMD();
}
}

View File

@ -10,24 +10,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
eMessage_NotSet,
eMessage_SetBuffer,
eMessage_BeginBufferIO,
eMessage_EndBufferIO,
eMessage_ResumeAfterBRK,
eMessage_Shutdown,
eMessage_QUERY_library_id,
eMessage_QUERY_library_revision_major,
eMessage_QUERY_library_revision_minor,
eMessage_QUERY_get_region,
eMessage_QUERY_get_mapper,
eMessage_QUERY_FIRST,
eMessage_QUERY_get_memory_size,
eMessage_QUERY_get_memory_data, //note: this function isnt used and hasnt been tested in a while
eMessage_QUERY_peek,
eMessage_QUERY_poke,
eMessage_QUERY_serialize_size,
eMessage_QUERY_poll_message,
eMessage_QUERY_dequeue_message,
eMessage_QUERY_set_color_lut,
eMessage_QUERY_GetMemoryIdName,
eMessage_QUERY_state_hook_exec,
@ -43,6 +33,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
eMessage_QUERY_peek_logical_register,
eMessage_QUERY_peek_cpu_regs,
eMessage_QUERY_set_cdl,
eMessage_QUERY_LAST,
eMessage_CMD_FIRST,
eMessage_CMD_init,
@ -52,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
eMessage_CMD_serialize,
eMessage_CMD_unserialize,
eMessage_CMD_load_cartridge_normal,
eMessage_CMD_load_cartridge_super_game_boy,
eMessage_CMD_load_cartridge_sgb,
eMessage_CMD_term,
eMessage_CMD_unload_cartridge,
eMessage_CMD_LAST,
@ -76,6 +67,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
eMessage_BRK_scanlineStart,
};
enum eStatus : int
{
eStatus_Idle,
eStatus_CMD,
eStatus_BRK
};
public enum SNES_REG : int
{
@ -203,7 +200,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
STROM = 10
}
public enum SNES_REGION : byte
public enum SNES_REGION : uint
{
NTSC = 0,
PAL = 1,

View File

@ -8,50 +8,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
unsafe partial class LibsnesApi
{
public string QUERY_library_id()
{
WritePipeMessage(eMessage.eMessage_QUERY_library_id);
return ReadPipeString();
}
public uint QUERY_library_revision_major()
{
WritePipeMessage(eMessage.eMessage_QUERY_library_revision_major);
return brPipe.ReadUInt32();
}
public uint QUERY_library_revision_minor()
{
WritePipeMessage(eMessage.eMessage_QUERY_library_revision_minor);
return brPipe.ReadUInt32();
}
public SNES_REGION QUERY_get_region()
{
WritePipeMessage(eMessage.eMessage_QUERY_get_region);
return (SNES_REGION)brPipe.ReadByte();
}
public SNES_MAPPER QUERY_get_mapper()
{
WritePipeMessage(eMessage.eMessage_QUERY_get_mapper);
return (SNES_MAPPER)brPipe.ReadByte();
}
public int QUERY_get_memory_size(SNES_MEMORY id)
{
WritePipeMessage(eMessage.eMessage_QUERY_get_memory_size);
bwPipe.Write((int)id);
bwPipe.Flush();
return brPipe.ReadInt32();
comm->value = (uint)id;
Message(eMessage.eMessage_QUERY_get_memory_size);
return (int)comm->value;
}
string QUERY_MemoryNameForId(SNES_MEMORY id)
{
WritePipeMessage(eMessage.eMessage_QUERY_GetMemoryIdName);
bwPipe.Write((uint)id);
bwPipe.Flush();
return ReadPipeString();
comm->id = (uint)id;
Message(eMessage.eMessage_QUERY_GetMemoryIdName);
return comm->GetAscii();
}
public byte* QUERY_get_memory_data(SNES_MEMORY id)
@ -64,177 +32,138 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public byte QUERY_peek(SNES_MEMORY id, uint addr)
{
WritePipeMessage(eMessage.eMessage_QUERY_peek);
bwPipe.Write((uint)id);
bwPipe.Write(addr);
bwPipe.Flush();
return brPipe.ReadByte();
comm->id = (uint)id;
comm->addr = addr;
Message(eMessage.eMessage_QUERY_peek);
return (byte)comm->value;
}
public void QUERY_poke(SNES_MEMORY id, uint addr, byte val)
{
WritePipeMessage(eMessage.eMessage_QUERY_poke);
bwPipe.Write((uint)id);
bwPipe.Write(addr);
bwPipe.Write(val);
bwPipe.Flush();
comm->id = (uint)id;
comm->addr = addr;
comm->value = (byte)val;
Message(eMessage.eMessage_QUERY_poke);
}
public int QUERY_serialize_size()
{
for (; ; )
{
WritePipeMessage(eMessage.eMessage_QUERY_serialize_size);
int ret = brPipe.ReadInt32();
Message(eMessage.eMessage_QUERY_serialize_size);
int ret = (int)comm->size;
if (ret > 100)
{
return ret;
}
else Console.WriteLine("WHY????????");
}
}
int QUERY_poll_message()
{
WritePipeMessage(eMessage.eMessage_QUERY_poll_message);
return brPipe.ReadInt32();
}
public bool QUERY_HasMessage { get { return QUERY_poll_message() != -1; } }
public string QUERY_DequeueMessage()
{
WritePipeMessage(eMessage.eMessage_QUERY_dequeue_message);
return ReadPipeString();
}
public void QUERY_set_color_lut(IntPtr colors)
{
int len = 4 * 16 * 32768;
byte[] buf = new byte[len];
Marshal.Copy(colors, buf, 0, len);
WritePipeMessage(eMessage.eMessage_QUERY_set_color_lut);
WritePipeBlob(buf);
comm->ptr = colors.ToPointer();
Message(eMessage.eMessage_QUERY_set_color_lut);
}
public void QUERY_set_state_hook_exec(bool state)
{
WritePipeMessage(eMessage.eMessage_QUERY_state_hook_exec);
bwPipe.Write(state);
comm->value = state ? 1u : 0u;
Message(eMessage.eMessage_QUERY_state_hook_exec);
}
public void QUERY_set_state_hook_read(bool state)
{
WritePipeMessage(eMessage.eMessage_QUERY_state_hook_read);
bwPipe.Write(state);
comm->value = state ? 1u : 0u;
Message(eMessage.eMessage_QUERY_state_hook_read);
}
public void QUERY_set_state_hook_write(bool state)
{
WritePipeMessage(eMessage.eMessage_QUERY_state_hook_write);
bwPipe.Write(state);
comm->value = state ? 1u : 0u;
Message(eMessage.eMessage_QUERY_state_hook_write);
}
public void QUERY_set_trace_callback(snes_trace_t callback)
{
this.traceCallback = callback;
WritePipeMessage(eMessage.eMessage_QUERY_enable_trace);
bwPipe.Write(callback != null);
bwPipe.Flush();
//TODO
//this.traceCallback = callback;
//WritePipeMessage(eMessage.eMessage_QUERY_enable_trace);
//bwPipe.Write(callback != null);
//bwPipe.Flush();
}
public void QUERY_set_scanlineStart(snes_scanlineStart_t scanlineStart)
{
this.scanlineStart = scanlineStart;
WritePipeMessage(eMessage.eMessage_QUERY_enable_scanline);
bwPipe.Write(scanlineStart != null);
bwPipe.Flush();
//TODO
//this.scanlineStart = scanlineStart;
//WritePipeMessage(eMessage.eMessage_QUERY_enable_scanline);
//bwPipe.Write(scanlineStart != null);
//bwPipe.Flush();
}
public void QUERY_set_audio_sample(snes_audio_sample_t audio_sample)
{
this.audio_sample = audio_sample;
WritePipeMessage(eMessage.eMessage_QUERY_enable_audio);
bwPipe.Write(audio_sample != null);
bwPipe.Flush();
comm->value = (audio_sample!=null) ? 1u : 0u;
Message(eMessage.eMessage_QUERY_enable_audio);
}
public void QUERY_set_layer_enable(int layer, int priority, bool enable)
public void QUERY_set_layer_enable()
{
WritePipeMessage(eMessage.eMessage_QUERY_set_layer_enable);
bwPipe.Write(layer);
bwPipe.Write(priority);
bwPipe.Write(enable);
bwPipe.Flush();
Message(eMessage.eMessage_QUERY_set_layer_enable);
}
public void QUERY_set_backdropColor(int backdropColor)
{
WritePipeMessage(eMessage.eMessage_QUERY_set_backdropColor);
bwPipe.Write(backdropColor);
bwPipe.Flush();
comm->value = (uint)backdropColor;
Message(eMessage.eMessage_QUERY_set_backdropColor);
}
public int QUERY_peek_logical_register(SNES_REG reg)
{
WritePipeMessage(eMessage.eMessage_QUERY_peek_logical_register);
bwPipe.Write((int)reg);
bwPipe.Flush();
return brPipe.ReadInt32();
comm->id = (uint)reg;
Message(eMessage.eMessage_QUERY_peek_logical_register);
return (int)comm->value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CpuRegs
public unsafe void QUERY_peek_cpu_regs(out CPURegs ret)
{
public uint pc;
public ushort a, x, y, z, s, d, vector; //7x
public byte p, nothing;
public uint aa, rd;
public byte sp, dp, db, mdr;
public const int SIZEOF = 32;
}
public unsafe void QUERY_peek_cpu_regs(out CpuRegs ret)
{
WritePipeMessage(eMessage.eMessage_QUERY_peek_cpu_regs);
//bwPipe.Flush();
byte[] temp = new byte[CpuRegs.SIZEOF];
brPipe.Read(temp, 0, CpuRegs.SIZEOF);
fixed(CpuRegs* ptr = &ret)
Marshal.Copy(temp, 0, new IntPtr(ptr), CpuRegs.SIZEOF);
Message(eMessage.eMessage_QUERY_peek_cpu_regs);
ret = comm->cpuregs;
}
public void QUERY_set_cdl(ICodeDataLog cdl)
{
WritePipeMessage(eMessage.eMessage_QUERY_set_cdl);
if (cdl == null)
{
for(int i=0;i<4*2;i++)
WritePipePointer(IntPtr.Zero);
}
else
{
WritePipePointer(cdl.GetPin("CARTROM"),false);
bwPipe.Write(cdl["CARTROM"].Length);
//TODO
//WritePipeMessage(eMessage.eMessage_QUERY_set_cdl);
//if (cdl == null)
//{
// for(int i=0;i<4*2;i++)
// WritePipePointer(IntPtr.Zero);
//}
//else
//{
// WritePipePointer(cdl.GetPin("CARTROM"),false);
// bwPipe.Write(cdl["CARTROM"].Length);
if (cdl.Has("CARTRAM"))
{
WritePipePointer(cdl.GetPin("CARTRAM"), false);
bwPipe.Write(cdl["CARTRAM"].Length);
}
else
{
WritePipePointer(IntPtr.Zero);
WritePipePointer(IntPtr.Zero);
}
// if (cdl.Has("CARTRAM"))
// {
// WritePipePointer(cdl.GetPin("CARTRAM"), false);
// bwPipe.Write(cdl["CARTRAM"].Length);
// }
// else
// {
// WritePipePointer(IntPtr.Zero);
// WritePipePointer(IntPtr.Zero);
// }
WritePipePointer(cdl.GetPin("WRAM"));
bwPipe.Write(cdl["WRAM"].Length);
// WritePipePointer(cdl.GetPin("WRAM"));
// bwPipe.Write(cdl["WRAM"].Length);
WritePipePointer(cdl.GetPin("APURAM"), false);
bwPipe.Write(cdl["APURAM"].Length);
bwPipe.Flush();
}
// WritePipePointer(cdl.GetPin("APURAM"), false);
// bwPipe.Write(cdl["APURAM"].Length);
// bwPipe.Flush();
//}
}
}

View File

@ -12,16 +12,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
default:
return false;
case eMessage.eMessage_SIG_video_refresh:
{
int width = brPipe.ReadInt32();
int height = brPipe.ReadInt32();
bwPipe.Write(0); //offset in mapped memory buffer
bwPipe.Flush();
brPipe.ReadBoolean(); //dummy synchronization
int width = (int)comm->width;
int height = (int)comm->height;
if (video_refresh != null)
{
video_refresh((int*)mmvaPtr, width, height);
video_refresh((int*)comm->ptr, width, height);
}
break;
}
@ -29,35 +27,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
break;
case eMessage.eMessage_SIG_input_state:
{
int port = brPipe.ReadInt32();
int device = brPipe.ReadInt32();
int index = brPipe.ReadInt32();
int id = brPipe.ReadInt32();
ushort ret = 0;
int port = comm->port;
int device = comm->device;
int index = comm->index;
int id = (int)comm->id;
if (input_state != null)
ret = input_state(port, device, index, id);
bwPipe.Write(ret);
bwPipe.Flush();
comm->value = input_state(port, device, index, id);
break;
}
case eMessage.eMessage_SIG_input_notify:
{
int index = brPipe.ReadInt32();
if (input_notify != null)
input_notify(index);
input_notify(comm->index);
break;
}
case eMessage.eMessage_SIG_audio_flush:
{
int nsamples = brPipe.ReadInt32();
bwPipe.Write(0); //location to store audio buffer in
bwPipe.Flush();
brPipe.ReadInt32(); //dummy synchronization
uint nsamples = comm->size;
if (audio_sample != null)
{
ushort* audiobuffer = ((ushort*)mmvaPtr);
for (int i = 0; i < nsamples; )
ushort* audiobuffer = ((ushort*)comm->ptr);
for (uint i = 0; i < nsamples; )
{
ushort left = audiobuffer[i++];
ushort right = audiobuffer[i++];
@ -65,32 +56,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
}
}
bwPipe.Write(0); //dummy synchronization
bwPipe.Flush();
brPipe.ReadInt32(); //dummy synchronization
break;
}
case eMessage.eMessage_SIG_path_request:
{
int slot = brPipe.ReadInt32();
string hint = ReadPipeString();
int slot = comm->slot;
string hint = comm->GetAscii();
string ret = hint;
if (pathRequest != null)
hint = pathRequest(slot, hint);
WritePipeString(hint);
hint = pathRequest(slot, hint);
SetAscii(hint);
break;
}
case eMessage.eMessage_SIG_trace_callback:
{
var trace = ReadPipeString();
if (traceCallback != null)
traceCallback(trace);
traceCallback(comm->GetAscii());
break;
}
case eMessage.eMessage_SIG_allocSharedMemory:
{
var name = ReadPipeString();
var size = brPipe.ReadInt32();
var name = comm->GetAscii();
var size = comm->size;
if (SharedMemoryBlocks.ContainsKey(name))
{
@ -115,25 +102,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
smb = new SharedMemoryBlock();
smb.Name = name;
smb.Size = size;
smb.Size = (int)size;
smb.BlockName = InstanceName + smb.Name;
smb.Allocate();
}
comm->ptr = smb.Ptr;
SharedMemoryBlocks[smb.Name] = smb;
WritePipeString(smb.BlockName);
CopyString(smb.BlockName);
break;
}
case eMessage.eMessage_SIG_freeSharedMemory:
{
string name = ReadPipeString();
var smb = SharedMemoryBlocks[name];
DeallocatedMemoryBlocks[name] = smb;
SharedMemoryBlocks.Remove(name);
foreach (var block in SharedMemoryBlocks.Values)
{
if (block.Ptr == comm->ptr)
{
DeallocatedMemoryBlocks[block.Name] = block;
SharedMemoryBlocks.Remove(block.Name);
break;
}
}
break;
}
} //switch(msg)
Message(eMessage.eMessage_ResumeAfterBRK);
return true;
}
}

View File

@ -107,9 +107,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
rom_xml = null,
rom_data = sgbRomData,
rom_size = (uint)sgbRomData.Length,
dmg_xml = null,
dmg_data = romData,
dmg_size = (uint)romData.Length
};
if (!LoadCurrent())
@ -143,7 +141,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
throw new Exception("snes_load_cartridge_normal() failed");
}
if (api.QUERY_get_region() == LibsnesApi.SNES_REGION.NTSC)
if (api.Region == LibsnesApi.SNES_REGION.NTSC)
{
//similar to what aviout reports from snes9x and seems logical from bsnes first principles. bsnes uses that numerator (ntsc master clockrate) for sure.
CoreComm.VsyncNum = 21477272;
@ -245,7 +243,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
{
LibsnesApi.CpuRegs regs;
LibsnesApi.CPURegs regs;
api.QUERY_peek_cpu_regs(out regs);
bool fn = (regs.p & 0x80)!=0;
@ -446,7 +444,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
api.SPECIAL_Resume();
}
void ExecHook(uint addr)
{
@ -454,7 +451,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
api.SPECIAL_Resume();
}
void WriteHook(uint addr, byte val)
{
@ -462,7 +458,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
api.SPECIAL_Resume();
}
LibsnesApi.snes_scanlineStart_t scanlineStart_cb;
@ -481,9 +476,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public string rom_xml;
public byte[] rom_data;
public uint rom_size;
public string dmg_xml;
public byte[] dmg_data;
public uint dmg_size;
}
LoadParams CurrLoadParams;
@ -493,9 +486,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
bool result = false;
if (CurrLoadParams.type == LoadParamType.Normal)
result = api.CMD_load_cartridge_normal(CurrLoadParams.xml_data, CurrLoadParams.rom_data);
else result = api.CMD_load_cartridge_super_game_boy(CurrLoadParams.rom_xml, CurrLoadParams.rom_data, CurrLoadParams.rom_size, CurrLoadParams.dmg_xml, CurrLoadParams.dmg_data, CurrLoadParams.dmg_size);
else result = api.CMD_load_cartridge_super_game_boy(CurrLoadParams.rom_xml, CurrLoadParams.rom_data, CurrLoadParams.rom_size, CurrLoadParams.dmg_data);
mapper = api.QUERY_get_mapper();
mapper = api.Mapper;
return result;
}
@ -639,11 +632,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public void FrameAdvance(bool render, bool rendersound)
{
api.MessageCounter = 0;
if(Settings.UseRingBuffer)
api.BeginBufferIO();
/* if the input poll callback is called, it will set this to false
* this has to be done before we save the per-frame state in deterministic
* mode, because in there, the core actually advances, and might advance
@ -684,19 +672,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
bool powerSignal = Controller.IsPressed("Power");
if (powerSignal) api.CMD_power();
//too many messages
api.QUERY_set_layer_enable(0, 0, Settings.ShowBG1_0);
api.QUERY_set_layer_enable(0, 1, Settings.ShowBG1_1);
api.QUERY_set_layer_enable(1, 0, Settings.ShowBG2_0);
api.QUERY_set_layer_enable(1, 1, Settings.ShowBG2_1);
api.QUERY_set_layer_enable(2, 0, Settings.ShowBG3_0);
api.QUERY_set_layer_enable(2, 1, Settings.ShowBG3_1);
api.QUERY_set_layer_enable(3, 0, Settings.ShowBG4_0);
api.QUERY_set_layer_enable(3, 1, Settings.ShowBG4_1);
api.QUERY_set_layer_enable(4, 0, Settings.ShowOBJ_0);
api.QUERY_set_layer_enable(4, 1, Settings.ShowOBJ_1);
api.QUERY_set_layer_enable(4, 2, Settings.ShowOBJ_2);
api.QUERY_set_layer_enable(4, 3, Settings.ShowOBJ_3);
var enables = new LibsnesApi.LayerEnables();
enables.BG1_Prio0 = Settings.ShowBG1_0;
enables.BG1_Prio1 = Settings.ShowBG1_1;
enables.BG2_Prio0 = Settings.ShowBG2_0;
enables.BG2_Prio1 = Settings.ShowBG2_1;
enables.BG3_Prio0 = Settings.ShowBG3_0;
enables.BG3_Prio1 = Settings.ShowBG3_1;
enables.BG4_Prio0 = Settings.ShowBG4_0;
enables.BG4_Prio1 = Settings.ShowBG4_1;
enables.Obj_Prio0 = Settings.ShowOBJ_0;
enables.Obj_Prio1 = Settings.ShowOBJ_1;
enables.Obj_Prio2 = Settings.ShowOBJ_2;
enables.Obj_Prio3 = Settings.ShowOBJ_3;
api.SetLayerEnables(ref enables);
RefreshMemoryCallbacks(false);
@ -704,16 +693,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
timeFrameCounter++;
api.CMD_run();
while (api.QUERY_HasMessage)
Console.WriteLine(api.QUERY_DequeueMessage());
//once upon a time we forwarded messages frmo bsnes here, by checking for queued text messages, but I don't think it's needed any longer
if (IsLagFrame)
LagCount++;
//diagnostics for IPC traffic
//Console.WriteLine(api.MessageCounter);
api.EndBufferIO();
}
void RefreshMemoryCallbacks(bool suppress)
@ -728,7 +711,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
get
{
if (api.QUERY_get_region() == LibsnesApi.SNES_REGION.NTSC)
if (api.Region == LibsnesApi.SNES_REGION.NTSC)
return DisplayType.NTSC;
else
return DisplayType.PAL;
@ -1351,7 +1334,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public bool ShowOBJ_2 = true;
public bool ShowOBJ_3 = true;
public bool UseRingBuffer = true;
public bool AlwaysDoubleSize = false;
public bool ForceDeterminism = true;
public string Palette = "BizHawk";

View File

@ -26,30 +26,7 @@ yabause: Load yabause/src/libyabause/libyabause.sln; builds as VC++ (VC10). Out
libdarm: stock build from https://github.com/jbremer/darm.git 71a027e0ffb2171d01213c6ecc6da1c49e158398
===========================================
Here's a guide zeromus developed on 03-feb-2015 to assembly a mingw-w64 + msys capable of building bsnes and maybe other things. The motivation: mingw make stopped working at some point (maybe a windows update) and the compiler is old and has problems.
Here's my reasoning for this approach:
1. People tell me mingw-w64 is superior
2. Mingw+msys or whatever will only install mingw
3. mingw-w64 doesnt come with msys
4. Therefore, we must 'upgrade' a mingw-w64 with msys.
Here's how:
install an mingw-w64 toolchain. lets say its at C:\i686-4.9.2-posix-dwarf-rt_v3-rev1
download mingw-get-setup.exe to C:\i686-4.9.2-posix-dwarf-rt_v3-rev1\mingw-get-setup.exe.
Run it and install to C:\i686-4.9.2-posix-dwarf-rt_v3-rev1. Turn off the shortcuts, but leave on the GUI options. Install it and pick continue.
Enable msys-base and pick installation > update catalogue. review changes and apply. then we'll be done. close, close, close.
You now have C:\i686-4.9.2-posix-dwarf-rt_v3-rev1\msys\1.0\msys.bat
run it, and run /postinstall/pi.sh
enter y,y and then C:/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32
answer Y (is this step MISSING?)
Now you've activated the mingw-w64 on this msys install.
It will tell you "Oh joy" you dont have a better make.exe. Thats bullshit, their make.exe is terrible. In fact youll want a new one
ln -s /mingw/bin/mingw32-make.exe /mingw/bin/make
Now, with the way msys sets up its paths, youll be using that make by default
libsnes: use libsnes/vs2015/libsnes.sln in vs2015
=========================================

View File

@ -22,18 +22,13 @@ extern "C" {
typedef void* cothread_t;
typedef void (*coentry_t)(void);
#if defined(LIBCO_IMPORT)
#define LIBCO_IMPORTDECL __declspec(dllimport)
#elif defined(LIBCO_EXPORT)
#define LIBCO_IMPORTDECL __declspec(dllexport)
#else
#define LIBCO_IMPORTDECL
#endif
LIBCO_IMPORTDECL cothread_t co_active();
LIBCO_IMPORTDECL cothread_t co_create(unsigned int, coentry_t);
LIBCO_IMPORTDECL void co_delete(cothread_t);
LIBCO_IMPORTDECL void co_switch(cothread_t);
void* co_getstack(cothread_t);
cothread_t co_active();
cothread_t co_create_withstack(void* stack, int stacksize, coentry_t);
cothread_t co_create(unsigned int, coentry_t);
void co_delete(cothread_t);
void co_switch(cothread_t);
cothread_t co_primary();
#ifdef __cplusplus
}

View File

@ -1,7 +1,20 @@
/*
original author: Nach
license: public domain
additional work: zeromus
note: more ARM compilers are supported here (check the ifdefs in _JUMP_BUFFER)
and: work has been done to make this coexist more peaceably with .net
*/
#define LIBCO_C
#include "libco.h"
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
@ -14,17 +27,107 @@ typedef struct
void *stack;
unsigned long seh_frame, stack_top, stack_bottom;
cothread_t caller;
int ownstack;
} cothread_struct;
static thread_local cothread_struct co_primary;
static thread_local cothread_struct _co_primary;
static thread_local cothread_struct *co_running = 0;
//-------------------
#if defined(_MSC_VER) || defined(MINGW32)
cothread_t co_primary() { return (cothread_t)&_co_primary; }
__declspec(dllimport) cothread_t os_co_create();
__declspec(dllimport) void os_pre_setjmp(cothread_t target);
__declspec(dllimport) void os_pre_longjmp(cothread_struct* rec);
//-------------------
#ifdef _MSC_VER
//links of interest
//http://connect.microsoft.com/VisualStudio/feedback/details/100319/really-wierd-behaviour-in-crt-io-coupled-with-some-inline-assembly
//http://en.wikipedia.org/wiki/Thread_Information_Block
//http://social.msdn.microsoft.com/Forums/en-US/vclanguage/thread/72093e46-4524-4f54-9f36-c7e8a309d1db/ //FS warning
#define WINVER 0x0400
#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(disable:4733)
#pragma warning(disable:4311)
static void capture_fs(cothread_struct* rec)
{
int temp;
__asm mov eax, dword ptr fs:[0];
__asm mov temp, eax;
rec->seh_frame = temp;
__asm mov eax, dword ptr fs:[4];
__asm mov temp, eax;
rec->stack_top = temp;
__asm mov eax, dword ptr fs:[8];
__asm mov temp, eax;
rec->stack_bottom = temp;
}
static void restore_fs(cothread_struct* rec)
{
int temp;
temp = rec->seh_frame;
__asm mov eax, temp;
__asm mov dword ptr fs:[0], eax
temp = rec->stack_top;
__asm mov eax, temp;
__asm mov dword ptr fs:[4], eax
temp = rec->stack_bottom;
__asm mov eax, temp;
__asm mov dword ptr fs:[8], eax
}
static void os_co_wrapper()
{
cothread_struct* rec = (cothread_struct*)co_active();
__try
{
rec->coentry();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//unhandled win32 exception in coroutine.
//this coroutine will now be suspended permanently and control will be yielded to caller, for lack of anything better to do.
//perhaps the process should just terminate.
for(;;)
{
//dead coroutine
co_switch(rec->caller);
}
}
}
static void os_co_create(cothread_struct* rec, unsigned int size, coentry_t coentry)
{
_JUMP_BUFFER* jb = (_JUMP_BUFFER*)&rec->context;
cothread_struct temp;
jb->Esp = (unsigned long)rec->stack + size - 4;
jb->Eip = (unsigned long)os_co_wrapper;
rec->stack_top = jb->Esp + 4;
rec->stack_bottom = (unsigned long)rec->stack;
//wild assumption about SEH frame.. seems to work
capture_fs(&temp);
rec->seh_frame = temp.seh_frame;
}
static void os_pre_setjmp(cothread_t target)
{
cothread_struct* rec = (cothread_struct*)target;
capture_fs(co_running);
rec->caller = co_running;
}
static void os_pre_longjmp(cothread_struct* rec)
{
restore_fs(rec);
}
#elif defined(__ARM_EABI__) || defined(__ARMCC_VERSION)
@ -68,25 +171,40 @@ static void os_pre_longjmp(cothread_struct* rec)
cothread_t co_active()
{
if(!co_running) co_running = &co_primary;
if(!co_running) co_running = &_co_primary;
return (cothread_t)co_running;
}
cothread_t co_create(unsigned int size, void (*coentry)(void))
void* co_getstack(cothread_t cothread)
{
return ((cothread_struct*)cothread)->stack;
}
cothread_t co_create(unsigned int stacksize, coentry_t coentry)
{
cothread_struct* ret = (cothread_struct*)co_create_withstack(malloc(stacksize), stacksize, coentry);
if(ret)
ret->ownstack = 1;
return (cothread_t)ret;
}
cothread_t co_create_withstack(void* stack, int stacksize, coentry_t coentry)
{
cothread_struct *thread;
if(!co_running) co_running = &co_primary;
if(!co_running) co_running = &_co_primary;
thread = (cothread_struct*)malloc(sizeof(cothread_struct));
if(thread)
{
thread->coentry = coentry;
thread->stack = malloc(size);
thread->stack = stack;
{
setjmp(thread->context);
os_co_create(thread,size,coentry);
os_co_create(thread,stacksize,coentry);
}
thread->ownstack = 0;
}
return (cothread_t)thread;
@ -96,10 +214,9 @@ void co_delete(cothread_t cothread)
{
if(cothread)
{
if(((cothread_struct*)cothread)->stack)
{
free(((cothread_struct*)cothread)->stack);
}
cothread_struct* thread = (cothread_struct*)cothread;
if (thread->ownstack)
free(thread->stack);
free(cothread);
}
}

View File

@ -1,93 +1,117 @@
/*
libco.x86 (2009-10-12)
author: byuu
license: public domain
*/
#define LIBCO_C
#include "libco.h"
#include <assert.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_MSC_VER)
#define fastcall __fastcall
#elif defined(__GNUC__)
#define fastcall __attribute__((fastcall))
#else
#error "libco: please define fastcall macro"
#endif
static thread_local long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (fastcall *co_swap)(cothread_t, cothread_t) = 0;
//ABI: fastcall
static unsigned char co_swap_function[] = {
0x89, 0x22, 0x8B, 0x21, 0x58, 0x89, 0x6A, 0x04, 0x89, 0x72, 0x08, 0x89, 0x7A, 0x0C, 0x89, 0x5A,
0x10, 0x8B, 0x69, 0x04, 0x8B, 0x71, 0x08, 0x8B, 0x79, 0x0C, 0x8B, 0x59, 0x10, 0xFF, 0xE0,
};
#ifdef _WIN32
#include <windows.h>
void co_init() {
DWORD old_privileges;
VirtualProtect(co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges);
}
#else
#include <unistd.h>
#include <sys/mman.h>
void co_init() {
unsigned long addr = (unsigned long)co_swap_function;
unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC);
}
#endif
static void crash() {
assert(0); /* called only if cothread_t entrypoint returns */
}
cothread_t co_active() {
if(!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
cothread_t handle;
if(!co_swap) {
co_init();
co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle) co_active_handle = &co_active_buffer;
size += 256; /* allocate additional space for storage */
size &= ~15; /* align stack to 16-byte boundary */
if(handle = (cothread_t)malloc(size)) {
long *p = (long*)((char*)handle + size); /* seek to top of stack */
*--p = (long)crash; /* crash if entrypoint returns */
*--p = (long)entrypoint; /* start of function */
*(long*)handle = (long)p; /* stack pointer */
}
return handle;
}
void co_delete(cothread_t handle) {
free(handle);
}
void co_switch(cothread_t handle) {
register cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
#ifdef __cplusplus
}
#endif
/*
libco.x86 (2009-10-12)
author: byuu
license: public domain
*/
#define LIBCO_C
#include "libco.h"
#include <assert.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_MSC_VER)
#define fastcall __fastcall
#elif defined(__GNUC__)
#define fastcall __attribute__((fastcall))
#else
#error "libco: please define fastcall macro"
#endif
static thread_local long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (fastcall *co_swap)(cothread_t, cothread_t) = 0;
//ABI: fastcall
static unsigned char co_swap_function[] = {
0x89, 0x22, /* mov [edx],esp */
0x8b, 0x21, /* mov esp,[ecx] */
0x58, /* pop eax */
0x89, 0x6a, 0x04, /* mov [edx+0x04],ebp */
0x89, 0x72, 0x08, /* mov [edx+0x08],esi */
0x89, 0x7a, 0x0c, /* mov [edx+0x0c],edi */
0x89, 0x5a, 0x10, /* mov [edx+0x10],ebx */
0x8b, 0x69, 0x04, /* mov ebp,[ecx+0x04] */
0x8b, 0x71, 0x08, /* mov esi,[ecx+0x08] */
0x8b, 0x79, 0x0c, /* mov edi,[ecx+0x0c] */
0x8b, 0x59, 0x10, /* mov ebx,[ecx+0x10] */
0xff, 0xe0, /* jmp eax */
};
#ifdef _WIN32
#include <windows.h>
void co_init(void)
{
DWORD old_privileges;
VirtualProtect(co_swap_function,
sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges);
}
#else
#include <unistd.h>
#include <sys/mman.h>
void co_init(void)
{
unsigned long addr = (unsigned long)co_swap_function;
unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC);
}
#endif
static void crash(void)
{
assert(0); /* called only if cothread_t entrypoint returns */
}
cothread_t co_active(void)
{
if(!co_active_handle)
co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void))
{
cothread_t handle;
if(!co_swap)
{
co_init();
co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle)
co_active_handle = &co_active_buffer;
size += 256; /* allocate additional space for storage */
size &= ~15; /* align stack to 16-byte boundary */
if((handle = (cothread_t)malloc(size)))
{
long *p = (long*)((char*)handle + size); /* seek to top of stack */
*--p = (long)crash; /* crash if entrypoint returns */
*--p = (long)entrypoint; /* start of function */
*(long*)handle = (long)p; /* stack pointer */
}
return handle;
}
void co_delete(cothread_t handle)
{
free(handle);
}
void co_switch(cothread_t handle)
{
register cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -327,7 +327,7 @@
<ClCompile Include="..\bsnes\gameboy\scheduler\scheduler.cpp" />
<ClCompile Include="..\bsnes\gameboy\system\system.cpp" />
<ClCompile Include="..\bsnes\gameboy\video\video.cpp" />
<ClCompile Include="..\bsnes\libco\x86.c" />
<ClCompile Include="..\bsnes\libco\sjlj-multi.c" />
<ClCompile Include="..\bsnes\snes\alt\cpu\cpu.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-Compatibility|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release-Compatibility|Win32'">true</ExcludedFromBuild>

View File

@ -566,7 +566,7 @@
<ClCompile Include="..\bsnes\snes\dsp\counter.cpp">
<Filter>snes\dsp</Filter>
</ClCompile>
<ClCompile Include="..\bsnes\libco\x86.c">
<ClCompile Include="..\bsnes\libco\sjlj-multi.c">
<Filter>libco</Filter>
</ClCompile>
</ItemGroup>