From bacbaa2bec645fbd97afa29283acb6bc087ab30c Mon Sep 17 00:00:00 2001 From: goyuken Date: Tue, 16 Sep 2014 00:40:15 +0000 Subject: [PATCH] lynx: controllers and frame timing fix --- .../Consoles/Atari/lynx/LibLynx.cs | 16 ++++++- .../Consoles/Atari/lynx/Lynx.cs | 39 ++++++++++++++---- lynx/mikie.cpp | 1 - lynx/system.cpp | 18 ++++---- lynx/system.h | 12 +++--- output/dll/bizlynx.dll | Bin 61440 -> 61440 bytes 6 files changed, 63 insertions(+), 23 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/LibLynx.cs b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/LibLynx.cs index f2202adf2e..e4a99ba946 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/LibLynx.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/LibLynx.cs @@ -22,6 +22,20 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx public static extern void Reset(IntPtr s); [DllImport(dllname, CallingConvention = cc)] - public static extern void Advance(IntPtr s, int buttons, int[] vbuff, short[] sbuff, ref int sbuffsize); + public static extern void Advance(IntPtr s, Buttons buttons, int[] vbuff, short[] sbuff, ref int sbuffsize); + + [Flags] + public enum Buttons : ushort + { + Up = 0x0080, + Down = 0x0040, + Left = 0x0010, + Right = 0x0020, + Option_1 = 0x008, + Option_2 = 0x004, + B = 0x002, + A = 0x001, + Pause = 0x100, + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs index a4d5f84d13..9188271150 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs @@ -80,10 +80,12 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx case 0x80000: pagesize0 = 0x800; break; case 0x30000: pagesize0 = 0x200; pagesize1 = 0x100; break; + case 0x50000: pagesize0 = 0x400; pagesize1 = 0x100; break; case 0x60000: pagesize0 = 0x400; pagesize1 = 0x200; break; + case 0x90000: pagesize0 = 0x800; pagesize1 = 0x100; break; + case 0xa0000: pagesize0 = 0x800; pagesize1 = 0x200; break; case 0xc0000: pagesize0 = 0x800; pagesize1 = 0x400; break; case 0x100000: pagesize0 = 0x800; pagesize1 = 0x800; break; - } Console.WriteLine("Auto-guessed banking options {0} {1}", pagesize0, pagesize1); } @@ -91,7 +93,8 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx Core = LibLynx.Create(realfile, realfile.Length, bios, bios.Length, pagesize0, pagesize1, false); try { - // ... + CoreComm.VsyncNum = 16000000; // 16.00 mhz refclock + CoreComm.VsyncDen = 16 * 105 * 159; } catch { @@ -103,14 +106,12 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx public void FrameAdvance(bool render, bool rendersound = true) { Frame++; - if (Controller["Power"]) LibLynx.Reset(Core); int samples = soundbuff.Length; - LibLynx.Advance(Core, 0, videobuff, soundbuff, ref samples); - numsamp = samples; - Console.WriteLine(numsamp); + LibLynx.Advance(Core, GetButtons(), videobuff, soundbuff, ref samples); + numsamp = samples / 2; // sound provider wants number of sample pairs } public int Frame { get; private set; } @@ -156,9 +157,31 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx #region Controller - public ControllerDefinition ControllerDefinition { get { return NullEmulator.NullController; } } + private static readonly ControllerDefinition LynxTroller = new ControllerDefinition + { + Name = "Lynx Controller", + BoolButtons = { "Up", "Down", "Left", "Right", "A", "B", "Option 1", "Option 2", "Pause", "Power" }, + }; + + public ControllerDefinition ControllerDefinition { get { return LynxTroller; } } public IController Controller { get; set; } + LibLynx.Buttons GetButtons() + { + LibLynx.Buttons ret = 0; + if (Controller["A"]) ret |= LibLynx.Buttons.A; + if (Controller["B"]) ret |= LibLynx.Buttons.B; + if (Controller["Up"]) ret |= LibLynx.Buttons.Up; + if (Controller["Down"]) ret |= LibLynx.Buttons.Down; + if (Controller["Left"]) ret |= LibLynx.Buttons.Left; + if (Controller["Right"]) ret |= LibLynx.Buttons.Right; + if (Controller["Pause"]) ret |= LibLynx.Buttons.Pause; + if (Controller["Option 1"]) ret |= LibLynx.Buttons.Option_1; + if (Controller["Option 2"]) ret |= LibLynx.Buttons.Option_2; + + return ret; + } + #endregion #region savestates @@ -239,7 +262,7 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx #region SoundProvider - short[] soundbuff = new short[1000000]; // todo: make this smaller once frame loop is resolved + short[] soundbuff = new short[2048]; int numsamp; public ISoundProvider SoundProvider { get { return null; } } diff --git a/lynx/mikie.cpp b/lynx/mikie.cpp index ffa710a07c..a56d9b93f1 100644 --- a/lynx/mikie.cpp +++ b/lynx/mikie.cpp @@ -406,7 +406,6 @@ uint32 CMikie::DisplayRenderLine() { uint32 work_done=0; - if(!mpDisplayCurrent) return 0; if(!mDISPCTL_DMAEnable) return 0; // if(mLynxLine&0x80000000) return 0; diff --git a/lynx/system.cpp b/lynx/system.cpp index e8ba339ed4..833806806d 100644 --- a/lynx/system.cpp +++ b/lynx/system.cpp @@ -158,27 +158,31 @@ void CSystem::Advance(int buttons, uint32 *vbuff, int16 *sbuff, int &sbuffsize) // this check needs to occur at least once every 250 million cycles or better mMikie->CheckWrap(); - SetButtonData(buttons); uint32 start = gSystemCycleCount; + // nominal timer values are div16 for prescalar, 158 for line timer, and 104 for frame timer + // reloads are actually +1 due to the way the hardware works + // so this is a frame, theoretically + uint32 target = gSystemCycleCount + 16 * 105 * 159 - frameoverflow; + // audio start frame mMikie->startTS = start; mMikie->mpDisplayCurrent = vbuff; - // go to next frame end, or no more than 200,000 cycles to avoid exploding the output buffer (was set at 60ms limit) - while (mMikie->mpDisplayCurrent && gSystemCycleCount - start < 200000) - // while (gSystemCycleCount - start < 700000) // what's the magic significance? + while (gSystemCycleCount < target) + //while (mMikie->mpDisplayCurrent && gSystemCycleCount - start < 800000) { - Update(); + Update(target); } // total cycles executed is now gSystemCycleCount - start - mMikie->mikbuf.end_frame((gSystemCycleCount - start) >> 2); - sbuffsize = mMikie->mikbuf.read_samples(sbuff, sbuffsize) / 2; + frameoverflow = gSystemCycleCount - target; + mMikie->mikbuf.end_frame((gSystemCycleCount - start) >> 2); + sbuffsize = mMikie->mikbuf.read_samples(sbuff, sbuffsize); } diff --git a/lynx/system.h b/lynx/system.h index a3ce564eca..5e4e5a8989 100644 --- a/lynx/system.h +++ b/lynx/system.h @@ -121,10 +121,10 @@ public: public: void Reset() MDFN_COLD; - inline void Update() + inline void Update(uint32 targetclock) { // Only update if there is a predicted timer event - if(gSystemCycleCount>=gNextTimerEvent) + if(gSystemCycleCount >= gNextTimerEvent) { mMikie->Update(); } @@ -135,7 +135,7 @@ public: // If the CPU is asleep then skip to the next timer event if(gSystemCPUSleep) { - gSystemCycleCount=gNextTimerEvent; + gSystemCycleCount = std::min(gNextTimerEvent, targetclock); } } @@ -192,11 +192,9 @@ public: // Miscellaneous void SetButtonData(uint32 data) {mSusie->SetButtonData(data);}; uint32 GetButtonData() {return mSusie->GetButtonData();}; - void SetCycleBreakpoint(uint32 breakpoint) {mCycleCountBreakpoint=breakpoint;}; uint8* GetRamPointer() {return mRam->GetRamPointer();}; public: - uint32 mCycleCountBreakpoint; CLynxBase *mMemoryHandlers[SYSTEM_SIZE]; CCart *mCart; CRom *mRom; @@ -210,11 +208,13 @@ public: uint32 gSuzieDoneTime; uint32 gSystemCycleCount; uint32 gNextTimerEvent; - //uint32 gCPUBootAddress; uint32 gSystemIRQ; uint32 gSystemNMI; uint32 gSystemCPUSleep; uint32 gSystemHalt; + + // frame overflow detection + int frameoverflow; }; #endif diff --git a/output/dll/bizlynx.dll b/output/dll/bizlynx.dll index 517969ff97a616d1d6b440caaaa3f960dc0a4dd2..853ba30fafd8c8b1b472c1d1551c43ca15c14db2 100644 GIT binary patch delta 10505 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 E2R+MY^Z)<= delta 10502 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