[Soft-Float] - Initial Interpreter Implementation of Ps2's floating point unit specification.

This Pull Request implements the first take ever on real Soft-Float support in PCSX2.

This work is a combination or several efforts and researches done prior.

Credits:

- https://www.gregorygaines.com/blog/emulating-ps2-floating-point-nums-ieee-754-diffs-part-1/

- https://github.com/GitHubProUser67/MultiServer3/blob/main/BackendServices/CastleLibrary/EmotionEngine.Emulator/Ps2Float.cs

- https://github.com/Goatman13/pcsx2/tree/accurate_int_add_sub

- PCSX2 Team for their help and support in this massive journey.

This pull request should be tested with every games requiring a clamping/rounding mode (cf: GameDatabase).

Currently, this PR fixes on the interpreters:

- https://github.com/PCSX2/pcsx2/issues/354

- https://github.com/PCSX2/pcsx2/issues/11507

- https://github.com/PCSX2/pcsx2/issues/10519

- https://github.com/PCSX2/pcsx2/issues/8068

- https://github.com/PCSX2/pcsx2/issues/7642

- https://github.com/PCSX2/pcsx2/issues/5257

This is important to note, that this implementation, while technically fixing Gran Turismo 4 and Klonoa 2, makes the games crash due to very high floats being passed in the emu code, and failing at some points later in the process. This has not yet been ironed-out.

Other than that, this sets the floor for Soft-Float in PCSX2, a long awaited contribution.
This commit is contained in:
GitHubProUser67 2024-11-12 21:10:30 +01:00
parent eeb919325e
commit de047eaa40
14 changed files with 2221 additions and 669 deletions

View File

@ -48,6 +48,16 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget*
connect(m_ui.vu0ClampMode, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) { setClampingMode(0, index); });
connect(m_ui.vu1ClampMode, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) { setClampingMode(1, index); });
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.eeSoftAddSub, "EmuCore/CPU/Recompiler", "fpuSoftAddSub", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.eeSoftMulDiv, "EmuCore/CPU/Recompiler", "fpuSoftMulDiv", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.eeSoftSqrt, "EmuCore/CPU/Recompiler", "fpuSoftSqrt", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu0SoftAddSub, "EmuCore/CPU/Recompiler", "vu0SoftAddSub", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu0SoftMulDiv, "EmuCore/CPU/Recompiler", "vu0SoftMulDiv", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu0SoftSqrt, "EmuCore/CPU/Recompiler", "vu0SoftSqrt", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu1SoftAddSub, "EmuCore/CPU/Recompiler", "vu1SoftAddSub", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu1SoftMulDiv, "EmuCore/CPU/Recompiler", "vu1SoftMulDiv", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu1SoftSqrt, "EmuCore/CPU/Recompiler", "vu1SoftSqrt", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.iopRecompiler, "EmuCore/CPU/Recompiler", "EnableIOP", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gameFixes, "EmuCore", "EnableGameFixes", true);

View File

@ -33,8 +33,8 @@
<rect>
<x>0</x>
<y>-447</y>
<width>790</width>
<height>1049</height>
<width>793</width>
<height>1283</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -94,10 +94,10 @@
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="eeDivRoundingLabel">
<item row="2" column="0">
<widget class="QLabel" name="eeClampLabel">
<property name="text">
<string extracomment="Rounding refers here to the mathematical term.">Division Rounding Mode:</string>
<string extracomment="Clamping: Forcing out of bounds things in bounds by changing them to the closest possible value. In this case, this refers to clamping large PS2 floating point values (which map to infinity or NaN in PCs' IEEE754 floats) to non-infinite ones.">Clamping Mode:</string>
</property>
</widget>
</item>
@ -125,38 +125,7 @@
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="eeClampLabel">
<property name="text">
<string extracomment="Clamping: Forcing out of bounds things in bounds by changing them to the closest possible value. In this case, this refers to clamping large PS2 floating point values (which map to infinity or NaN in PCs' IEEE754 floats) to non-infinite ones.">Clamping Mode:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="eeClampMode">
<item>
<property name="text">
<string comment="ClampMode">None</string>
</property>
</item>
<item>
<property name="text">
<string>Normal (Default)</string>
</property>
</item>
<item>
<property name="text">
<string extracomment="Sign: refers here to the mathematical meaning (plus/minus).">Extra + Preserve Sign</string>
</property>
</item>
<item>
<property name="text">
<string>Full</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="eeSettingsMisc">
<item row="1" column="0">
<widget class="QCheckBox" name="eeWaitLoopDetection">
@ -208,6 +177,67 @@
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="eeClampMode">
<item>
<property name="text">
<string comment="ClampMode">None</string>
</property>
</item>
<item>
<property name="text">
<string>Normal (Default)</string>
</property>
</item>
<item>
<property name="text">
<string extracomment="Sign: refers here to the mathematical meaning (plus/minus).">Extra + Preserve Sign</string>
</property>
</item>
<item>
<property name="text">
<string>Full</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="eeDivRoundingLabel">
<property name="text">
<string extracomment="Rounding refers here to the mathematical term.">Division Rounding Mode:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="eeSoftFloat">
<property name="title">
<string>Software Float</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QCheckBox" name="eeSoftMulDiv">
<property name="text">
<string>Multiplication/Division</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="eeSoftAddSub">
<property name="text">
<string>Addition/Subtraction</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="eeSoftSqrt">
<property name="text">
<string>Square Root</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
@ -218,7 +248,7 @@
<string extracomment="Vector Unit/VU: refers to two of PS2's processors. Do not translate the full text or do so as a comment. Leave the acronym as-is.">Vector Units (VU)</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="vu1RoundingLabel">
<property name="text">
<string>VU1 Rounding Mode:</string>
@ -249,7 +279,129 @@
</item>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="4" column="0">
<widget class="QLabel" name="vu1ClampLabel">
<property name="text">
<string>VU1 Clamping Mode:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="vu0RoundingLabel">
<property name="text">
<string>VU0 Rounding Mode:</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QGroupBox" name="vu1SoftFloat">
<property name="title">
<string>VU1 Software Float</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="1">
<widget class="QCheckBox" name="vu1SoftMulDiv">
<property name="text">
<string>Multiplication/Division</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="vu1SoftAddSub">
<property name="text">
<string>Addition/Subtraction</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="vu1SoftSqrt">
<property name="text">
<string>Float Square Root</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="vu0SoftFloat">
<property name="title">
<string>VU0 Software Float</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="1">
<widget class="QCheckBox" name="vu0SoftMulDiv">
<property name="text">
<string>Multiplication/Division</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="vu0SoftAddSub">
<property name="text">
<string>Addition/Subtraction</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="vu0SoftSqrt">
<property name="text">
<string>Square Root</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="vu1RoundingMode">
<item>
<property name="text">
<string>Nearest</string>
</property>
</item>
<item>
<property name="text">
<string>Negative</string>
</property>
</item>
<item>
<property name="text">
<string>Positive</string>
</property>
</item>
<item>
<property name="text">
<string>Chop/Zero (Default)</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="vu0ClampMode">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Normal (Default)</string>
</property>
</item>
<item>
<property name="text">
<string>Extra</string>
</property>
</item>
<item>
<property name="text">
<string>Extra + Preserve Sign</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0" colspan="2">
<layout class="QGridLayout" name="vuSettingsLayout">
<item row="1" column="0">
<widget class="QCheckBox" name="vuFlagHack">
@ -281,30 +433,6 @@
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="vu0ClampMode">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Normal (Default)</string>
</property>
</item>
<item>
<property name="text">
<string>Extra</string>
</property>
</item>
<item>
<property name="text">
<string>Extra + Preserve Sign</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="vu0ClampLabel">
<property name="text">
@ -312,45 +440,7 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="vu0RoundingLabel">
<property name="text">
<string>VU0 Rounding Mode:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="vu1ClampLabel">
<property name="text">
<string>VU1 Clamping Mode:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="vu1RoundingMode">
<item>
<property name="text">
<string>Nearest</string>
</property>
</item>
<item>
<property name="text">
<string>Negative</string>
</property>
</item>
<item>
<property name="text">
<string>Positive</string>
</property>
</item>
<item>
<property name="text">
<string>Chop/Zero (Default)</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="vu1ClampMode">
<item>
<property name="text">

View File

@ -93,6 +93,7 @@ set(pcsx2Sources
MTGS.cpp
MTVU.cpp
Patch.cpp
Ps2Float.cpp
Pcsx2Config.cpp
PerformanceMetrics.cpp
PrecompiledHeader.cpp
@ -173,6 +174,7 @@ set(pcsx2Headers
MTVU.h
Memory.h
MemoryTypes.h
Ps2Float.h
Patch.h
PerformanceMetrics.h
PrecompiledHeader.h

View File

@ -596,17 +596,32 @@ struct Pcsx2Config
vu0ExtraOverflow : 1,
vu0SignOverflow : 1,
vu0Underflow : 1;
bool
vu0SoftAddSub : 1,
vu0SoftMulDiv : 1,
vu0SoftSqrt : 1;
bool
vu1Overflow : 1,
vu1ExtraOverflow : 1,
vu1SignOverflow : 1,
vu1Underflow : 1;
bool
vu1SoftAddSub : 1,
vu1SoftMulDiv : 1,
vu1SoftSqrt : 1;
bool
fpuOverflow : 1,
fpuExtraOverflow : 1,
fpuFullMode : 1;
bool
fpuSoftAddSub : 1,
fpuSoftMulDiv : 1,
fpuSoftSqrt : 1;
bool
EnableEECache : 1;
@ -1426,11 +1441,19 @@ namespace EmuFolders
#define CHECK_VU_SIGN_OVERFLOW(vunum) (((vunum) == 0) ? EmuConfig.Cpu.Recompiler.vu0SignOverflow : EmuConfig.Cpu.Recompiler.vu1SignOverflow)
#define CHECK_VU_UNDERFLOW(vunum) (((vunum) == 0) ? EmuConfig.Cpu.Recompiler.vu0Underflow : EmuConfig.Cpu.Recompiler.vu1Underflow)
#define CHECK_VU_SOFT_ADDSUB(vunum) (((vunum) == 0) ? EmuConfig.Cpu.Recompiler.vu0SoftAddSub : EmuConfig.Cpu.Recompiler.vu1SoftAddSub)
#define CHECK_VU_SOFT_MULDIV(vunum) (((vunum) == 0) ? EmuConfig.Cpu.Recompiler.vu0SoftMulDiv : EmuConfig.Cpu.Recompiler.vu1SoftMulDiv)
#define CHECK_VU_SOFT_SQRT(vunum) (((vunum) == 0) ? EmuConfig.Cpu.Recompiler.vu0SoftSqrt : EmuConfig.Cpu.Recompiler.vu1SoftSqrt)
#define CHECK_FPU_OVERFLOW (EmuConfig.Cpu.Recompiler.fpuOverflow)
#define CHECK_FPU_EXTRA_OVERFLOW (EmuConfig.Cpu.Recompiler.fpuExtraOverflow) // If enabled, Operands are checked for infinities before being used in the FPU recs
#define CHECK_FPU_EXTRA_FLAGS 1 // Always enabled now // Sets D/I flags on FPU instructions
#define CHECK_FPU_FULL (EmuConfig.Cpu.Recompiler.fpuFullMode)
#define CHECK_FPU_SOFT_ADDSUB (EmuConfig.Cpu.Recompiler.fpuSoftAddSub)
#define CHECK_FPU_SOFT_MULDIV (EmuConfig.Cpu.Recompiler.fpuSoftMulDiv)
#define CHECK_FPU_SOFT_SQRT (EmuConfig.Cpu.Recompiler.fpuSoftSqrt)
//------------ EE Recompiler defines - Comment to disable a recompiler ---------------
#define SHIFT_RECOMPILE // Speed majorly reduced if disabled

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "Ps2Float.h"
#include <cmath>
// Helper Macros
@ -63,28 +63,57 @@
// If we have an infinity value, then Overflow has occured.
bool checkOverflow(u32& xReg, u32 cFlagsToSet)
{
if ((xReg & ~0x80000000) == PosInfinity) {
/*Console.Warning( "FPU OVERFLOW!: Changing to +/-Fmax!!!!!!!!!!!!\n" );*/
xReg = (xReg & 0x80000000) | posFmax;
_ContVal_ |= (cFlagsToSet);
return true;
if (CHECK_FPU_SOFT_ADDSUB || CHECK_FPU_SOFT_MULDIV || CHECK_FPU_SOFT_SQRT)
{
if (xReg == Ps2Float::MAX_FLOATING_POINT_VALUE || xReg == Ps2Float::MIN_FLOATING_POINT_VALUE)
{
_ContVal_ |= (cFlagsToSet);
return true;
}
else if (cFlagsToSet & FPUflagO)
_ContVal_ &= ~FPUflagO;
}
else
{
if ((xReg & ~0x80000000) == PosInfinity)
{
/*Console.Warning( "FPU OVERFLOW!: Changing to +/-Fmax!!!!!!!!!!!!\n" );*/
xReg = (xReg & 0x80000000) | posFmax;
_ContVal_ |= (cFlagsToSet);
return true;
}
else if (cFlagsToSet & FPUflagO)
_ContVal_ &= ~FPUflagO;
}
else if (cFlagsToSet & FPUflagO)
_ContVal_ &= ~FPUflagO;
return false;
}
// If we have a denormal value, then Underflow has occured.
bool checkUnderflow(u32& xReg, u32 cFlagsToSet) {
if ( ( (xReg & 0x7F800000) == 0 ) && ( (xReg & 0x007FFFFF) != 0 ) ) {
/*Console.Warning( "FPU UNDERFLOW!: Changing to +/-0!!!!!!!!!!!!\n" );*/
xReg &= 0x80000000;
_ContVal_ |= (cFlagsToSet);
return true;
if (CHECK_FPU_SOFT_ADDSUB || CHECK_FPU_SOFT_MULDIV || CHECK_FPU_SOFT_SQRT)
{
if (Ps2Float(xReg).IsDenormalized())
{
_ContVal_ |= (cFlagsToSet);
return true;
}
else if (cFlagsToSet & FPUflagU)
_ContVal_ &= ~FPUflagU;
}
else
{
if (((xReg & 0x7F800000) == 0) && ((xReg & 0x007FFFFF) != 0))
{
/*Console.Warning( "FPU UNDERFLOW!: Changing to +/-0!!!!!!!!!!!!\n" );*/
xReg &= 0x80000000;
_ContVal_ |= (cFlagsToSet);
return true;
}
else if (cFlagsToSet & FPUflagU)
_ContVal_ &= ~FPUflagU;
}
else if (cFlagsToSet & FPUflagU)
_ContVal_ &= ~FPUflagU;
return false;
}
@ -106,9 +135,36 @@ __fi u32 fp_min(u32 a, u32 b)
*/
bool checkDivideByZero(u32& xReg, u32 yDivisorReg, u32 zDividendReg, u32 cFlagsToSet1, u32 cFlagsToSet2) {
if ( (yDivisorReg & 0x7F800000) == 0 ) {
_ContVal_ |= ( (zDividendReg & 0x7F800000) == 0 ) ? cFlagsToSet2 : cFlagsToSet1;
xReg = ( (yDivisorReg ^ zDividendReg) & 0x80000000 ) | posFmax;
if (CHECK_FPU_SOFT_ADDSUB || CHECK_FPU_SOFT_MULDIV || CHECK_FPU_SOFT_SQRT)
{
Ps2Float yMatrix = Ps2Float(yDivisorReg);
Ps2Float zMatrix = Ps2Float(zDividendReg);
if (yMatrix.IsZero())
{
bool dividendZero = zMatrix.IsZero();
_ContVal_ |= dividendZero ? cFlagsToSet2 : cFlagsToSet1;
bool IsSigned = yMatrix.Sign ^ zMatrix.Sign;
if (dividendZero)
xReg = IsSigned ? Ps2Float::MIN_FLOATING_POINT_VALUE : Ps2Float::MAX_FLOATING_POINT_VALUE;
else
{
Ps2Float zeroRes = Ps2Float(0);
zeroRes.Sign = IsSigned;
xReg = zeroRes.AsUInt32();
}
return true;
}
}
else if ((yDivisorReg & 0x7F800000) == 0)
{
_ContVal_ |= ((zDividendReg & 0x7F800000) == 0) ? cFlagsToSet2 : cFlagsToSet1;
xReg = ((yDivisorReg ^ zDividendReg) & 0x80000000) | posFmax;
return true;
}
@ -182,19 +238,60 @@ float fpuDouble(u32 f)
}
}
static __fi uint32_t fpuAccurateAddSub(u32 a, u32 b, bool issub)
{
if (CHECK_FPU_SOFT_ADDSUB)
{
if (issub)
return Ps2Float(a).Sub(Ps2Float(b), 1).AsUInt32();
else
return Ps2Float(a).Add(Ps2Float(b), 1).AsUInt32();
}
if (issub)
return std::bit_cast<u32>(fpuDouble(a) - fpuDouble(b));
else
return std::bit_cast<u32>(fpuDouble(a) + fpuDouble(b));
}
static __fi uint32_t fpuAccurateMulDiv(u32 a, u32 b, bool isdiv)
{
if (CHECK_FPU_SOFT_MULDIV)
{
if (isdiv)
return Ps2Float(a).Div(Ps2Float(b)).AsUInt32();
else
return Ps2Float(a).Mul(Ps2Float(b)).AsUInt32();
}
if (isdiv)
return std::bit_cast<u32>(fpuDouble(a) / fpuDouble(b));
else
return std::bit_cast<u32>(fpuDouble(a) * fpuDouble(b));
}
static __fi s32 double_to_int(double value)
{
if (value >= 2147483647.0)
return 2147483647LL;
if (value <= -2147483648.0)
return -2147483648LL;
return value;
}
void ABS_S() {
_FdValUl_ = _FsValUl_ & 0x7fffffff;
clearFPUFlags( FPUflagO | FPUflagU );
}
void ADD_S() {
_FdValf_ = fpuDouble( _FsValUl_ ) + fpuDouble( _FtValUl_ );
_FdValUl_ = fpuAccurateAddSub(_FsValUl_, _FtValUl_, 0);
if (checkOverflow( _FdValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FdValUl_, FPUflagU | FPUflagSU);
}
void ADDA_S() {
_FAValf_ = fpuDouble( _FsValUl_ ) + fpuDouble( _FtValUl_ );
_FAValUl_ = fpuAccurateAddSub(_FsValUl_, _FtValUl_, 0);
if (checkOverflow( _FAValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FAValUl_, FPUflagU | FPUflagSU);
}
@ -253,14 +350,30 @@ void CVT_S() {
}
void CVT_W() {
if ( ( _FsValUl_ & 0x7F800000 ) <= 0x4E800000 ) { _FdValSl_ = (s32)_FsValf_; }
else if ( ( _FsValUl_ & 0x80000000 ) == 0 ) { _FdValUl_ = 0x7fffffff; }
else { _FdValUl_ = 0x80000000; }
if (CHECK_FPU_SOFT_ADDSUB || CHECK_FPU_SOFT_MULDIV || CHECK_FPU_SOFT_SQRT)
{
_FdValSl_ = double_to_int(Ps2Float(_FsValUl_).ToDouble());
}
else
{
if ((_FsValUl_ & 0x7F800000) <= 0x4E800000)
{
_FdValSl_ = (s32)_FsValf_;
}
else if ((_FsValUl_ & 0x80000000) == 0)
{
_FdValUl_ = 0x7fffffff;
}
else
{
_FdValUl_ = 0x80000000;
}
}
}
void DIV_S() {
if (checkDivideByZero( _FdValUl_, _FtValUl_, _FsValUl_, FPUflagD | FPUflagSD, FPUflagI | FPUflagSI)) return;
_FdValf_ = fpuDouble( _FsValUl_ ) / fpuDouble( _FtValUl_ );
_FdValUl_ = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 1);
if (checkOverflow( _FdValUl_, 0)) return;
checkUnderflow( _FdValUl_, 0);
}
@ -271,14 +384,16 @@ void DIV_S() {
*/
void MADD_S() {
FPRreg temp;
temp.f = fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
_FdValf_ = fpuDouble( _FAValUl_ ) + fpuDouble( temp.UL );
temp.UL = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
_FdValUl_ = fpuAccurateAddSub(_FAValUl_, temp.UL, 0);
if (checkOverflow( _FdValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FdValUl_, FPUflagU | FPUflagSU);
}
void MADDA_S() {
_FAValf_ += fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
FPRreg temp;
temp.UL = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
_FAValUl_ = fpuAccurateAddSub(_FAValUl_, temp.UL, 0);
if (checkOverflow( _FAValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FAValUl_, FPUflagU | FPUflagSU);
}
@ -304,14 +419,16 @@ void MOV_S() {
void MSUB_S() {
FPRreg temp;
temp.f = fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
_FdValf_ = fpuDouble( _FAValUl_ ) - fpuDouble( temp.UL );
temp.UL = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
_FdValUl_ = fpuAccurateAddSub(_FAValUl_, temp.UL, 1);
if (checkOverflow( _FdValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FdValUl_, FPUflagU | FPUflagSU);
}
void MSUBA_S() {
_FAValf_ -= fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
FPRreg temp;
temp.UL = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
_FAValUl_ = fpuAccurateAddSub(_FAValUl_, temp.UL, 1);
if (checkOverflow( _FAValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FAValUl_, FPUflagU | FPUflagSU);
}
@ -321,13 +438,13 @@ void MTC1() {
}
void MUL_S() {
_FdValf_ = fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
_FdValUl_ = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
if (checkOverflow( _FdValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FdValUl_, FPUflagU | FPUflagSU);
}
void MULA_S() {
_FAValf_ = fpuDouble( _FsValUl_ ) * fpuDouble( _FtValUl_ );
_FAValUl_ = fpuAccurateMulDiv(_FsValUl_, _FtValUl_, 0);
if (checkOverflow( _FAValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FAValUl_, FPUflagU | FPUflagSU);
}
@ -341,17 +458,45 @@ void RSQRT_S() {
FPRreg temp;
clearFPUFlags(FPUflagD | FPUflagI);
if ( ( _FtValUl_ & 0x7F800000 ) == 0 ) { // Ft is zero (Denormals are Zero)
_ContVal_ |= FPUflagD | FPUflagSD;
_FdValUl_ = ( _FtValUl_ & 0x80000000 ) | posFmax;
return;
if (CHECK_FPU_SOFT_SQRT)
{
Ps2Float value = Ps2Float(_FtValUl_);
if (value.IsDenormalized())
{
_ContVal_ |= FPUflagD | FPUflagSD;
_FdValUl_ = value.Sign ? Ps2Float::MIN_FLOATING_POINT_VALUE : Ps2Float::MAX_FLOATING_POINT_VALUE;
return;
}
else if (_FtValUl_ & 0x80000000)
{ // Ft is negative
_ContVal_ |= FPUflagI | FPUflagSI;
_FdValUl_ = Ps2Float(_FsValUl_).Rsqrt(Ps2Float(value.Abs())).AsUInt32();
}
else
{
_FdValUl_ = Ps2Float(_FsValUl_).Rsqrt(value).AsUInt32();
} // Ft is positive and not zero
}
else if ( _FtValUl_ & 0x80000000 ) { // Ft is negative
_ContVal_ |= FPUflagI | FPUflagSI;
temp.f = sqrt( fabs( fpuDouble( _FtValUl_ ) ) );
_FdValf_ = fpuDouble( _FsValUl_ ) / fpuDouble( temp.UL );
else
{
if ((_FtValUl_ & 0x7F800000) == 0)
{ // Ft is zero (Denormals are Zero)
_ContVal_ |= FPUflagD | FPUflagSD;
_FdValUl_ = (_FtValUl_ & 0x80000000) | posFmax;
return;
}
else if (_FtValUl_ & 0x80000000)
{ // Ft is negative
_ContVal_ |= FPUflagI | FPUflagSI;
temp.f = sqrt(fabs(fpuDouble(_FtValUl_)));
_FdValf_ = fpuDouble(_FsValUl_) / fpuDouble(temp.UL);
}
else
{
_FdValf_ = fpuDouble(_FsValUl_) / sqrt(fpuDouble(_FtValUl_));
} // Ft is positive and not zero
}
else { _FdValf_ = fpuDouble( _FsValUl_ ) / sqrt( fpuDouble( _FtValUl_ ) ); } // Ft is positive and not zero
if (checkOverflow( _FdValUl_, 0)) return;
checkUnderflow( _FdValUl_, 0);
@ -360,23 +505,40 @@ void RSQRT_S() {
void SQRT_S() {
clearFPUFlags(FPUflagI | FPUflagD);
if ( ( _FtValUl_ & 0x7F800000 ) == 0 ) // If Ft = +/-0
_FdValUl_ = _FtValUl_ & 0x80000000;// result is 0
else if ( _FtValUl_ & 0x80000000 ) { // If Ft is Negative
_ContVal_ |= FPUflagI | FPUflagSI;
_FdValf_ = sqrt( fabs( fpuDouble( _FtValUl_ ) ) );
} else
_FdValf_ = sqrt( fpuDouble( _FtValUl_ ) ); // If Ft is Positive
if (CHECK_FPU_SOFT_SQRT)
{
Ps2Float value = Ps2Float(_FtValUl_);
if (_FtValUl_ & 0x80000000)
{ // If Ft is Negative
_ContVal_ |= FPUflagI | FPUflagSI;
_FdValUl_ = Ps2Float(value.Abs()).Sqrt().AsUInt32();
}
else
_FdValUl_ = value.Sqrt().AsUInt32(); // If Ft is Positive
}
else
{
if ((_FtValUl_ & 0x7F800000) == 0) // If Ft = +/-0
_FdValUl_ = _FtValUl_ & 0x80000000; // result is 0
else if (_FtValUl_ & 0x80000000)
{ // If Ft is Negative
_ContVal_ |= FPUflagI | FPUflagSI;
_FdValf_ = sqrt(fabs(fpuDouble(_FtValUl_)));
}
else
_FdValf_ = sqrt(fpuDouble(_FtValUl_)); // If Ft is Positive
}
}
void SUB_S() {
_FdValf_ = fpuDouble( _FsValUl_ ) - fpuDouble( _FtValUl_ );
_FdValUl_ = fpuAccurateAddSub(_FsValUl_, _FtValUl_, 1);
if (checkOverflow( _FdValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FdValUl_, FPUflagU | FPUflagSU);
}
void SUBA_S() {
_FAValf_ = fpuDouble( _FsValUl_ ) - fpuDouble( _FtValUl_ );
_FAValUl_ = fpuAccurateAddSub(_FsValUl_, _FtValUl_, 1);
if (checkOverflow( _FAValUl_, FPUflagO | FPUflagSO)) return;
checkUnderflow( _FAValUl_, FPUflagU | FPUflagSU);
}

View File

@ -536,14 +536,27 @@ void Pcsx2Config::RecompilerOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBool(vu0ExtraOverflow);
SettingsWrapBitBool(vu0SignOverflow);
SettingsWrapBitBool(vu0Underflow);
SettingsWrapBitBool(vu0SoftAddSub);
SettingsWrapBitBool(vu0SoftMulDiv);
SettingsWrapBitBool(vu0SoftSqrt);
SettingsWrapBitBool(vu1Overflow);
SettingsWrapBitBool(vu1ExtraOverflow);
SettingsWrapBitBool(vu1SignOverflow);
SettingsWrapBitBool(vu1Underflow);
SettingsWrapBitBool(vu1SoftAddSub);
SettingsWrapBitBool(vu1SoftMulDiv);
SettingsWrapBitBool(vu1SoftSqrt);
SettingsWrapBitBool(fpuOverflow);
SettingsWrapBitBool(fpuExtraOverflow);
SettingsWrapBitBool(fpuFullMode);
SettingsWrapBitBool(fpuSoftAddSub);
SettingsWrapBitBool(fpuSoftMulDiv);
SettingsWrapBitBool(fpuSoftSqrt);
}
u32 Pcsx2Config::RecompilerOptions::GetEEClampMode() const

865
pcsx2/Ps2Float.cpp Normal file
View File

@ -0,0 +1,865 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include <stdexcept>
#include <cmath>
#include <string>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <bit>
#include "Ps2Float.h"
#include "Common.h"
const uint8_t Ps2Float::BIAS = 127;
const uint32_t Ps2Float::SIGNMASK = 0x80000000;
const uint32_t Ps2Float::MAX_FLOATING_POINT_VALUE = 0x7FFFFFFF;
const uint32_t Ps2Float::MIN_FLOATING_POINT_VALUE = 0xFFFFFFFF;
const uint32_t Ps2Float::POSITIVE_INFINITY_VALUE = 0x7F800000;
const uint32_t Ps2Float::NEGATIVE_INFINITY_VALUE = 0xFF800000;
const uint32_t Ps2Float::ONE = 0x3F800000;
const uint32_t Ps2Float::MIN_ONE = 0xBF800000;
const int32_t Ps2Float::IMPLICIT_LEADING_BIT_POS = 23;
Ps2Float::Ps2Float(uint32_t value)
: Sign((value >> 31) & 1)
, Exponent((uint8_t)(((value >> 23) & 0xFF)))
, Mantissa(value & 0x7FFFFF)
{
}
Ps2Float::Ps2Float(bool sign, uint8_t exponent, uint32_t mantissa)
: Sign(sign)
, Exponent(exponent)
, Mantissa(mantissa)
{
}
Ps2Float Ps2Float::Max()
{
return Ps2Float(MAX_FLOATING_POINT_VALUE);
}
Ps2Float Ps2Float::Min()
{
return Ps2Float(MIN_FLOATING_POINT_VALUE);
}
Ps2Float Ps2Float::One()
{
return Ps2Float(ONE);
}
Ps2Float Ps2Float::MinOne()
{
return Ps2Float(MIN_ONE);
}
uint32_t Ps2Float::AsUInt32() const
{
uint32_t result = 0;
result |= (Sign ? 1u : 0u) << 31;
result |= (uint32_t)(Exponent << 23);
result |= Mantissa;
return result;
}
Ps2Float Ps2Float::Add(Ps2Float addend, bool COP1)
{
if (IsDenormalized() || addend.IsDenormalized())
return SolveAddSubDenormalizedOperation(*this, addend, true);
if (IsAbnormal() && addend.IsAbnormal())
return SolveAbnormalAdditionOrSubtractionOperation(*this, addend, true, COP1);
uint32_t a = AsUInt32();
uint32_t b = addend.AsUInt32();
int32_t temp = 0;
//exponent difference
int exp_diff = ((a >> 23) & 0xff) - ((b >> 23) & 0xff);
//diff = 25 .. 255 , expt < expd
if (exp_diff >= 25)
{
b = b & Ps2Float::SIGNMASK;
}
//diff = 1 .. 24, expt < expd
else if (exp_diff > 0)
{
exp_diff = exp_diff - 1;
temp = 0xffffffff << exp_diff;
b = temp & b;
}
//diff = -255 .. -25, expd < expt
else if (exp_diff <= -25)
{
a = a & Ps2Float::SIGNMASK;
}
//diff = -24 .. -1 , expd < expt
else if (exp_diff < 0)
{
exp_diff = -exp_diff;
exp_diff = exp_diff - 1;
temp = 0xffffffff << exp_diff;
a = a & temp;
}
return Ps2Float(a).DoAdd(Ps2Float(b));
}
Ps2Float Ps2Float::Sub(Ps2Float subtrahend, bool COP1)
{
if (IsDenormalized() || subtrahend.IsDenormalized())
return SolveAddSubDenormalizedOperation(*this, subtrahend, false);
if (IsAbnormal() && subtrahend.IsAbnormal())
return SolveAbnormalAdditionOrSubtractionOperation(*this, subtrahend, false, COP1);
uint32_t a = AsUInt32();
uint32_t b = subtrahend.AsUInt32();
int32_t temp = 0;
//exponent difference
int exp_diff = ((a >> 23) & 0xff) - ((b >> 23) & 0xff);
//diff = 25 .. 255 , expt < expd
if (exp_diff >= 25)
{
b = b & Ps2Float::SIGNMASK;
}
//diff = 1 .. 24, expt < expd
else if (exp_diff > 0)
{
exp_diff = exp_diff - 1;
temp = 0xffffffff << exp_diff;
b = temp & b;
}
//diff = -255 .. -25, expd < expt
else if (exp_diff <= -25)
{
a = a & Ps2Float::SIGNMASK;
}
//diff = -24 .. -1 , expd < expt
else if (exp_diff < 0)
{
exp_diff = -exp_diff;
exp_diff = exp_diff - 1;
temp = 0xffffffff << exp_diff;
a = a & temp;
}
return Ps2Float(a).DoAdd(Neg(Ps2Float(b)));
}
Ps2Float Ps2Float::Mul(Ps2Float mulend)
{
if (IsDenormalized() || mulend.IsDenormalized())
return SolveMultiplicationDenormalizedOperation(*this, mulend);
if (IsAbnormal() && mulend.IsAbnormal())
return SolveAbnormalMultiplicationOrDivisionOperation(*this, mulend, true);
if (IsZero() || mulend.IsZero())
{
Ps2Float result = Ps2Float(0);
result.Sign = DetermineMultiplicationDivisionOperationSign(*this, mulend);
return result;
}
return DoMul(mulend);
}
Ps2Float Ps2Float::Div(Ps2Float divend)
{
if (IsDenormalized() || divend.IsDenormalized())
return SolveDivisionDenormalizedOperation(*this, divend);
if (IsAbnormal() && divend.IsAbnormal())
return SolveAbnormalMultiplicationOrDivisionOperation(*this, divend, false);
if (IsZero())
{
Ps2Float result = Ps2Float(0);
result.Sign = DetermineMultiplicationDivisionOperationSign(*this, divend);
return result;
}
else if (divend.IsZero())
return DetermineMultiplicationDivisionOperationSign(*this, divend) ? Min() : Max();
return DoDiv(divend);
}
Ps2Float Ps2Float::Sqrt()
{
int32_t t;
int32_t s = 0;
int32_t q = 0;
uint32_t r = 0x01000000; /* r = moving bit from right to left */
if (IsDenormalized())
return Ps2Float(0);
// PS2 only takes positive numbers for SQRT, and convert if necessary.
int32_t ix = (int32_t)(Ps2Float(false, Exponent, Mantissa).AsUInt32());
/* extract mantissa and unbias exponent */
int32_t m = (ix >> 23) - BIAS;
ix = (ix & 0x007fffff) | 0x00800000;
if ((m & 1) == 1)
{
/* odd m, double x to make it even */
ix += ix;
}
m >>= 1; /* m = [m/2] */
/* generate sqrt(x) bit by bit */
ix += ix;
while (r != 0)
{
t = s + (int32_t)(r);
if (t <= ix)
{
s = t + (int32_t)(r);
ix -= t;
q += (int32_t)(r);
}
ix += ix;
r >>= 1;
}
/* use floating add to find out rounding direction */
if (ix != 0)
{
q += q & 1;
}
ix = (q >> 1) + 0x3f000000;
ix += m << 23;
return Ps2Float((uint32_t)(ix));
}
Ps2Float Ps2Float::Rsqrt(Ps2Float other)
{
return Div(other.Sqrt());
}
bool Ps2Float::IsDenormalized()
{
return Exponent == 0;
}
bool Ps2Float::IsAbnormal()
{
uint32_t val = AsUInt32();
return val == MAX_FLOATING_POINT_VALUE || val == MIN_FLOATING_POINT_VALUE ||
val == POSITIVE_INFINITY_VALUE || val == NEGATIVE_INFINITY_VALUE;
}
bool Ps2Float::IsZero()
{
return (Abs()) == 0;
}
uint32_t Ps2Float::Abs()
{
return (AsUInt32() & MAX_FLOATING_POINT_VALUE);
}
Ps2Float Ps2Float::RoundTowardsZero()
{
return Ps2Float((uint32_t)(std::trunc((double)(AsUInt32()))));
}
int32_t Ps2Float::CompareTo(Ps2Float other)
{
int32_t selfTwoComplementVal = (int32_t)(Abs());
if (Sign)
selfTwoComplementVal = -selfTwoComplementVal;
int32_t otherTwoComplementVal = (int32_t)(other.Abs());
if (other.Sign)
otherTwoComplementVal = -otherTwoComplementVal;
if (selfTwoComplementVal < otherTwoComplementVal)
return -1;
else if (selfTwoComplementVal == otherTwoComplementVal)
return 0;
else
return 1;
}
double Ps2Float::ToDouble()
{
return std::bit_cast<double>(((u64)Sign << 63) | ((((u64)Exponent - BIAS) + 1023ULL) << 52) | ((u64)Mantissa << 29));
}
std::string Ps2Float::ToString()
{
double res = ToDouble();
uint32_t value = AsUInt32();
std::ostringstream oss;
oss << std::fixed << std::setprecision(6);
if (IsDenormalized())
{
oss << "Denormalized(" << res << ")";
}
else if (value == MAX_FLOATING_POINT_VALUE)
{
oss << "Fmax(" << res << ")";
}
else if (value == MIN_FLOATING_POINT_VALUE)
{
oss << "-Fmax(" << res << ")";
}
else if (value == POSITIVE_INFINITY_VALUE)
{
oss << "Inf(" << res << ")";
}
else if (value == NEGATIVE_INFINITY_VALUE)
{
oss << "-Inf(" << res << ")";
}
else
{
oss << "Ps2Float(" << res << ")";
}
return oss.str();
}
Ps2Float Ps2Float::DoAdd(Ps2Float other)
{
const uint8_t roundingMultiplier = 6;
uint8_t selfExponent = Exponent;
int32_t resExponent = selfExponent - other.Exponent;
if (resExponent < 0)
return other.DoAdd(*this);
else if (resExponent >= 25)
return *this;
// http://graphics.stanford.edu/~seander/bithacks.html#ConditionalNegate
uint32_t sign1 = (uint32_t)((int32_t)AsUInt32() >> 31);
int32_t selfMantissa = (int32_t)(((Mantissa | 0x800000) ^ sign1) - sign1);
uint32_t sign2 = (uint32_t)((int32_t)other.AsUInt32() >> 31);
int32_t otherMantissa = (int32_t)(((other.Mantissa | 0x800000) ^ sign2) - sign2);
// PS2 multiply by 2 before doing the Math here.
int32_t man = (selfMantissa << roundingMultiplier) + ((otherMantissa << roundingMultiplier) >> resExponent);
int32_t absMan = abs(man);
if (absMan == 0)
return Ps2Float(0);
// Remove from exponent the PS2 Multiplier value.
int32_t rawExp = selfExponent - roundingMultiplier;
int32_t amount = normalizeAmounts[clz(absMan)];
rawExp -= amount;
absMan <<= amount;
int32_t msbIndex = BitScanReverse8(absMan >> 23);
rawExp += msbIndex;
absMan >>= msbIndex;
if (rawExp > 255)
return man < 0 ? Min() : Max();
else if (rawExp <= 0)
return Ps2Float(man < 0, 0, 0);
return Ps2Float((uint32_t)man & Ps2Float::SIGNMASK | (uint32_t)rawExp << 23 | ((uint32_t)absMan & 0x7FFFFF)).RoundTowardsZero();
}
Ps2Float Ps2Float::DoMul(Ps2Float other)
{
uint32_t selfMantissa = Mantissa | 0x800000;
uint32_t otherMantissa = other.Mantissa | 0x800000;
int32_t resExponent = Exponent + other.Exponent - BIAS;
Ps2Float result = Ps2Float(0);
result.Sign = DetermineMultiplicationDivisionOperationSign(*this, other);
if (resExponent > 255)
return result.Sign ? Min() : Max();
else if (resExponent <= 0)
return Ps2Float(result.Sign, 0, 0);
uint32_t testImprecision = otherMantissa ^ ((otherMantissa >> 4) & 0x800); // For some reason, 0x808000 loses a bit and 0x800800 loses a bit, but 0x808800 does not
int64_t res = 0;
uint64_t mask = 0xFFFFFFFFFFFFFFFF;
result.Exponent = (uint8_t)(resExponent);
otherMantissa <<= 1;
uint32_t part[13]; // Partial products
uint32_t bit[13]; // More partial products. 0 or 1.
for (int i = 0; i <= 12; i++, otherMantissa >>= 2)
{
uint32_t test = otherMantissa & 7;
if (test == 0 || test == 7)
{
part[i] = 0;
bit[i] = 0;
}
else if (test == 3)
{
part[i] = (selfMantissa << 1);
bit[i] = 0;
}
else if (test == 4)
{
part[i] = ~(selfMantissa << 1);
bit[i] = 1;
}
else if (test < 4)
{
part[i] = selfMantissa;
bit[i] = 0;
}
else
{
part[i] = ~selfMantissa;
bit[i] = 1;
}
}
for (int i = 0; i <= 12; i++)
{
res += (uint64_t)(int32_t)part[i] << (i * 2);
res &= mask;
res += bit[i] << (i * 2);
}
result.Mantissa = (uint32_t)(res >> 23);
if ((testImprecision & 0x000aaa) && !(res & 0x7FFFFF))
result.Mantissa -= 1;
if (result.Mantissa > 0)
{
int32_t leadingBitPosition = Ps2Float::GetMostSignificantBitPosition(result.Mantissa);
while (leadingBitPosition != IMPLICIT_LEADING_BIT_POS)
{
if (leadingBitPosition > IMPLICIT_LEADING_BIT_POS)
{
result.Mantissa >>= 1;
int32_t exp = ((int32_t)result.Exponent + 1);
if (exp > 255)
return result.Sign ? Min() : Max();
result.Exponent = (uint8_t)exp;
leadingBitPosition--;
}
else if (leadingBitPosition < IMPLICIT_LEADING_BIT_POS)
{
result.Mantissa <<= 1;
int32_t exp = ((int32_t)result.Exponent - 1);
if (exp <= 0)
return Ps2Float(result.Sign, 0, 0);
result.Exponent = (uint8_t)exp;
leadingBitPosition++;
}
}
}
result.Mantissa &= 0x7FFFFF;
return result.RoundTowardsZero();
}
Ps2Float Ps2Float::DoDiv(Ps2Float other)
{
uint64_t selfMantissa64;
uint32_t selfMantissa = Mantissa | 0x800000;
uint32_t otherMantissa = other.Mantissa | 0x800000;
int resExponent = Exponent - other.Exponent + BIAS;
Ps2Float result = Ps2Float(0);
result.Sign = DetermineMultiplicationDivisionOperationSign(*this, other);
if (resExponent > 255)
return result.Sign ? Min() : Max();
else if (resExponent <= 0)
return Ps2Float(result.Sign, 0, 0);
if (selfMantissa < otherMantissa)
{
--resExponent;
if (resExponent == 0)
return Ps2Float(result.Sign, 0, 0);
selfMantissa64 = (uint64_t)(selfMantissa) << 31;
}
else
{
selfMantissa64 = (uint64_t)(selfMantissa) << 30;
}
uint32_t resMantissa = (uint32_t)(selfMantissa64 / otherMantissa);
if ((resMantissa & 0x3F) == 0)
resMantissa |= ((uint64_t)(otherMantissa)*resMantissa != selfMantissa64) ? 1U : 0;
result.Exponent = (uint8_t)(resExponent);
result.Mantissa = (resMantissa + 0x39U /* Non-standard value, 40U in IEEE754 (PS2: rsqrt(0x40400000, 0x40400000) = 0x3FDDB3D7 -> IEEE754: rsqrt(0x40400000, 0x40400000) = 0x3FDDB3D8 */) >> 7;
if (result.Mantissa > 0)
{
int32_t leadingBitPosition = Ps2Float::GetMostSignificantBitPosition(result.Mantissa);
while (leadingBitPosition != IMPLICIT_LEADING_BIT_POS)
{
if (leadingBitPosition > IMPLICIT_LEADING_BIT_POS)
{
result.Mantissa >>= 1;
int32_t exp = ((int32_t)result.Exponent + 1);
if (exp > 255)
return result.Sign ? Min() : Max();
result.Exponent = (uint8_t)exp;
leadingBitPosition--;
}
else if (leadingBitPosition < IMPLICIT_LEADING_BIT_POS)
{
result.Mantissa <<= 1;
int32_t exp = ((int32_t)result.Exponent - 1);
if (exp <= 0)
return Ps2Float(result.Sign, 0, 0);
result.Exponent = (uint8_t)exp;
leadingBitPosition++;
}
}
}
result.Mantissa &= 0x7FFFFF;
return result.RoundTowardsZero();
}
Ps2Float Ps2Float::SolveAbnormalAdditionOrSubtractionOperation(Ps2Float a, Ps2Float b, bool add, bool COP1)
{
uint32_t aval = a.AsUInt32();
uint32_t bval = b.AsUInt32();
if (aval == MAX_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return add ? Max() : Ps2Float(0);
if (aval == MIN_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return add ? Min() : Ps2Float(0);
if (aval == MIN_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return COP1 ? Min() : (add ? Ps2Float(0) : Min());
if (aval == MAX_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return COP1 ? Max() : (add ? Ps2Float(0) : Max());
if (aval == POSITIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return add ? Max() : Ps2Float(0);
if (aval == NEGATIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return COP1 ? Min() : (add ? Ps2Float(0) : Min());
if (aval == POSITIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return COP1 ? Max() : (add ? Ps2Float(0) : Max());
if (aval == NEGATIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return add ? Min() : Ps2Float(0);
if (aval == MAX_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return add ? Max() : Ps2Float(0x7F7FFFFE);
if (aval == MAX_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return add ? Ps2Float(0x7F7FFFFE) : Max();
if (aval == MIN_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return add ? Ps2Float(0xFF7FFFFE) : Min();
if (aval == MIN_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return add ? Min() : Ps2Float(0xFF7FFFFE);
if (aval == POSITIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return add ? Max() : Ps2Float(0xFF7FFFFE);
if (aval == POSITIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return add ? Ps2Float(0xFF7FFFFE) : Max();
if (aval == NEGATIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return add ? Ps2Float(0x7F7FFFFE) : Min();
if (aval == NEGATIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return add ? Min() : Ps2Float(0x7F7FFFFE);
Console.Error("Unhandled abnormal add/sub floating point operation");
}
Ps2Float Ps2Float::SolveAbnormalMultiplicationOrDivisionOperation(Ps2Float a, Ps2Float b, bool mul)
{
uint32_t aval = a.AsUInt32();
uint32_t bval = b.AsUInt32();
if (mul)
{
if ((aval == MAX_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE) ||
(aval == MIN_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE))
return Max();
if ((aval == MAX_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE) ||
(aval == MIN_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE))
return Min();
if (aval == POSITIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Max();
if (aval == NEGATIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Min();
if (aval == POSITIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Min();
if (aval == NEGATIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Max();
if (aval == MAX_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Max();
if (aval == MAX_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Min();
if (aval == MIN_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Min();
if (aval == MIN_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Max();
if (aval == POSITIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return Max();
if (aval == POSITIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return Min();
if (aval == NEGATIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return Min();
if (aval == NEGATIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return Max();
}
else
{
if ((aval == MAX_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE) ||
(aval == MIN_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE))
return One();
if ((aval == MAX_FLOATING_POINT_VALUE && bval == MIN_FLOATING_POINT_VALUE) ||
(aval == MIN_FLOATING_POINT_VALUE && bval == MAX_FLOATING_POINT_VALUE))
return MinOne();
if (aval == POSITIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return One();
if (aval == NEGATIVE_INFINITY_VALUE && bval == POSITIVE_INFINITY_VALUE)
return MinOne();
if (aval == POSITIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return MinOne();
if (aval == NEGATIVE_INFINITY_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return One();
if (aval == MAX_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Ps2Float(0x3FFFFFFF);
if (aval == MAX_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Ps2Float(0xBFFFFFFF);
if (aval == MIN_FLOATING_POINT_VALUE && bval == POSITIVE_INFINITY_VALUE)
return Ps2Float(0xBFFFFFFF);
if (aval == MIN_FLOATING_POINT_VALUE && bval == NEGATIVE_INFINITY_VALUE)
return Ps2Float(0x3FFFFFFF);
if (aval == POSITIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return Ps2Float(0x3F000001);
if (aval == POSITIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return Ps2Float(0xBF000001);
if (aval == NEGATIVE_INFINITY_VALUE && bval == MAX_FLOATING_POINT_VALUE)
return Ps2Float(0xBF000001);
if (aval == NEGATIVE_INFINITY_VALUE && bval == MIN_FLOATING_POINT_VALUE)
return Ps2Float(0x3F000001);
}
Console.Error("Unhandled abnormal mul/div floating point operation");
}
Ps2Float Ps2Float::SolveAddSubDenormalizedOperation(Ps2Float a, Ps2Float b, bool add)
{
Ps2Float result = Ps2Float(0);
if (a.IsDenormalized() && !b.IsDenormalized())
result = b;
else if (!a.IsDenormalized() && b.IsDenormalized())
result = a;
else if (a.IsDenormalized() && b.IsDenormalized())
{
}
else
Console.Error("Both numbers are not denormalized");
result.Sign = add ? DetermineAdditionOperationSign(a, b) : DetermineSubtractionOperationSign(a, b);
return result;
}
Ps2Float Ps2Float::SolveMultiplicationDenormalizedOperation(Ps2Float a, Ps2Float b)
{
Ps2Float result = Ps2Float(0);
result.Sign = DetermineMultiplicationDivisionOperationSign(a, b);
return result;
}
Ps2Float Ps2Float::SolveDivisionDenormalizedOperation(Ps2Float a, Ps2Float b)
{
bool sign = DetermineMultiplicationDivisionOperationSign(a, b);
Ps2Float result = Ps2Float(0);
if (a.IsDenormalized() && !b.IsDenormalized())
{
}
else if (!a.IsDenormalized() && b.IsDenormalized())
return sign ? Min() : Max();
else if (a.IsDenormalized() && b.IsDenormalized())
return sign ? Min() : Max();
else
Console.Error("Both numbers are not denormalized");
result.Sign = sign;
return result;
}
Ps2Float Ps2Float::Neg(Ps2Float self)
{
return Ps2Float(self.AsUInt32() ^ SIGNMASK);
}
bool Ps2Float::DetermineMultiplicationDivisionOperationSign(Ps2Float a, Ps2Float b)
{
return a.Sign ^ b.Sign;
}
bool Ps2Float::DetermineAdditionOperationSign(Ps2Float a, Ps2Float b)
{
if (a.IsZero() && b.IsZero())
{
if (!a.Sign || !b.Sign)
return false;
else if (a.Sign && b.Sign)
return true;
else
Console.Error("Unhandled addition operation flags");
}
else if (a.IsZero())
return b.Sign;
return a.Sign;
}
bool Ps2Float::DetermineSubtractionOperationSign(Ps2Float a, Ps2Float b)
{
if (a.IsZero() && b.IsZero())
{
if (!a.Sign || b.Sign)
return false;
else if (a.Sign && !b.Sign)
return true;
else
Console.Error("Unhandled subtraction operation flags");
}
else if (a.IsZero())
return !b.Sign;
else if (b.IsZero())
return a.Sign;
return a.CompareTo(b) >= 0 ? a.Sign : !b.Sign;
}
int32_t Ps2Float::clz(int x)
{
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return debruijn32[(uint)x * 0x8c0b2891u >> 26];
}
int32_t Ps2Float::BitScanReverse8(int b)
{
return msb[b];
}
int32_t Ps2Float::GetMostSignificantBitPosition(uint32_t value)
{
for (int32_t i = 31; i >= 0; i--)
{
if (((value >> i) & 1) != 0)
return i;
}
return -1;
}
const int8_t Ps2Float::msb[256] =
{
-1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7};
const int32_t Ps2Float::debruijn32[64] =
{
32, 8, 17, -1, -1, 14, -1, -1, -1, 20, -1, -1, -1, 28, -1, 18,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 26, 25, 24,
4, 11, 23, 31, 3, 7, 10, 16, 22, 30, -1, -1, 2, 6, 13, 9,
-1, 15, -1, 21, -1, 29, 19, -1, -1, -1, -1, -1, 1, 27, 5, 12};
const int32_t Ps2Float::normalizeAmounts[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24};

104
pcsx2/Ps2Float.h Normal file
View File

@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <vector>
class Ps2Float
{
public:
bool Sign;
uint8_t Exponent;
uint32_t Mantissa;
static const uint8_t BIAS;
static const uint32_t SIGNMASK;
static const uint32_t MAX_FLOATING_POINT_VALUE;
static const uint32_t MIN_FLOATING_POINT_VALUE;
static const uint32_t POSITIVE_INFINITY_VALUE;
static const uint32_t NEGATIVE_INFINITY_VALUE;
static const uint32_t ONE;
static const uint32_t MIN_ONE;
static const int IMPLICIT_LEADING_BIT_POS;
static const int8_t msb[256];
static const int32_t debruijn32[64];
static const int32_t normalizeAmounts[];
Ps2Float(uint32_t value);
Ps2Float(bool sign, uint8_t exponent, uint32_t mantissa);
static Ps2Float Max();
static Ps2Float Min();
static Ps2Float One();
static Ps2Float MinOne();
static Ps2Float Neg(Ps2Float self);
uint32_t AsUInt32() const;
Ps2Float Add(Ps2Float addend, bool COP1);
Ps2Float Sub(Ps2Float subtrahend, bool COP1);
Ps2Float Mul(Ps2Float mulend);
Ps2Float Div(Ps2Float divend);
Ps2Float Sqrt();
Ps2Float Rsqrt(Ps2Float other);
bool IsDenormalized();
bool IsAbnormal();
bool IsZero();
uint32_t Abs();
Ps2Float RoundTowardsZero();
int32_t CompareTo(Ps2Float other);
double ToDouble();
std::string ToString();
protected:
private:
Ps2Float DoAdd(Ps2Float other);
Ps2Float DoMul(Ps2Float other);
Ps2Float DoDiv(Ps2Float other);
static Ps2Float SolveAbnormalAdditionOrSubtractionOperation(Ps2Float a, Ps2Float b, bool add, bool COP1);
static Ps2Float SolveAbnormalMultiplicationOrDivisionOperation(Ps2Float a, Ps2Float b, bool mul);
static Ps2Float SolveAddSubDenormalizedOperation(Ps2Float a, Ps2Float b, bool add);
static Ps2Float SolveMultiplicationDenormalizedOperation(Ps2Float a, Ps2Float b);
static Ps2Float SolveDivisionDenormalizedOperation(Ps2Float a, Ps2Float b);
static bool DetermineMultiplicationDivisionOperationSign(Ps2Float a, Ps2Float b);
static bool DetermineAdditionOperationSign(Ps2Float a, Ps2Float b);
static bool DetermineSubtractionOperationSign(Ps2Float a, Ps2Float b);
static int32_t GetMostSignificantBitPosition(uint32_t value);
static int32_t BitScanReverse8(int32_t b);
static int32_t clz(int32_t x);
};

View File

@ -124,6 +124,7 @@ struct alignas(16) VURegs
REG_VI q;
REG_VI p;
VECTOR TMP;
uint idx; // VU index (0 or 1)
// flags/cycle are needed by VIF dma code, so they have to be here (for now)

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "Ps2Float.h"
#include <cmath>
#include <float.h>
@ -12,21 +12,22 @@
/* NEW FLAGS */ //By asadr. Thnkx F|RES :p
/*****************************************/
static __ri u32 VU_MAC_UPDATE( int shift, VURegs * VU, float f )
static __ri u32 VU_MAC_UPDATE(int shift, VURegs* VU, uint32_t f)
{
u32 v = *(u32*)&f;
int exp = (v >> 23) & 0xff;
u32 s = v & 0x80000000;
Ps2Float ps2f = Ps2Float(f);
uint exp = ps2f.Exponent;
u32 s = ps2f.AsUInt32() & Ps2Float::SIGNMASK;
if (s)
VU->macflag |= 0x0010<<shift;
else
VU->macflag &= ~(0x0010<<shift);
if( f == 0 )
if (ps2f.IsZero())
{
VU->macflag = (VU->macflag & ~(0x1100<<shift)) | (0x0001<<shift);
return v;
return f;
}
switch(exp)
@ -35,33 +36,48 @@ static __ri u32 VU_MAC_UPDATE( int shift, VURegs * VU, float f )
VU->macflag = (VU->macflag&~(0x1000<<shift)) | (0x0101<<shift);
return s;
case 255:
VU->macflag = (VU->macflag&~(0x0101<<shift)) | (0x1000<<shift);
if (CHECK_VU_OVERFLOW((VU == &VU1) ? 1 : 0))
return s | 0x7f7fffff; /* max allowed */
if (CHECK_VU_SOFT_ADDSUB((VU == &VU1) ? 1 : 0) || CHECK_VU_SOFT_MULDIV((VU == &VU1) ? 1 : 0) || CHECK_VU_SOFT_SQRT((VU == &VU1) ? 1 : 0))
{
if (f == Ps2Float::MAX_FLOATING_POINT_VALUE || f == Ps2Float::MIN_FLOATING_POINT_VALUE)
{
VU->macflag = (VU->macflag & ~(0x0101 << shift)) | (0x1000 << shift);
return f;
}
else
return f;
}
else if (CHECK_VU_OVERFLOW((VU == &VU1) ? 1 : 0))
{
VU->macflag = (VU->macflag & ~(0x0101 << shift)) | (0x1000 << shift);
return s | 0x7f7fffff; /* max IEEE754 allowed */
}
else
return v;
{
VU->macflag = (VU->macflag & ~(0x0101 << shift)) | (0x1000 << shift);
return f;
}
default:
VU->macflag = (VU->macflag & ~(0x1101<<shift));
return v;
return f;
}
}
__fi u32 VU_MACx_UPDATE(VURegs * VU, float x)
__fi u32 VU_MACx_UPDATE(VURegs * VU, uint32_t x)
{
return VU_MAC_UPDATE(3, VU, x);
}
__fi u32 VU_MACy_UPDATE(VURegs * VU, float y)
__fi u32 VU_MACy_UPDATE(VURegs* VU, uint32_t y)
{
return VU_MAC_UPDATE(2, VU, y);
}
__fi u32 VU_MACz_UPDATE(VURegs * VU, float z)
__fi u32 VU_MACz_UPDATE(VURegs* VU, uint32_t z)
{
return VU_MAC_UPDATE(1, VU, z);
}
__fi u32 VU_MACw_UPDATE(VURegs * VU, float w)
__fi u32 VU_MACw_UPDATE(VURegs* VU, uint32_t w)
{
return VU_MAC_UPDATE(0, VU, w);
}

View File

@ -4,10 +4,10 @@
#pragma once
#include "VU.h"
extern u32 VU_MACx_UPDATE(VURegs * VU, float x);
extern u32 VU_MACy_UPDATE(VURegs * VU, float y);
extern u32 VU_MACz_UPDATE(VURegs * VU, float z);
extern u32 VU_MACw_UPDATE(VURegs * VU, float w);
extern u32 VU_MACx_UPDATE(VURegs * VU, uint32_t x);
extern u32 VU_MACy_UPDATE(VURegs* VU, uint32_t y);
extern u32 VU_MACz_UPDATE(VURegs* VU, uint32_t z);
extern u32 VU_MACw_UPDATE(VURegs* VU, uint32_t w);
extern void VU_MACx_CLEAR(VURegs * VU);
extern void VU_MACy_CLEAR(VURegs * VU);
extern void VU_MACz_CLEAR(VURegs * VU);

File diff suppressed because it is too large Load Diff

View File

@ -281,6 +281,7 @@
<ClCompile Include="PINE.cpp" />
<ClCompile Include="FW.cpp" />
<ClCompile Include="PerformanceMetrics.cpp" />
<ClCompile Include="Ps2Float.cpp" />
<ClCompile Include="Recording\InputRecording.cpp" />
<ClCompile Include="Recording\InputRecordingControls.cpp" />
<ClCompile Include="Recording\InputRecordingFile.cpp" />
@ -726,6 +727,7 @@
<ClInclude Include="PINE.h" />
<ClInclude Include="FW.h" />
<ClInclude Include="PerformanceMetrics.h" />
<ClInclude Include="Ps2Float.h" />
<ClInclude Include="Recording\InputRecording.h" />
<ClInclude Include="Recording\InputRecordingControls.h" />
<ClInclude Include="Recording\InputRecordingFile.h" />
@ -1025,4 +1027,4 @@
<Import Condition="$(Configuration.Contains(Debug)) Or $(Configuration.Contains(Devel))" Project="$(SolutionDir)3rdparty\winpixeventruntime\WinPixEventRuntime.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>
</Project>

View File

@ -289,6 +289,9 @@
<Filter Include="System\Ps2\EmotionEngine\EE\Dynarec\arm64">
<UniqueIdentifier>{cd8ec519-2196-43f7-86de-7faced2d4296}</UniqueIdentifier>
</Filter>
<Filter Include="System\Ps2\EmotionEngine\Shared">
<UniqueIdentifier>{9a40984b-cb23-4a54-a5e9-9c54f3c16c5b}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="Docs\License.txt">
@ -1443,6 +1446,9 @@
<ClCompile Include="SIO\Pad\PadNegcon.cpp">
<Filter>System\Ps2\Iop\SIO\PAD</Filter>
</ClCompile>
<ClCompile Include="Ps2Float.cpp">
<Filter>System\Ps2\EmotionEngine\Shared</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Patch.h">
@ -2399,6 +2405,9 @@
<ClInclude Include="SIO\Pad\PadNegcon.h">
<Filter>System\Ps2\Iop\SIO\PAD</Filter>
</ClInclude>
<ClInclude Include="Ps2Float.h">
<Filter>System\Ps2\EmotionEngine\Shared</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuildStep Include="rdebug\deci2.h">
@ -2428,4 +2437,4 @@
<Filter>System\Ps2\GS</Filter>
</Natvis>
</ItemGroup>
</Project>
</Project>