Update quickerNES and add Arkanoid input support (squashed PR #3997)

* Update QuickerNES interface to accept arkanoid controllers

* Updating

* Updating quickernes to its latest version and now supporting ArkanoidNES and ArkanoidFamicom controllers

* Apply suggestions from code review

applying yoshi's suggestions

Co-authored-by: James Groom <OSSYoshiRulz+GitHub@gmail.com>

* Using proper C#

* Updating based on Morilli's comments

* Updating the linux core

* Fixes based on Yoshi's comments

---------

Co-authored-by: James Groom <OSSYoshiRulz+GitHub@gmail.com>
This commit is contained in:
Sergio Martin 2024-09-08 13:24:27 +02:00 committed by GitHub
parent 5e9d3cd2bc
commit aa662eb930
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 169 additions and 19 deletions

Binary file not shown.

Binary file not shown.

View File

@ -50,9 +50,45 @@ QN_EXPORT const char *qn_set_sample_rate(quickerNES::Emu *e, int rate)
return ret;
}
QN_EXPORT const char *qn_emulate_frame(quickerNES::Emu *e, int pad1, int pad2)
QN_EXPORT const char *qn_emulate_frame(quickerNES::Emu *e, uint32_t pad1, uint32_t pad2, uint8_t arkanoidPosition, uint8_t arkanoidFire, int controllerType)
{
return e->emulate_frame((uint32_t)pad1, (uint32_t)pad2);
e->setControllerType((quickerNES::Core::controllerType_t)controllerType);
uint32_t arkanoidLatch = 0;
if ((quickerNES::Core::controllerType_t) controllerType == quickerNES::Core::controllerType_t::arkanoidNES_t ||
(quickerNES::Core::controllerType_t) controllerType == quickerNES::Core::controllerType_t::arkanoidFamicom_t)
{
e->setControllerType((quickerNES::Core::controllerType_t) controllerType);
// This is where we calculate the stream of bits required by the NES / Famicom to correctly interpret the Arkanoid potentiometer signal
// The logic and procedure were created based on the information in https://www.nesdev.org/wiki/Arkanoid_controller
// - The arkanoidPosition variable is the intended value
// - The centeringPotValue is a calibration parameter. The arkanoidPosition value is passed to the console as a relative value to this.
// This can be change tod calibrate a misaligned physical potentiomenter (not relevant in emulation)
// The minumum / maximum ranges for this values are (0x0D-0xAD) to (0x5C-0xFC). NesHawk seems to be calibrated at: 0xAB (171).
// The value of centeringPotValue is calibrated to coincide exactly with that of the NesHawk emulator
uint8_t centeringPotValue = 0xAB;
// Procedure, as expected by the console:
// 1) Obtain the relative value of arkanoidPosition from the centeringPotValue
uint8_t relativePosition = centeringPotValue - arkanoidPosition;
// 2) The result is bit-inverted (required by the console)
// The easiest solution is simply to do this per bit
if ((relativePosition & 128) > 0) arkanoidLatch += 1;
if ((relativePosition & 64) > 0) arkanoidLatch += 2;
if ((relativePosition & 32) > 0) arkanoidLatch += 4;
if ((relativePosition & 16) > 0) arkanoidLatch += 8;
if ((relativePosition & 8) > 0) arkanoidLatch += 16;
if ((relativePosition & 4) > 0) arkanoidLatch += 32;
if ((relativePosition & 2) > 0) arkanoidLatch += 64;
if ((relativePosition & 1) > 0) arkanoidLatch += 128;
}
return e->emulate_frame(pad1, pad2, arkanoidLatch, arkanoidFire);
}
QN_EXPORT void qn_blit(quickerNES::Emu *e, int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom)

@ -1 +1 @@
Subproject commit 027f63aee19840ac1bf571a5d00d7758ca517220
Subproject commit 61ff28710c44b75c170dff1ee8f89831296e34b5

View File

@ -4,7 +4,7 @@ CP = cp
CXXFLAGS = -I../core/source/quickerNES/core/ -I../core/extern/jaffarCommon/include -Wall -Wfatal-errors -Werror \
-std=c++20 -O3 -fomit-frame-pointer -flto -fvisibility=internal -fvisibility-inlines-hidden \
-D_GNU_SOURCE -D__INLINE__=inline -D_QUICKERNES_DETECT_JOYPAD_READS -D_QUICKERNES_ENABLE_TRACEBACK_SUPPORT
-D_GNU_SOURCE -D__INLINE__=inline -D_QUICKERNES_DETECT_JOYPAD_READS -D_QUICKERNES_ENABLE_TRACEBACK_SUPPORT -D_QUICKERNES_SUPPORT_ARKANOID_INPUTS
# TODO: include these as options in the Makefile
# -fprofile-generate

View File

@ -149,6 +149,7 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\core\source\quickerNES\core;..\core\extern\jaffarCommon\include;$(IncludePath)</IncludePath>
<OutDir>..\Assets\dll</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>..\core\source\quickerNES\core;..\core\extern\jaffarCommon\include;$(IncludePath)</IncludePath>
@ -159,7 +160,7 @@
<Optimization>Disabled</Optimization>
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);_QUICKERNES_DETECT_JOYPAD_READS;_QUICKERNES_ENABLE_TRACEBACK_SUPPORT;</PreprocessorDefinitions>
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);_QUICKERNES_DETECT_JOYPAD_READS;_QUICKERNES_ENABLE_TRACEBACK_SUPPORT;_QUICKERNES_SUPPORT_ARKANOID_INPUTS;__INLINE__=inline</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
@ -179,7 +180,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);_QUICKERNES_DETECT_JOYPAD_READS;_QUICKERNES_ENABLE_TRACEBACK_SUPPORT;</PreprocessorDefinitions>
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);_QUICKERNES_DETECT_JOYPAD_READS;_QUICKERNES_ENABLE_TRACEBACK_SUPPORT;_QUICKERNES_SUPPORT_ARKANOID_INPUTS;__INLINE__=inline</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>

View File

@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
/// <param name="pad2">pad 2 input</param>
/// <returns>string error</returns>
[BizImport(CallingConvention.Cdecl)]
public abstract IntPtr qn_emulate_frame(IntPtr e, uint pad1, uint pad2);
public abstract IntPtr qn_emulate_frame(IntPtr e, uint pad1, uint pad2, byte arkanoidPos, byte arkanoidFire, uint controllerType);
/// <summary>
/// blit to rgb32
/// </summary>

View File

@ -156,6 +156,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
Gamepad = 0x1,
FourScore = 0x2,
//FourScore2 = 0x3, // not available for port 1
ArkanoidNES = 0x4,
ArkanoidFamicom = 0x5,
}
public enum Port2PeripheralOption : byte

View File

@ -82,21 +82,84 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
private void SetControllerDefinition()
{
ControllerDefinition def = new("NES Controller");
// Function to add gamepad buttons
void AddButtons(IEnumerable<(string PrefixedName, uint Bitmask)> entries)
=> def.BoolButtons.AddRange(entries.Select(static p => p.PrefixedName));
AddButtons(_syncSettings.Port1 switch
// Parsing Port1 inputs
switch (_syncSettings.Port1)
{
Port1PeripheralOption.Gamepad => GamepadButtons[0],
Port1PeripheralOption.FourScore => FourScoreButtons[0],
_ => Enumerable.Empty<(string PrefixedName, uint Bitmask)>()
});
AddButtons(_syncSettings.Port2 switch
case Port1PeripheralOption.Gamepad:
// Adding set of gamepad buttons (P1)
AddButtons(GamepadButtons[0]);
break;
case Port1PeripheralOption.FourScore:
// Adding set of gamepad buttons (P1)
AddButtons(FourScoreButtons[0]);
break;
case Port1PeripheralOption.ArkanoidNES:
// Adding Arkanoid Paddle potentiometer
def.AddAxis("P2 Paddle", 0.RangeTo(160), 80);
// Adding Arkanoid Fire button
def.BoolButtons.Add("P2 Fire");
break;
case Port1PeripheralOption.ArkanoidFamicom:
// Adding set of gamepad buttons (P1)
AddButtons(GamepadButtons[0]);
// Adding dummy set of P2 buttons (not yet supported)
def.BoolButtons.Add("P2 Up");
def.BoolButtons.Add("P2 Down");
def.BoolButtons.Add("P2 Left");
def.BoolButtons.Add("P2 Right");
def.BoolButtons.Add("P2 B");
def.BoolButtons.Add("P2 A");
def.BoolButtons.Add("P2 M"); // Microphone
// Adding Arkanoid Paddle potentiometer
def.AddAxis("P3 Paddle", 0.RangeTo(160), 80);
// Adding Arkanoid Fire button
def.BoolButtons.Add("P3 Fire");
break;
}
// Parsing Port2 inputs
switch (_syncSettings.Port2)
{
Port2PeripheralOption.Gamepad => GamepadButtons[1],
Port2PeripheralOption.FourScore2 => FourScoreButtons[1],
_ => Enumerable.Empty<(string PrefixedName, uint Bitmask)>()
});
case Port2PeripheralOption.Gamepad:
// Adding set of gamepad buttons (P1)
AddButtons(GamepadButtons[1]);
break;
case Port2PeripheralOption.FourScore2:
// Adding set of gamepad buttons (P2)
AddButtons(FourScoreButtons[1]);
break;
}
// Adding console buttons
def.BoolButtons.AddRange(new[] { "Reset", "Power" }); // console buttons
ControllerDefinition = def.MakeImmutable();
}
@ -167,7 +230,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
};
private void SetPads(IController controller, out uint j1, out uint j2)
{
static uint PackGamepadButtonsFor(int portNumber, IController controller)
@ -198,6 +260,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
switch (_syncSettings.Port1)
{
case Port1PeripheralOption.Gamepad:
case Port1PeripheralOption.ArkanoidFamicom:
j1 = PackGamepadButtonsFor(0, controller);
break;
case Port1PeripheralOption.FourScore:
@ -215,6 +278,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
}
}
public enum QuickerNESInternalControllerTypeEnumeration : byte
{
None = 0x0,
Joypad = 0x1,
ArkanoidNES = 0x2,
ArkanoidFamicom = 0x3,
}
public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
{
CheckDisposed();
@ -228,7 +299,47 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
QN.qn_set_tracecb(Context, Tracer.IsEnabled() ? _traceCb : null);
LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2));
// Getting correct internal controller type for QuickerNES
QuickerNESInternalControllerTypeEnumeration internalQuickerNESControllerType = QuickerNESInternalControllerTypeEnumeration.None;
// Handling Port2
switch (_syncSettings.Port2)
{
case Port2PeripheralOption.Gamepad:
case Port2PeripheralOption.FourScore2:
internalQuickerNESControllerType = QuickerNESInternalControllerTypeEnumeration.Joypad; break;
}
// Handling Port1 -- Using Arkanoid overrides the selection for Port2
switch (_syncSettings.Port1)
{
case Port1PeripheralOption.Gamepad:
case Port1PeripheralOption.FourScore:
internalQuickerNESControllerType = QuickerNESInternalControllerTypeEnumeration.Joypad; break;
case Port1PeripheralOption.ArkanoidNES:
internalQuickerNESControllerType = QuickerNESInternalControllerTypeEnumeration.ArkanoidNES; break;
case Port1PeripheralOption.ArkanoidFamicom:
internalQuickerNESControllerType = QuickerNESInternalControllerTypeEnumeration.ArkanoidFamicom; break;
}
// Parsing arkanoid inputs
byte arkanoidPos = 0;
byte arkanoidFire = 0;
switch (_syncSettings.Port1)
{
case Port1PeripheralOption.ArkanoidNES:
arkanoidPos = unchecked((byte)controller.AxisValue("P2 Paddle"));
arkanoidFire = controller.IsPressed("P2 Fire") ? (byte) 1 : (byte) 0;
break;
case Port1PeripheralOption.ArkanoidFamicom:
arkanoidPos = unchecked((byte)controller.AxisValue("P3 Paddle"));
arkanoidFire = controller.IsPressed("P3 Fire") ? (byte) 1 : (byte) 0;
break;
}
LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2, arkanoidPos, arkanoidFire, (uint) internalQuickerNESControllerType));
IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0;
if (IsLagFrame)
LagCount++;