diff --git a/.gitattributes b/.gitattributes
index e39a7f135..5b44a82e0 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,3 +2,4 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto eol=lf
+*.json text eol=lf
diff --git a/.gitignore b/.gitignore
index 8357033e6..0c46c50c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,8 @@ build/
[Oo]bj/
AppDir/
publish_appimage/
+publish_ava/
+publish_tmp_ava/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/
diff --git a/COMPILING.md b/COMPILING.md
index 238c1ade8..edfb35ac4 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -5,7 +5,7 @@ If you wish to build the emulator yourself, follow these steps:
### Step 1
-Install the [.NET 9.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/9.0).
+Install the [.NET 10.0 (or higher) SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0).
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
### Step 2
diff --git a/Directory.Build.props b/Directory.Build.props
index d7a2ac1f2..a4df830a3 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- net9.0
- latest
+ net10.0
+ preview
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b2a838496..fd61602a8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,6 +16,7 @@
+
@@ -41,7 +42,6 @@
-
diff --git a/README.md b/README.md
index 3dcf939b1..7a785055f 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ If you are planning to contribute or just want to learn more about this project
- **Audio**
Audio output is entirely supported, audio input (microphone) isn't supported.
- We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
+ We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL3](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
- **CPU**
diff --git a/Ryujinx.sln b/Ryujinx.sln
index 4babf3fb9..24def42a3 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -45,17 +45,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "src\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "src\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
@@ -79,12 +77,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL3.Common", "src\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj", "{F6F9826A-BC58-4D78-A700-F358A66B2B06}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
.github\workflows\canary.yml = .github\workflows\canary.yml
Directory.Packages.props = Directory.Packages.props
+ Directory.Build.props = Directory.Build.props
.github\workflows\release.yml = .github\workflows\release.yml
nuget.config = nuget.config
EndProjectSection
@@ -92,164 +93,468 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|x64.Build.0 = Debug|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|x86.Build.0 = Debug|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|x64.ActiveCfg = Release|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|x64.Build.0 = Release|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|x86.ActiveCfg = Release|Any CPU
+ {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|x86.Build.0 = Release|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|x64.Build.0 = Debug|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|x86.Build.0 = Debug|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|x64.ActiveCfg = Release|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|x64.Build.0 = Release|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|x86.ActiveCfg = Release|Any CPU
+ {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|x86.Build.0 = Release|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|x64.Build.0 = Debug|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|x86.Build.0 = Debug|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|x64.ActiveCfg = Release|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|x64.Build.0 = Release|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|x86.ActiveCfg = Release|Any CPU
+ {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|x86.Build.0 = Release|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|x64.Build.0 = Debug|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|x86.Build.0 = Debug|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|x64.ActiveCfg = Release|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|x64.Build.0 = Release|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|x86.ActiveCfg = Release|Any CPU
+ {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|x86.Build.0 = Release|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|x64.Build.0 = Debug|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|x86.Build.0 = Debug|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|x64.ActiveCfg = Release|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|x64.Build.0 = Release|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|x86.ActiveCfg = Release|Any CPU
+ {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|x86.Build.0 = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|x64.Build.0 = Debug|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|x86.Build.0 = Debug|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|x64.ActiveCfg = Release|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|x64.Build.0 = Release|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|x86.ActiveCfg = Release|Any CPU
+ {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|x86.Build.0 = Release|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|x64.Build.0 = Debug|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|x86.Build.0 = Debug|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|x64.ActiveCfg = Release|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|x64.Build.0 = Release|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|x86.ActiveCfg = Release|Any CPU
+ {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|x86.Build.0 = Release|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|x64.Build.0 = Debug|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|x86.Build.0 = Debug|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|x64.ActiveCfg = Release|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|x64.Build.0 = Release|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|x86.ActiveCfg = Release|Any CPU
+ {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|x86.Build.0 = Release|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|x64.Build.0 = Debug|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|x86.Build.0 = Debug|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x64.ActiveCfg = Release|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x64.Build.0 = Release|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x86.ActiveCfg = Release|Any CPU
+ {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x86.Build.0 = Release|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Debug|x64.Build.0 = Debug|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Debug|x86.Build.0 = Debug|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Release|x64.ActiveCfg = Release|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Release|x64.Build.0 = Release|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Release|x86.ActiveCfg = Release|Any CPU
+ {E1B1AD28-289D-47B7-A106-326972240207}.Release|x86.Build.0 = Release|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|x64.Build.0 = Debug|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|x86.Build.0 = Debug|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|x64.ActiveCfg = Release|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|x64.Build.0 = Release|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|x86.ActiveCfg = Release|Any CPU
+ {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|x86.Build.0 = Release|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|x64.Build.0 = Debug|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|x86.Build.0 = Debug|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|x64.ActiveCfg = Release|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|x64.Build.0 = Release|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|x86.ActiveCfg = Release|Any CPU
+ {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|x86.Build.0 = Release|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|x64.Build.0 = Debug|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|x86.Build.0 = Debug|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.ActiveCfg = Release|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.Build.0 = Release|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|x64.ActiveCfg = Release|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|x64.Build.0 = Release|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|x86.ActiveCfg = Release|Any CPU
+ {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|x86.Build.0 = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|x64.Build.0 = Debug|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|x86.Build.0 = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|x64.ActiveCfg = Release|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|x64.Build.0 = Release|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|x86.ActiveCfg = Release|Any CPU
+ {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|x86.Build.0 = Release|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|x64.Build.0 = Debug|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|x86.Build.0 = Debug|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|x64.ActiveCfg = Release|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|x64.Build.0 = Release|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|x86.ActiveCfg = Release|Any CPU
+ {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|x86.Build.0 = Release|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|x64.Build.0 = Debug|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|x86.Build.0 = Debug|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|x64.ActiveCfg = Release|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|x64.Build.0 = Release|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|x86.ActiveCfg = Release|Any CPU
+ {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|x86.Build.0 = Release|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|x64.Build.0 = Debug|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|x86.Build.0 = Debug|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|x64.ActiveCfg = Release|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|x64.Build.0 = Release|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|x86.ActiveCfg = Release|Any CPU
+ {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|x86.Build.0 = Release|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|x64.Build.0 = Debug|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|x86.Build.0 = Debug|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|x64.ActiveCfg = Release|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|x64.Build.0 = Release|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|x86.ActiveCfg = Release|Any CPU
+ {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|x86.Build.0 = Release|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|x64.Build.0 = Debug|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|x86.Build.0 = Debug|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|x64.ActiveCfg = Release|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|x64.Build.0 = Release|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|x86.ActiveCfg = Release|Any CPU
+ {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|x86.Build.0 = Release|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|x64.Build.0 = Debug|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|x86.Build.0 = Debug|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.Build.0 = Release|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|x64.ActiveCfg = Release|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|x64.Build.0 = Release|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|x86.ActiveCfg = Release|Any CPU
+ {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|x86.Build.0 = Release|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|x64.Build.0 = Debug|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|x86.Build.0 = Debug|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x64.ActiveCfg = Release|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x64.Build.0 = Release|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x86.ActiveCfg = Release|Any CPU
+ {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x86.Build.0 = Release|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|x64.Build.0 = Debug|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|x86.Build.0 = Debug|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|x64.ActiveCfg = Release|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|x64.Build.0 = Release|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|x86.ActiveCfg = Release|Any CPU
+ {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|x86.Build.0 = Release|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|x64.Build.0 = Debug|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|x86.Build.0 = Debug|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Release|x64.ActiveCfg = Release|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Release|x64.Build.0 = Release|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Release|x86.ActiveCfg = Release|Any CPU
+ {716364DE-B988-41A6-BAB4-327964266ECC}.Release|x86.Build.0 = Release|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|x64.Build.0 = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|x86.Build.0 = Debug|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU
- {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
- {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU
- {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x64.ActiveCfg = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x64.Build.0 = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.ActiveCfg = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.Build.0 = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x64.Build.0 = Debug|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x86.Build.0 = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|x64.ActiveCfg = Release|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|x64.Build.0 = Release|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|x86.ActiveCfg = Release|Any CPU
+ {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|x86.Build.0 = Release|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|x64.Build.0 = Debug|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|x86.Build.0 = Debug|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU
- {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|x64.ActiveCfg = Release|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|x64.Build.0 = Release|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|x86.ActiveCfg = Release|Any CPU
+ {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|x86.Build.0 = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|x64.Build.0 = Debug|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|x86.Build.0 = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|x64.ActiveCfg = Release|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|x64.Build.0 = Release|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|x86.ActiveCfg = Release|Any CPU
+ {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|x86.Build.0 = Release|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|x64.Build.0 = Debug|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|x86.Build.0 = Debug|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|x64.ActiveCfg = Release|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|x64.Build.0 = Release|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|x86.ActiveCfg = Release|Any CPU
+ {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|x86.Build.0 = Release|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|x64.Build.0 = Debug|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|x86.Build.0 = Debug|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|x64.ActiveCfg = Release|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|x64.Build.0 = Release|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|x86.ActiveCfg = Release|Any CPU
+ {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|x86.Build.0 = Release|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|x64.Build.0 = Debug|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|x86.Build.0 = Debug|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|x64.ActiveCfg = Release|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|x64.Build.0 = Release|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|x86.ActiveCfg = Release|Any CPU
+ {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|x86.Build.0 = Release|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|x64.Build.0 = Debug|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|x86.Build.0 = Debug|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.Build.0 = Release|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Release|x64.ActiveCfg = Release|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Release|x64.Build.0 = Release|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Release|x86.ActiveCfg = Release|Any CPU
+ {77F96ECE-4952-42DB-A528-DED25572A573}.Release|x86.Build.0 = Release|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|x64.Build.0 = Debug|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|x86.Build.0 = Debug|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|x64.ActiveCfg = Release|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|x64.Build.0 = Release|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|x86.ActiveCfg = Release|Any CPU
+ {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|x86.Build.0 = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|x64.Build.0 = Debug|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|x86.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|x64.ActiveCfg = Release|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|x64.Build.0 = Release|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|x86.ActiveCfg = Release|Any CPU
+ {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|x86.Build.0 = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|x64.Build.0 = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|x86.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|x64.ActiveCfg = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|x64.Build.0 = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|x86.ActiveCfg = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|x86.Build.0 = Release|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|x64.Build.0 = Debug|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|x86.Build.0 = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|x64.ActiveCfg = Release|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|x64.Build.0 = Release|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|x86.ActiveCfg = Release|Any CPU
+ {4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|x86.Build.0 = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|x64.Build.0 = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Debug|x86.Build.0 = Debug|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x64.ActiveCfg = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x64.Build.0 = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x86.ActiveCfg = Release|Any CPU
+ {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x86.Build.0 = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.Build.0 = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.Build.0 = Debug|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.ActiveCfg = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.Build.0 = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.ActiveCfg = Release|Any CPU
+ {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.Build.0 = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.Build.0 = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.Build.0 = Debug|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.ActiveCfg = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.Build.0 = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.ActiveCfg = Release|Any CPU
+ {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/assets/locales.json b/assets/locales.json
index af11978f7..8899bf692 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -89,7 +89,7 @@
"tr_TR": "",
"uk_UA": "Редактор Mii",
"zh_CN": "Mii 编辑器",
- "zh_TW": ""
+ "zh_TW": "Mii 編輯器"
}
},
{
@@ -225,7 +225,7 @@
"el_GR": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)",
"en_US": "Host Unchecked (Fastest, Unsafe)",
"es_ES": "Host Sin Verificación (Más rápido, Inseguro)",
- "fr_FR": "Hôte Non Vérifié (plus rapide, non sécurisé)",
+ "fr_FR": "Hôte Non Vérifié (Plus Rapide, Non Sécurisé)",
"he_IL": "מארח לא מבוקר (המהיר ביותר, לא בטוח)",
"it_IT": "Host senza verifica (più veloce, non sicura)",
"ja_JP": "ホスト, チェックなし (最高速, 安全でない)",
@@ -295,7 +295,7 @@
{
"ID": "MenuBarFileOpenFromFile",
"Translations": {
- "ar_SA": "_تحميل التطبيق...",
+ "ar_SA": "_تحميل التطبيق...",
"de_DE": "_Anwendung laden...",
"el_GR": "_Φόρτωση εφαρμογής...",
"en_US": "_Load Application...",
@@ -375,7 +375,7 @@
"el_GR": "",
"en_US": "Load DLC...",
"es_ES": "Cargar DLC...",
- "fr_FR": "Charger les DLC...",
+ "fr_FR": "Charger des DLC...",
"he_IL": "",
"it_IT": "Carica DLC...",
"ja_JP": "",
@@ -400,7 +400,7 @@
"el_GR": "",
"en_US": "Load Title Updates...",
"es_ES": "Cargar Actualizaciones de Títulos...",
- "fr_FR": "Charger les Mises à Jour du Titre...",
+ "fr_FR": "Charger des Mises à Jour de Titres...",
"he_IL": "",
"it_IT": "Carica aggiornamenti...",
"ja_JP": "",
@@ -575,7 +575,7 @@
"el_GR": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη",
"en_US": "Start Games in Fullscreen Mode",
"es_ES": "Iniciar Juegos en Pantalla Completa",
- "fr_FR": "Démarrer les Jeux en Mode Plein Écran",
+ "fr_FR": "Démarrer les Jeux en Plein Écran",
"he_IL": "התחל משחקים במסך מלא",
"it_IT": "Avvia i giochi a schermo intero",
"ja_JP": "全画面モードでゲームを開始",
@@ -1057,14 +1057,14 @@
"ko_KR": "도구",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Ferramentas",
"ru_RU": "Инструменты",
"sv_SE": "Verktyg",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "工具",
- "zh_TW": ""
+ "zh_TW": "工具"
}
},
{
@@ -1314,7 +1314,7 @@
"tr_TR": "",
"uk_UA": "FAQ & Усунення несправностей",
"zh_CN": "常见问题与疑难解答",
- "zh_TW": ""
+ "zh_TW": "常見問題與疑難排解"
}
},
{
@@ -1346,7 +1346,7 @@
"ID": "MenuBarHelpMultiplayer",
"Translations": {
"ar_SA": "متعدد اللاعبين (LDN/LAN)",
- "de_DE": "Multiplayer (LDN/LAN)",
+ "de_DE": "",
"el_GR": "Πολλαπλοί Παίκτες (LDN/LAN)",
"en_US": "Multiplayer (LDN/LAN)",
"es_ES": "Multijugador (LDN/LAN)",
@@ -2482,7 +2482,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -2527,7 +2527,7 @@
"es_ES": "Logotipo",
"fr_FR": null,
"he_IL": "",
- "it_IT": "Logo",
+ "it_IT": "",
"ja_JP": "ロゴ",
"ko_KR": "로고",
"no_NO": "",
@@ -3250,14 +3250,14 @@
"el_GR": "Διεπαφή",
"en_US": "Interface",
"es_ES": "Interfaz",
- "fr_FR": "Interface",
+ "fr_FR": null,
"he_IL": "ממשק",
"it_IT": "Interfaccia",
"ja_JP": "インターフェース",
"ko_KR": "인터페이스",
"no_NO": "Grensesnitt",
"pl_PL": "Interfejs",
- "pt_BR": "Interface",
+ "pt_BR": null,
"ru_RU": "Интерфейс",
"sv_SE": "Gränssnitt",
"th_TH": "อินเทอร์เฟซ",
@@ -3350,7 +3350,7 @@
"el_GR": "Ερώτημα",
"en_US": "Prompt",
"es_ES": "Al Inicio",
- "fr_FR": "Demande",
+ "fr_FR": "Demander",
"he_IL": "הודעה",
"it_IT": "Domanda",
"ja_JP": "プロンプト",
@@ -3513,7 +3513,7 @@
"th_TH": "บล็อกข้อมูล & ปิดเสียง",
"tr_TR": "Girişi engelle & sesi kapat",
"uk_UA": "Блокувати введення & вимкнути звук",
- "zh_CN": "阻止输入 & 静音",
+ "zh_CN": "阻止输入并静音",
"zh_TW": "停用輸入 & 靜音"
}
},
@@ -4027,7 +4027,7 @@
"es_ES": null,
"fr_FR": "Australie",
"he_IL": "אוסטרליה",
- "it_IT": "Australia",
+ "it_IT": "",
"ja_JP": "オーストラリア",
"ko_KR": "호주",
"no_NO": "",
@@ -4057,7 +4057,7 @@
"ko_KR": "중국",
"no_NO": "Kina",
"pl_PL": "Chiny",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Китай",
"sv_SE": "Kina",
"th_TH": "จีน",
@@ -4107,7 +4107,7 @@
"ko_KR": "대만",
"no_NO": "",
"pl_PL": "Tajwan",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Тайвань",
"sv_SE": null,
"th_TH": "ไต้หวัน",
@@ -4800,7 +4800,7 @@
"el_GR": "Backend Ήχου:",
"en_US": "Audio Backend:",
"es_ES": "Motor de Audio:",
- "fr_FR": "Bibliothèque Audio :",
+ "fr_FR": "Moteur Audio :",
"he_IL": "אחראי שמע:",
"it_IT": "Backend audio:",
"ja_JP": "音声バックエンド:",
@@ -4893,12 +4893,12 @@
}
},
{
- "ID": "SettingsTabSystemAudioBackendSDL2",
+ "ID": "SettingsTabSystemAudioBackendSDL3",
"Translations": {
"ar_SA": null,
"de_DE": null,
"el_GR": null,
- "en_US": "SDL2",
+ "en_US": "SDL3",
"es_ES": null,
"fr_FR": null,
"he_IL": null,
@@ -5307,7 +5307,7 @@
"ko_KR": "2배",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 2",
@@ -5332,7 +5332,7 @@
"ko_KR": "4배",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 4",
@@ -5357,7 +5357,7 @@
"ko_KR": "8배",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 8",
@@ -5382,7 +5382,7 @@
"ko_KR": "16배",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 16",
@@ -5482,7 +5482,7 @@
"ko_KR": "2배(1440p/2160p)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 2 (1440p/2160p)",
@@ -5507,7 +5507,7 @@
"ko_KR": "3배(2160p/3240p)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "คูณ 3 (2160p/3240p)",
@@ -5700,7 +5700,7 @@
"el_GR": "Έκταση σε όλο το παράθυρο",
"en_US": "Stretch to Fit Window",
"es_ES": "Estirar a la Ventana",
- "fr_FR": "Ajuster à la Taille de la Fenêtre",
+ "fr_FR": "Adapter à la Taille de la Fenêtre",
"he_IL": "מתח לגודל חלון",
"it_IT": "Adatta alla finestra",
"ja_JP": "ウインドウサイズに合わせる",
@@ -5750,7 +5750,7 @@
"el_GR": "Τοποθεσία Shaders Γραφικών:",
"en_US": "Graphics Shader Dump Path:",
"es_ES": "Directorio de Volcado de Sombreadores:",
- "fr_FR": "Chemin du Dossier de Copie des Shaders :",
+ "fr_FR": "Chemin du dump des shaders graphiques :",
"he_IL": "",
"it_IT": "Percorso di dump degli shader:",
"ja_JP": "グラフィックス シェーダー ダンプパス:",
@@ -6250,7 +6250,7 @@
"el_GR": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων",
"en_US": "Enable Debug Logs",
"es_ES": "Habilitar Registros de Debug",
- "fr_FR": "Activer les Journaux de Débogage",
+ "fr_FR": "Activer les Journaux de Debug",
"he_IL": "אפשר רישום ניפוי באגים",
"it_IT": "Attiva log di debug",
"ja_JP": "デバッグログを有効にする",
@@ -6465,7 +6465,6 @@
"uk_UA": "Я хочу скинути налаштування.",
"zh_CN": "我要重置我的设置。",
"zh_TW": "我想重設我的設定。"
-
}
},
{
@@ -6958,7 +6957,7 @@
"ko_KR": "프로 컨트롤러",
"no_NO": "",
"pl_PL": "Pro Kontroler",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Pro Контроллер",
"sv_SE": null,
"th_TH": "โปรคอนโทรลเลอร์",
@@ -8458,7 +8457,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "LED-Подсветка",
"sv_SE": null,
"th_TH": "ตั้งค่าไฟ LED",
@@ -9008,7 +9007,7 @@
"ko_KR": "메뉴",
"no_NO": "Meny",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Меню",
"sv_SE": "Meny",
"th_TH": "เมนู",
@@ -9133,7 +9132,7 @@
"ko_KR": "엔터",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Enter",
@@ -9208,7 +9207,7 @@
"ko_KR": "탭",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Tab",
@@ -9233,7 +9232,7 @@
"ko_KR": "백스페이스",
"no_NO": "Tilbaketast",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": "Backsteg",
"th_TH": "ปุ่ม Backspace",
@@ -9258,7 +9257,7 @@
"ko_KR": null,
"no_NO": "Sett inn",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Insert",
@@ -9283,7 +9282,7 @@
"ko_KR": null,
"no_NO": "Slett",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Delete",
@@ -9308,7 +9307,7 @@
"ko_KR": null,
"no_NO": "Side opp",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Page Up",
@@ -9333,7 +9332,7 @@
"ko_KR": null,
"no_NO": "Side ned",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Page Down",
@@ -9358,7 +9357,7 @@
"ko_KR": null,
"no_NO": "Hjem",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Home",
@@ -9383,7 +9382,7 @@
"ko_KR": null,
"no_NO": "Avslutt",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม End",
@@ -9408,7 +9407,7 @@
"ko_KR": null,
"no_NO": "Skiftelås",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Caps Lock",
@@ -9433,7 +9432,7 @@
"ko_KR": null,
"no_NO": "Rullelås",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Scroll Lock",
@@ -9458,7 +9457,7 @@
"ko_KR": null,
"no_NO": "Skjermbilde",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Print Screen",
@@ -9483,7 +9482,7 @@
"ko_KR": null,
"no_NO": "Stans midlertidig",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Pause",
@@ -9508,7 +9507,7 @@
"ko_KR": null,
"no_NO": "Numerisk Lås",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม Num Lock",
@@ -9533,7 +9532,7 @@
"ko_KR": null,
"no_NO": "Tøm",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": "Töm",
"th_TH": "ล้าง",
@@ -9558,7 +9557,7 @@
"ko_KR": "키패드 0",
"no_NO": "Numerisk 0",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 0",
"sv_SE": "Numerisk 0",
"th_TH": "ปุ่ม 0 บนแป้นตัวเลข",
@@ -9583,7 +9582,7 @@
"ko_KR": "키패드 1",
"no_NO": "Numerisk 1",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 1",
"sv_SE": "Numerisk 1",
"th_TH": "ปุ่ม 1 บนแป้นตัวเลข",
@@ -9608,7 +9607,7 @@
"ko_KR": "키패드 2",
"no_NO": "Numerisk 2",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 2",
"sv_SE": "Numerisk 2",
"th_TH": "ปุ่ม 2 บนแป้นตัวเลข",
@@ -9633,7 +9632,7 @@
"ko_KR": "키패드 3",
"no_NO": "Numerisk 3",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 3",
"sv_SE": "Numerisk 3",
"th_TH": "ปุ่ม 3 บนแป้นตัวเลข",
@@ -9658,7 +9657,7 @@
"ko_KR": "키패드 4",
"no_NO": "Numerisk 4",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 4",
"sv_SE": "Numerisk 4",
"th_TH": "ปุ่ม 4 บนแป้นตัวเลข",
@@ -9683,7 +9682,7 @@
"ko_KR": "키패드 5",
"no_NO": "Numerisk 5",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 5",
"sv_SE": "Numerisk 5",
"th_TH": "ปุ่ม 5 บนแป้นตัวเลข",
@@ -9708,7 +9707,7 @@
"ko_KR": "키패드 6",
"no_NO": "Numerisk 6",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 6",
"sv_SE": "Numerisk 6",
"th_TH": "ปุ่ม 6 บนแป้นตัวเลข",
@@ -9733,7 +9732,7 @@
"ko_KR": "키패드 7",
"no_NO": "Numerisk 7",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 7",
"sv_SE": "Numerisk 7",
"th_TH": "ปุ่ม 7 บนแป้นตัวเลข",
@@ -9758,7 +9757,7 @@
"ko_KR": "키패드 8",
"no_NO": "Numerisk 8",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 8",
"sv_SE": "Numerisk 8",
"th_TH": "ปุ่ม 8 บนแป้นตัวเลข",
@@ -9783,7 +9782,7 @@
"ko_KR": "키패드 9",
"no_NO": "Numerisk 9",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Блок цифр 9",
"sv_SE": "Numerisk 9",
"th_TH": "ปุ่ม 9 บนแป้นตัวเลข",
@@ -9808,7 +9807,7 @@
"ko_KR": "키패드 분할",
"no_NO": "Numerisk Dele",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "/ (блок цифр)",
"sv_SE": "Keypad /",
"th_TH": "ปุ่ม / บนแป้นตัวเลข",
@@ -9833,7 +9832,7 @@
"ko_KR": "키패드 멀티플",
"no_NO": "Numerisk Multiplisere",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "* (блок цифр)",
"sv_SE": "Keypad *",
"th_TH": "ปุ่ม * บนแป้นตัวเลข",
@@ -9858,7 +9857,7 @@
"ko_KR": "키패드 빼기",
"no_NO": "Numerisk Subtrakt",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "- (блок цифр)",
"sv_SE": "Keypad -",
"th_TH": "ปุ่ม - บนแป้นตัวเลข",
@@ -9883,7 +9882,7 @@
"ko_KR": "키패드 추가",
"no_NO": "Numerisk Legg til",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "+ (блок цифр)",
"sv_SE": "Keypad +",
"th_TH": "ปุ่ม + บนแป้นตัวเลข",
@@ -9908,7 +9907,7 @@
"ko_KR": "숫자 키패드",
"no_NO": "Numerisk Desimal",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": ". (блок цифр)",
"sv_SE": "Keypad ,",
"th_TH": "ปุ่ม . บนแป้นตัวเลข",
@@ -9933,7 +9932,7 @@
"ko_KR": "키패드 엔터",
"no_NO": "Numerisk Enter",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Enter (блок цифр)",
"sv_SE": "Enter (numerisk)",
"th_TH": "ปุ่ม Enter บนแป้นตัวเลข",
@@ -9958,7 +9957,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -9983,7 +9982,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10008,7 +10007,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10033,7 +10032,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10058,7 +10057,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10083,7 +10082,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10108,7 +10107,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10133,7 +10132,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10158,7 +10157,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10183,7 +10182,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10208,7 +10207,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10233,7 +10232,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10258,7 +10257,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10283,7 +10282,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10308,7 +10307,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10333,7 +10332,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10358,7 +10357,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10383,7 +10382,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10408,7 +10407,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10433,7 +10432,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10458,7 +10457,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10483,7 +10482,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -10783,7 +10782,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม - บนจอยเกม",
@@ -10808,7 +10807,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "ปุ่ม + บนจอยเกม",
@@ -10858,7 +10857,7 @@
"ko_KR": "기타",
"no_NO": "Diverse",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Outros",
"ru_RU": "Прочее",
"sv_SE": "Diverse",
"th_TH": "เบ็ดเตล็ด",
@@ -10883,7 +10882,7 @@
"ko_KR": "패들 1",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Доп.кнопка 1",
"sv_SE": "Paddel 1",
"th_TH": "ปุ่ม พาเดิล 1",
@@ -10908,7 +10907,7 @@
"ko_KR": "패들 2",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Доп.кнопка 2",
"sv_SE": "Paddel 2",
"th_TH": "ปุ่ม พาเดิล 2",
@@ -10933,7 +10932,7 @@
"ko_KR": "패들 3",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Доп.кнопка 3",
"sv_SE": "Paddel 3",
"th_TH": "ปุ่ม พาเดิล 3",
@@ -10958,7 +10957,7 @@
"ko_KR": "패들 4",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Доп.кнопка 4",
"sv_SE": "Paddel 4",
"th_TH": "ปุ่ม พาเดิล 4",
@@ -10983,7 +10982,7 @@
"ko_KR": "터치패드",
"no_NO": "Berøringsplate",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Тачпад",
"sv_SE": "Pekplatta",
"th_TH": "ทัชแพด",
@@ -11508,7 +11507,7 @@
"ko_KR": "확인",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": "Ok",
"th_TH": "ตกลง",
@@ -13458,7 +13457,7 @@
"ko_KR": "{0} : {1}",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "",
@@ -14650,22 +14649,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "{0} DRAM Enabled",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "{0} DRAM Habilitada",
+ "fr_FR": "{0} DRAM Activée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "{0} DRAM 활성화",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "{0} DRAM Ativado",
"ru_RU": "",
"sv_SE": "{0} DRAM aktiverat",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "已启用 {0} DRAM",
+ "zh_TW": "將使用 {0} DRAM"
}
},
{
@@ -14675,22 +14674,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "Using above 4GiB DRAM may cause crashes in some applications.",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "Usar más de 4 GiB de DRAM puede causar fallos en algunas aplicaciones.",
+ "fr_FR": "L’utilisation de plus de 4 Gio de DRAM peut provoquer des plantages dans certaines applications.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "4GB 이상의 DRAM을 사용하면 일부 앱에서 충돌이 발생할 수 있습니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Usar DRAM acima de 4GiB pode ocasionar falha do sistema em algumas aplicações.",
"ru_RU": "",
"sv_SE": "Användning av mer än 4GiB DRAM kan orsaka krascher i vissa applikationer.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "启用 4GiB 以上的 DRAM 可能导致某些应用程序崩溃。",
+ "zh_TW": "使用超過 4GiB DRAM 有機會於模擬某些應用程式時停止運作。"
}
},
{
@@ -14700,22 +14699,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "Debug: GDB Stub Enabled (Port: {0})",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "Debug: GDB Stub Habilitado (Puerto: {0})",
+ "fr_FR": "Debug : GDB Stub Activé (Port : {0})",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "디버그 : GDB Stub 활성화됨(포트 : {0})",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Depuração: GDB Stub ativado (Porta: {0})",
"ru_RU": "",
"sv_SE": "Felsök: GDB Stub aktiverad (Port: {0})",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "调试: 已开启 GDB Stub (端口: {0})",
+ "zh_TW": "偵錯:已啟用 GDB Stub (通訊埠:{0})"
}
},
{
@@ -14725,22 +14724,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "This will affect performance.",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "Esto afectará el rendimiento.",
+ "fr_FR": "Cela affectera les performances.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "이는 성능에 영향을 미칠 것입니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Isto vai afetar a performance.",
"ru_RU": "",
"sv_SE": "Detta kommer att påverka prestandan.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "这会影响性能",
+ "zh_TW": "啟用此選項會影響效能。"
}
},
{
@@ -14750,22 +14749,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "Debug: Suspend on Start Enabled",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "Debug: Suspender al Inicio Habilitado",
+ "fr_FR": "Debug : Suspension au Démarrage Activée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "디버그 : 시작 시, 일시 중지 활성화",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Depuração: Suspensa ao Iniciar ativado.",
"ru_RU": "",
"sv_SE": "Felsök: Vänteläge vid start aktiverat",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "调试: 已开启启动时暂停",
+ "zh_TW": "偵錯:已啟用執行時暫停應用程式"
}
},
{
@@ -14775,22 +14774,22 @@
"de_DE": "",
"el_GR": "",
"en_US": "Application has been suspended. Attach a debugger to continue.",
- "es_ES": "",
- "fr_FR": "",
+ "es_ES": "La aplicación ha sido suspendida. Adjunte un depurador para continuar.",
+ "fr_FR": "L’application a été suspendue. Attachez un débogueur pour continuer.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
- "ko_KR": "",
+ "ko_KR": "앱이 일시 중지되었습니다. 계속하려면 디버거를 연결하세요.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "A aplicação foi suspensa. Anexar um depurador para continuar.",
"ru_RU": "",
"sv_SE": "Applikationen har satts i vänteläge. Anslut en felsökare för att fortsätta.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
- "zh_TW": ""
+ "zh_CN": "应用程序已被暂停。请附加一个调试器继续。",
+ "zh_TW": "應用程式已暫停。附加偵錯器以繼續。"
}
},
{
@@ -15001,7 +15000,7 @@
"el_GR": "Πολυνηματική Επεξεργασία Γραφικών:",
"en_US": "Graphics Backend Multithreading:",
"es_ES": "Multihilado del Motor Gráfico:",
- "fr_FR": "Interface graphique multithread :",
+ "fr_FR": "Interface Graphique Multithread :",
"he_IL": "אחראי גרפיקה רב-תהליכי:",
"it_IT": "Multithreading del backend grafico:",
"ja_JP": "グラフィックスバックエンドのマルチスレッド実行:",
@@ -15558,7 +15557,7 @@
"ko_KR": "이 게임에 호환되지 않는 Amiibo 표시",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Mostar Amiibos incompatíveis para este jogo.",
"ru_RU": "",
"sv_SE": "Visa Amiibo som inte är kompatibla med detta spel",
"th_TH": "",
@@ -16371,26 +16370,26 @@
{
"ID": "AudioBackendTooltip",
"Translations": {
- "ar_SA": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.",
- "de_DE": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.",
+ "ar_SA": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL3 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL3 إذا لم تكن متأكدا.",
+ "de_DE": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL3 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL3 auswählen.",
"el_GR": "Αλλαγή ήχου υποστήριξης",
- "en_US": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
- "es_ES": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.",
- "fr_FR": "Modifie la backend utilisé pour donner un rendu audio.\n\nSDL2 est recommandé, tandis que OpenAL et SoundIO sont utilisés en secours. Dummy ne produit aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.",
- "he_IL": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.",
- "it_IT": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.",
- "ja_JP": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.",
- "ko_KR": "오디오 렌더링에 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 SoundIO는 대체 수단으로 사용됩니다. 더미에는 소리가 나지 않습니다.\n\n모르면 SDL2로 설정하세요.",
- "no_NO": "Endrer backend brukt til å gjengi lyd.\n\nSDL2 er foretrukket, mens OpenAL og SoundIO brukes som reserveløsning. Dummy kommer ikke til å ha lyd.\n\nSett til SDL2 hvis usikker.",
- "pl_PL": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.",
- "pt_BR": "Altera o módulo usado para renderizar áudio.\n\nSDL2 é o preferido, enquanto OpenAL e SoundIO são usados como fallbacks. Dummy não terá som.\n\nDefina como SDL2 se não tiver certeza.",
- "ru_RU": "Меняет бэкенд используемый для воспроизведения аудио.\n\nSDL2 — предпочтительный вариант, в то время как OpenAL и SoundIO используются как резервные. Dummy не будет воспроизводить звук.\n\nРекомендуется использовать SDL2.",
- "sv_SE": "Ändrar bakänden som används för att rendera ljud.\n\nSDL2 är den föredragna, men OpenAL och SoundIO används för att falla tillbaka på. Dummy har inget ljud.\n\nStäll in till SDL2 om du är osäker.",
- "th_TH": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nแนะนำเป็น SDL2 ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nตั้งค่าเป็น SDL2 หากคุณไม่แน่ใจ",
- "tr_TR": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.",
- "uk_UA": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.",
- "zh_CN": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。",
- "zh_TW": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL2。"
+ "en_US": "Changes the backend used to render audio.\n\nSDL3 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL3 if unsure.",
+ "es_ES": "Cambia el motor usado para renderizar audio.\n\nSDL3 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL3 si no sabes qué hacer.",
+ "fr_FR": "Modifie la backend utilisé pour donner un rendu audio.\n\nSDL3 est recommandé, tandis que OpenAL et SoundIO sont utilisés en secours. Dummy ne produit aucun son.\n\nLaissez sur SDL3 si vous n'êtes pas sûr.",
+ "he_IL": "משנה את אחראי השמע.\n\nSDL3 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL3 אם לא בטוחים.",
+ "it_IT": "Cambia il backend usato per riprodurre l'audio.\n\nSDL3 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL3.",
+ "ja_JP": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL3 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL3 を設定してください.",
+ "ko_KR": "오디오 렌더링에 사용되는 백엔드를 변경합니다.\n\nSDL3가 선호되는 반면 OpenAL 및 SoundIO는 대체 수단으로 사용됩니다. 더미에는 소리가 나지 않습니다.\n\n모르면 SDL3로 설정하세요.",
+ "no_NO": "Endrer backend brukt til å gjengi lyd.\n\nSDL3 er foretrukket, mens OpenAL og SoundIO brukes som reserveløsning. Dummy kommer ikke til å ha lyd.\n\nSett til SDL3 hvis usikker.",
+ "pl_PL": "Zmienia backend używany do renderowania dźwięku.\n\nSDL3 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL3.",
+ "pt_BR": "Altera o módulo usado para renderizar áudio.\n\nSDL3 é o preferido, enquanto OpenAL e SoundIO são usados como fallbacks. Dummy não terá som.\n\nDefina como SDL3 se não tiver certeza.",
+ "ru_RU": "Меняет бэкенд используемый для воспроизведения аудио.\n\nSDL3 — предпочтительный вариант, в то время как OpenAL и SoundIO используются как резервные. Dummy не будет воспроизводить звук.\n\nРекомендуется использовать SDL3.",
+ "sv_SE": "Ändrar bakänden som används för att rendera ljud.\n\nSDL3 är den föredragna, men OpenAL och SoundIO används för att falla tillbaka på. Dummy har inget ljud.\n\nStäll in till SDL3 om du är osäker.",
+ "th_TH": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nแนะนำเป็น SDL3 ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nตั้งค่าเป็น SDL3 หากคุณไม่แน่ใจ",
+ "tr_TR": "Ses çıkış motorunu değiştirir.\n\nSDL3 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL3 seçeneğine ayarlayın.",
+ "uk_UA": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL3 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL3, якщо не впевнені.",
+ "zh_CN": "更改音频处理引擎。\n\n推荐选择“SDL3”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL3”。",
+ "zh_TW": "變更用於繪製音訊的後端。\n\nSDL3 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL3。"
}
},
{
@@ -16599,9 +16598,9 @@
"ar_SA": "",
"de_DE": "Diese Option überspringt den Dialog 'Benutzerprofile verwalten' während des Spiels und verwendet ein voreingestelltes Profil.\n\nDie Profilumschaltung finden Sie unter 'Einstellungen' - 'Benutzerprofile verwalten'. Wählen Sie das gewünschte Profil aus, bevor Sie das Spiel laden.",
"el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.",
- "en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
- "es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
- "fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
+ "en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Options' - 'User Profiles'. Select the desired profile before loading the game.",
+ "es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Opciones' - 'Perfiles de Usuario'. Seleccione el perfil deseado antes de cargar el juego.",
+ "fr_FR": "Cette option permet de passer le dialogue 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Options' - 'Profils d'Utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
"he_IL": "",
"it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.",
"ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。",
@@ -17101,7 +17100,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων",
"en_US": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.",
"es_ES": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.",
- "fr_FR": "Affiche dans la console les journaux de débogage.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.",
+ "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.",
"he_IL": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.",
"it_IT": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.",
"ja_JP": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.",
@@ -17408,7 +17407,7 @@
"ko_KR": "중앙처리장치",
"no_NO": "Prosessor",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Processador",
"ru_RU": "Процессор",
"sv_SE": "Processor",
"th_TH": "ซีพียู",
@@ -17583,7 +17582,7 @@
"ko_KR": "{0}FPS({1}밀리초)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": "{0} bilder/s ({1}ms)",
"th_TH": "{0} FPS ({1}มิลลิวินาที)",
@@ -17608,7 +17607,7 @@
"ko_KR": "{0}FPS({1}밀리초), ({2}%) 터보",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "{0} FPS ({1}ms), Турбо ({2}%)",
"sv_SE": "{0} bilder/s ({1}ms), Turbo ({2}%)",
"th_TH": "{0} FPS ({1} มิลลิวินาที), โหมดเทอร์โบ ({2}%)",
@@ -19101,7 +19100,7 @@
"el_GR": "",
"en_US": "Choose a Switch compatible FILE to load",
"es_ES": "Elige un ARCHIVO compatible con Switch para cargar",
- "fr_FR": "Choisissez un FICHIER compatible avec Switch à charger",
+ "fr_FR": "Choisissez un FICHIER compatible Switch à charger",
"he_IL": "",
"it_IT": "Scegli un FILE compatibile con Switch da caricare",
"ja_JP": "",
@@ -19115,7 +19114,7 @@
"tr_TR": "",
"uk_UA": "Виберіть ФАЙЛ, сумісний із Switch, для завантаження",
"zh_CN": "请选择要加载的 Switch 兼容文件",
- "zh_TW": ""
+ "zh_TW": "請選擇要載入的 Switch 相容檔案"
}
},
{
@@ -19140,7 +19139,7 @@
"tr_TR": "",
"uk_UA": "Виберіть РОЗПАКОВАНИЙ сумісний із Switch додаток для завантаження",
"zh_CN": "请选择要加载的已解包的 Switch 兼容应用程序",
- "zh_TW": ""
+ "zh_TW": "請選擇要載入的已解壓縮 Switch 相容應用程式"
}
},
{
@@ -19151,7 +19150,7 @@
"el_GR": "",
"en_US": "Choose one or more FOLDERS to bulk load title updates from",
"es_ES": "Elige una o más CARPETAS para cargar actualizaciones de título de forma masiva",
- "fr_FR": "Choisissez un ou plusieurs DOSSIERS pour charger en masse des mises à jour du titre",
+ "fr_FR": "Choisissez un ou plusieurs DOSSIERS pour charger en masse des mises à jour de titres",
"he_IL": "",
"it_IT": "Scegli una o più CARTELLE da cui caricare in blocco gli aggiornamenti del titolo",
"ja_JP": "",
@@ -19165,7 +19164,7 @@
"tr_TR": "",
"uk_UA": "Виберіть одну або кілька ПАПОК для масового завантаження оновлень титулів",
"zh_CN": "请选择一个或多个文件夹来批量加载游戏更新",
- "zh_TW": ""
+ "zh_TW": "請選擇一個或多個資料夾以批次載入遊戲更新"
}
},
{
@@ -19190,7 +19189,7 @@
"tr_TR": "",
"uk_UA": "Виберіть одну або кілька ПАПОК для масового завантаження DLC",
"zh_CN": "请选择一个或多个文件夹来批量加载 DLC",
- "zh_TW": ""
+ "zh_TW": "請選擇一個或多個資料夾以批次載入 DLC"
}
},
{
@@ -19272,7 +19271,7 @@
"ID": "SettingsTabHotkeys",
"Translations": {
"ar_SA": "اختصارات",
- "de_DE": "Hotkeys",
+ "de_DE": "",
"el_GR": "Συντομεύσεις",
"en_US": "Hotkeys",
"es_ES": "Atajos",
@@ -19578,12 +19577,12 @@
"es_ES": null,
"fr_FR": null,
"he_IL": "אמיבו",
- "it_IT": "Amiibo",
+ "it_IT": "",
"ja_JP": "",
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "อมิโบ้",
@@ -20383,7 +20382,7 @@
"ko_KR": "{0:n0}MB",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "{0:n0} Мб",
"sv_SE": null,
"th_TH": "{0:n0} เมกะไบต์",
@@ -20833,7 +20832,7 @@
"ko_KR": "{0} 모드",
"no_NO": "{0} Modifikasjoner(s)",
"pl_PL": "{0} Mod(y/ów)",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "{0} Мод(а/ов)",
"sv_SE": "{0} modd(ar)",
"th_TH": "{0} ม็อด",
@@ -22033,7 +22032,7 @@
"ko_KR": "쌍선형",
"no_NO": "",
"pl_PL": "Dwuliniowe",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Билинейная",
"sv_SE": "Bilinjär",
"th_TH": "บิไลเนียร์",
@@ -22258,7 +22257,7 @@
"ko_KR": "SMAA 울트라",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "SMAA Ультра",
"sv_SE": "SMAA ultra",
"th_TH": "ลดรอยหยัก (SMAA สูงทาก)",
@@ -22521,26 +22520,26 @@
{
"ID": "MultiplayerModeTooltip",
"Translations": {
- "ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
- "de_DE": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
+ "ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم ldn_mitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
+ "de_DE": "Ändert den LDN-Mehrspielermodus.\n\nldn_mitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
"el_GR": "",
- "en_US": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
- "es_ES": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
- "fr_FR": "Change le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
+ "en_US": "Change LDN multiplayer mode.\n\nldn_mitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
+ "es_ES": "Cambiar modo LDN multijugador.\n\nldn_mitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
+ "fr_FR": "Change le mode multijoueur LDN.\n\nldn_mitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "",
- "it_IT": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
+ "it_IT": "Cambia la modalità multigiocatore LDN.\n\nldn_mitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
"ja_JP": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.",
- "ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
- "no_NO": "Endre LDN flerspillermodus.\n\nLdnMitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
+ "ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nldn_mitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
+ "no_NO": "Endre LDN flerspillermodus.\n\nldn_mitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
"pl_PL": "",
- "pt_BR": "Alterar o modo multiplayer LDN.\n\nLdnMitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
- "ru_RU": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
- "sv_SE": "Ändra LDN-flerspelarläge\n\nLdnMitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
- "th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
+ "pt_BR": "Alterar o modo multiplayer LDN.\n\nldn_mitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
+ "ru_RU": "Меняет многопользовательский режим LDN.\n\nldn_mitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
+ "sv_SE": "Ändra LDN-flerspelarläge\n\nldn_mitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
+ "th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nldn_mitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
"tr_TR": "",
- "uk_UA": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
+ "uk_UA": "Змінити LDN мультиплеєру.\n\nldn_mitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
"zh_CN": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,游戏版本 v13.0.1 无法与 v13.0.0 联机)。\n\n如果不确定,请保持为“禁用”。",
- "zh_TW": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。"
+ "zh_TW": "變更 LDN 多人遊戲模式。\n\nldn_mitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。"
}
},
{
@@ -22801,7 +22800,7 @@
"el_GR": "",
"en_US": "Generates a new passphrase, which can be shared with other players.",
"es_ES": "Genera una nueva frase de contraseña, que puede ser compartida con otros jugadores.",
- "fr_FR": "Génére un nouveau mot de passe, qui peut être partagé avec les autres.",
+ "fr_FR": "Génère un nouveau mot de passe, qui peut être partagé avec les autres.",
"he_IL": "",
"it_IT": "Genera una nuova passphrase, che può essere condivisa con altri giocatori.",
"ja_JP": "",
@@ -22908,7 +22907,7 @@
"ko_KR": "수직 동기화 :",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "วีซิงค์:",
@@ -22958,7 +22957,7 @@
"ko_KR": "스위치",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "เปลี่ยนโหมด",
@@ -23025,7 +23024,7 @@
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. \"Unbounded\" ist eine unbegrenzte Bildwiederholfrequenz.",
"el_GR": "",
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.",
- "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite.",
+ "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60\u202FHz. ‘Sin límite’ es una frecuencia de actualización sin límite.",
"fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité.",
"he_IL": "",
"it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento.",
@@ -23050,7 +23049,7 @@
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. „Unbounded“ ist eine unbegrenzte Bildwiederholfrequenz. „Benutzerdefinierte Bildwiederholfrequenz“ emuliert die angegebene benutzerdefinierte Bildwiederholfrequenz.",
"el_GR": "",
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.",
- "es_ES": "Sincronización Vertical Emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite. ‘Frecuencia de actualización personalizada’ emula la frecuencia de actualización personalizada especificada.",
+ "es_ES": "Sincronización Vertical Emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60\u202FHz. ‘Sin límite’ es una frecuencia de actualización sin límite. ‘Frecuencia de actualización personalizada’ emula la frecuencia de actualización personalizada especificada.",
"fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité. 'Taux de Rafraîchissement Customisé' émule le taux de rafraîchissement spécifié.",
"he_IL": "",
"it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento. \"Frequenza di aggiornamento personalizzata\" emula la frequenza di aggiornamento specificata.",
@@ -23608,7 +23607,7 @@
"ko_KR": "상태",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "Данные",
"sv_SE": "Statistik",
"th_TH": "",
@@ -23933,7 +23932,7 @@
"ko_KR": "(글로벌)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": "(Глобальная)",
"sv_SE": null,
"th_TH": "(ทั่วทั้งระบบ)",
@@ -24033,14 +24032,14 @@
"ko_KR": "디버그",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Depuração",
"ru_RU": "Отладка",
"sv_SE": "Felsökning",
"th_TH": "ดีบัก",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "调试",
- "zh_TW": "除錯"
+ "zh_TW": "偵錯"
}
},
{
@@ -24058,14 +24057,14 @@
"ko_KR": "디버그",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Depuração",
"ru_RU": "Отладка",
"sv_SE": "Felsök",
"th_TH": "ดีบัก",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "调试",
- "zh_TW": "除錯"
+ "zh_TW": "偵錯"
}
},
{
@@ -24083,7 +24082,7 @@
"ko_KR": "경고 : 개발자 전용으로, 성능이 저하될 수 있습니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "AVISO: Uso apenas para desenvolvedores, reduzirá a performance.",
"ru_RU": "ВНИМАНИЕ: Только для разработчиков. Снижают производительность",
"sv_SE": "VARNING: Endast för utvecklare, minskar prestandan",
"th_TH": "คำเตือน: สำหรับนักพัฒนาเท่านั้น การเปิดใช้งานจะทำให้ประสิทธิภาพลดลง",
@@ -24108,7 +24107,7 @@
"ko_KR": "GDB Stub 활성화",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Ativar GDB Stub",
"ru_RU": "Включить GDB-Отладчик",
"sv_SE": "Aktivera GDB Stub",
"th_TH": "เปิดใช้งาน GDB Stub",
@@ -24133,14 +24132,14 @@
"ko_KR": "GDB Stub을 활성화하여 실행 중인 응용 프로그램을 디버그할 수 있도록 합니다. 개발 용도로만 사용하십시오!",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Ativa o GDB Stub tornando possível a depurar a aplicação em execução. Apenas para desenvolvedores!",
"ru_RU": "Активирует GDB-заглушку, позволяя выполнять отладку работающего приложения. Использовать только для разработки!",
"sv_SE": "Aktiverar GDB Stub som gör det möjligt att felsöka den körande applikationen. Endast för utvecklingsändamål!",
"th_TH": "เปิดใช้งาน GDB Stub เพื่อให้สามารถดีบักแอปพลิเคชันระหว่างการทำงานได้ (สำหรับนักพัฒนาเท่านั้น!)",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "启用 GDB stub 使得可以调试正在运行的应用程序。仅限开发用途!",
- "zh_TW": "啟用 GDB stub 可利用 gdb 除錯正在執行的應用程式。僅供開發使用!"
+ "zh_TW": "啟用 GDB stub 可利用 gdb 偵錯正在執行的應用程式。僅供開發使用!"
}
},
{
@@ -24158,7 +24157,7 @@
"ko_KR": "GDB Stub 포트 :",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Porta GDB Stub:",
"ru_RU": "Порт GDB-Заглушки:",
"sv_SE": "Port för GDB Stub:",
"th_TH": "พอร์ต GDB Stub:",
@@ -24183,7 +24182,7 @@
"ko_KR": "시작 시, 앱 일시 중지",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Suspender Aplicação ao Iniciar",
"ru_RU": "Приостанавливать приложение при запуске",
"sv_SE": "Försätt applikation i vänteläge vid start",
"th_TH": "หยุดการทำงานของแอปชั่วคราวเมื่อเริ่มต้นระบบ",
@@ -24208,14 +24207,14 @@
"ko_KR": "1번째 명령어를 실행하기 전에 앱앱을 일시 중지하여 가장 초기의 단계에서 디버깅을 가능하게 합니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Suspende a aplicação antes de executar a primeira instrução, permitindo depurar desde o primeiro ponto.",
"ru_RU": "Приостанавливает приложения до выполнения первой инструкции, позволяя начать отладку с самого раннего этапа.",
"sv_SE": "Försätter applikationen i vänteläge innan den första instruktionen exekveras, vilket möjliggör felsökning från tidigaste möjliga punkt.",
"th_TH": "หยุดการทำงานของแอปพลิเคชันชั่วคราวก่อนดำเนินการคำสั่งแรก เพื่อให้สามารถดีบักได้ตั้งแต่เริ่มต้น",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "在执行首条指令前挂起应用程序,这样就可以从最早的点开始调试。",
- "zh_TW": "在執行首項指令前暫停應用程式,以便從最早的點開始除錯。"
+ "zh_TW": "在執行首項指令前暫停應用程式,以便從最早的點開始偵錯。"
}
},
{
@@ -24233,7 +24232,7 @@
"ko_KR": "LDN 게임 목록 열기",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Lista de Jogos LDN",
"ru_RU": "Cписок LDN игр",
"sv_SE": "LDN-spellista",
"th_TH": "เปิดรายชื่อเกม LDN",
@@ -24258,7 +24257,7 @@
"ko_KR": "LDN 게임 브라우저 - {0}게임",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Navegador de Jogos LDN - {0} jogos",
"ru_RU": "Браузер игр LDN - {0} игр",
"sv_SE": "LDN-spelbläddrare - {0} spel",
"th_TH": "ตัวเรียกดูเกม LDN - {0} เกม",
@@ -24283,7 +24282,7 @@
"ko_KR": "{0} LDN 게임 검색...",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Pesquisar {0} jogos LDN...",
"ru_RU": "Поиск среди {0} игр LDN...",
"sv_SE": "Sök efter {0} LDN-spel...",
"th_TH": "ค้นหาเกม LDN จำนวน {0} เกม...",
@@ -24308,7 +24307,7 @@
"ko_KR": "LDN이 뭔가요?",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "O que é o LDN?",
"ru_RU": "Что такое LDN?",
"sv_SE": "Vad är LDN?",
"th_TH": "LDN คืออะไร?",
@@ -24333,7 +24332,7 @@
"ko_KR": "{0} 서버에서 사용 가능한 게임을 새로 고침.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Atualizar jogos disponíveis do servidor {0}.",
"ru_RU": "Обновить доступные игры с сервера по адресу {0}.",
"sv_SE": "Uppdatera tillgängliga spel från servern på {0}.",
"th_TH": "รีเฟรชเกมที่มีอยู่จากเซิร์ฟเวอร์ที่ {0}",
@@ -24358,7 +24357,7 @@
"ko_KR": "플레이어 수 - 비활성화",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Contagem de Jogadores - Desativado",
"ru_RU": "Количество игроков - Отключить",
"sv_SE": "Spelarantal - Inaktivera",
"th_TH": "จำนวนผู้เล่น – ปิดใช้งาน",
@@ -24383,7 +24382,7 @@
"ko_KR": "플레이어 수 - 오름차순",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Contagem de Jogadores - Crescente",
"ru_RU": "Количество игроков - По Возрастанию",
"sv_SE": "Spelarantal - Stigande",
"th_TH": "จำนวนผู้เล่น – เรียงจากน้อยไปมาก",
@@ -24408,7 +24407,7 @@
"ko_KR": "플레이어 수 - 내림차순",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Contagem de Jogadores - Decrescente",
"ru_RU": "Количество игроков - По Убыванию",
"sv_SE": "Spelarantal - Fallande",
"th_TH": "จำนวนผู้เล่น – เรียงจากมากไปน้อย",
@@ -24433,7 +24432,7 @@
"ko_KR": "필터",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Filtros",
"ru_RU": "Фильтры",
"sv_SE": "Filter",
"th_TH": "ตัวกรอง",
@@ -24458,7 +24457,7 @@
"ko_KR": "공개 게임만 표시",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Mostrar apenas salas públicas",
"ru_RU": "Показывать только публичные игры",
"sv_SE": "Visa endast publika spel",
"th_TH": "แสดงเฉพาะเกมสาธารณะ",
@@ -24483,7 +24482,7 @@
"ko_KR": "참여 가능한 게임만 표시",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Mostrar apenas salas acessíveis",
"ru_RU": "Показывать только доступные для присоединения игры",
"sv_SE": "Visa endast spel som man kan delta i",
"th_TH": "แสดงเฉพาะเกมที่สามารถเข้าร่วมได้",
@@ -24508,7 +24507,7 @@
"ko_KR": "마스터 서버 프록시",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Proxy do Servidor Mestre",
"ru_RU": "Прокси главного сервера",
"sv_SE": "Proxy för huvudserver",
"th_TH": "พร็อกซีเซิร์ฟเวอร์หลัก",
@@ -24533,7 +24532,7 @@
"ko_KR": null,
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": "แบบเพียร์ทูเพียร์ (P2P)",
@@ -24558,7 +24557,7 @@
"ko_KR": "RyuLDN 서버를 통해 연결합니다.(느림)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Conectar através do servidor RyuLDN (mais lento)",
"ru_RU": "Подключается через сервер RyuLDN (медленнее).",
"sv_SE": "Ansluter via RyuLDN-servern (långsammare).",
"th_TH": "เชื่อมต่อผ่านเซิร์ฟเวอร์ RyuLDN (ช้ากว่า)",
@@ -24583,7 +24582,7 @@
"ko_KR": "UPnP를 통한 P2P 방식으로 연결합니다.(빠름)",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Conectar via Peer-to-Peer usando UPnP (mais rápido)",
"ru_RU": "Подключается через прямое соединение (Peer-to-Peer) через UPnP (быстрее).",
"sv_SE": "Ansluter via Peer-to-Peer via UPnP (snabbare).",
"th_TH": "เชื่อมต่อแบบเพียร์ทูเพียร์ผ่าน UPnP (เร็วกว่า)",
@@ -24608,7 +24607,7 @@
"ko_KR": "생성일 : {0}",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Criado: {0}",
"ru_RU": "Создано: {0}",
"sv_SE": "Skapat: {0}",
"th_TH": "สร้างเมื่อ: {0}",
@@ -24633,7 +24632,7 @@
"ko_KR": "플레이어({1}명 중 {0}명) :",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Jogadores ({0}) de ({1}):",
"ru_RU": "Игроки ({0} из {1}):",
"sv_SE": "Spelare ({0} utav {1}):",
"th_TH": "ผู้เล่น ({0} จาก {1}):",
@@ -24658,7 +24657,7 @@
"ko_KR": "참여 가능",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Disponível",
"ru_RU": "Можно присоединиться",
"sv_SE": "Möjligt att gå med",
"th_TH": "สามารถเข้าร่วมได้",
@@ -24683,7 +24682,7 @@
"ko_KR": "게임이 공개되어 있거나 비밀번호를 알고 있는 경우 게임에 참여할 수 있습니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "O jogo multijogador está disponível se a sala for pública ou se você conhecer a senha.",
"ru_RU": "К игре можно присоединиться, если она публичная или если вы знаете пароль",
"sv_SE": "Du kan delta i spelet om det är publikt eller om du kan lösenordet.",
"th_TH": "เกมสามารถเข้าร่วมได้หากเป็นเกมสาธารณะหรือหากคุณทราบรหัสผ่าน",
@@ -24708,7 +24707,7 @@
"ko_KR": "가입 불가",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Indisponível",
"ru_RU": "Нельзя присоединиться",
"sv_SE": "Inte möjligt att gå med",
"th_TH": "ไม่สามารถเข้าร่วมได้",
@@ -24733,7 +24732,7 @@
"ko_KR": "현재 게임이 진행 중입니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "O jogo multijogador está em execução.",
"ru_RU": "Игра в данный момент идёт",
"sv_SE": "Spelet pågår för närvarande.",
"th_TH": "เกมกำลังดำเนินอยู่ในขณะนี้",
@@ -24758,7 +24757,7 @@
"ko_KR": "공개",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Pública",
"ru_RU": "Публичная",
"sv_SE": "Publik",
"th_TH": "สาธารณะ",
@@ -24783,7 +24782,7 @@
"ko_KR": "누구나 이 게임에 참여할 수 있습니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Qualquer um pode entrar neste jogo.",
"ru_RU": "Любой может присоединиться к этой игре.",
"sv_SE": "Vem som helst kan delta i detta spel.",
"th_TH": "ใครก็สามารถเข้าร่วมเกมนี้ได้",
@@ -24808,7 +24807,7 @@
"ko_KR": "비공개",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Privado",
"ru_RU": "Частная",
"sv_SE": "Privat",
"th_TH": "ส่วนตัว",
@@ -24833,7 +24832,7 @@
"ko_KR": "설정에 동일한 LDN 암호가 있는 경우에만 이 게임에 참여할 수 있습니다.",
"no_NO": "",
"pl_PL": "",
- "pt_BR": "",
+ "pt_BR": "Você só poderá entrar neste jogo multijogador se possuir a mesma senha LDN nas suas configurações.",
"ru_RU": "Вы можете присоединиться к этой игре только если в настройках у вас тот же LDN пароль.",
"sv_SE": "Du kan endast delta i detta spel om du också har samma LDN-lösenord i dina inställningar.",
"th_TH": "คุณสามารถเข้าร่วมเกมนี้ได้ก็ต่อเมื่อคุณมีรหัสผ่าน LDN เดียวกันในการตั้งค่าของคุณด้วย",
@@ -24844,4 +24843,4 @@
}
}
]
-}
+}
\ No newline at end of file
diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh
index daeea9bfd..5793f8359 100755
--- a/distribution/linux/Ryujinx.sh
+++ b/distribution/linux/Ryujinx.sh
@@ -2,8 +2,8 @@
SCRIPT_DIR=$(dirname "$(realpath "$0")")
-if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
- RYUJINX_BIN="Ryujinx.Headless.SDL2"
+if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL3" ]; then
+ RYUJINX_BIN="Ryujinx.Headless.SDL3"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
diff --git a/distribution/macos/Assets.car b/distribution/macos/Assets.car
new file mode 100644
index 000000000..d0121ede0
Binary files /dev/null and b/distribution/macos/Assets.car differ
diff --git a/distribution/macos/Info.plist b/distribution/macos/Info.plist
index 7f7fe0dd4..a2d8a96f8 100644
--- a/distribution/macos/Info.plist
+++ b/distribution/macos/Info.plist
@@ -10,6 +10,8 @@
Ryujinx
CFBundleIconFile
Ryujinx.icns
+ CFBundleIconName
+ Ryujinx
CFBundleDocumentTypes
@@ -40,7 +42,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.2
+ 1.3
CFBundleSignature
????
CFBundleVersion
diff --git a/distribution/macos/create_app_bundle.sh b/distribution/macos/create_app_bundle.sh
index 0b4ab8e04..b90848334 100755
--- a/distribution/macos/create_app_bundle.sh
+++ b/distribution/macos/create_app_bundle.sh
@@ -25,6 +25,7 @@ cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
+cp Assets.car "$APP_BUNDLE_DIRECTORY/Contents/Resources/Assets.car"
cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh
deleted file mode 100755
index 6052b202c..000000000
--- a/distribution/macos/create_macos_build_headless.sh
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/bin/bash
-
-set -e
-
-if [ "$#" -lt 8 ]; then
- echo "usage "
- exit 1
-fi
-
-mkdir -p "$1"
-mkdir -p "$2"
-mkdir -p "$3"
-
-BASE_DIR=$(readlink -f "$1")
-TEMP_DIRECTORY=$(readlink -f "$2")
-OUTPUT_DIRECTORY=$(readlink -f "$3")
-ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
-VERSION=$5
-SOURCE_REVISION_ID=$6
-CONFIGURATION=$7
-CANARY=$8
-
-if [[ "$(uname)" == "Darwin" ]]; then
- echo "Clearing xattr on all dot undercsore files"
- find "$BASE_DIR" -type f -name "._*" -exec sh -c '
- for f; do
- dir=$(dirname "$f")
- base=$(basename "$f")
- orig="$dir/${base#._}"
- [ -f "$orig" ] && xattr -c "$orig" || true
- done
- ' sh {} +
-fi
-
-if [ "$CANARY" == "1" ]; then
- RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
-elif [ "$VERSION" == "1.1.0" ]; then
- RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
-else
- RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
-fi
-
-ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
-X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
-UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
-EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2
-
-rm -rf "$TEMP_DIRECTORY"
-mkdir -p "$TEMP_DIRECTORY"
-
-DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
-
-dotnet restore
-dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2
-dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
-dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
-
-# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
-rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
-
-# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
-# TODO: remove this once done
-rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
-
-rm -rf "$OUTPUT_DIRECTORY"
-mkdir -p "$OUTPUT_DIRECTORY"
-
-# Let's copy one of the two different outputs and remove the executable
-cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
-rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
-
-# Make its libraries universal
-python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
-
-if ! [ -x "$(command -v lipo)" ];
-then
- if ! [ -x "$(command -v llvm-lipo-17)" ];
- then
- LIPO=llvm-lipo
- else
- LIPO=llvm-lipo-17
- fi
-else
- LIPO=lipo
-fi
-
-# Make the executable universal
-$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
-
-# Now sign it
-if ! [ -x "$(command -v codesign)" ];
-then
- if ! [ -x "$(command -v rcodesign)" ];
- then
- echo "Cannot find rcodesign on your system, please install rcodesign."
- exit 1
- fi
-
- # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
- # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
- echo "Using rcodesign for ad-hoc signing"
- for FILE in "$UNIVERSAL_OUTPUT"/*; do
- if [[ $(file "$FILE") == *"Mach-O"* ]]; then
- rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
- fi
- done
-else
- echo "Using codesign for ad-hoc signing"
- for FILE in "$UNIVERSAL_OUTPUT"/*; do
- if [[ $(file "$FILE") == *"Mach-O"* ]]; then
- codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE"
- fi
- done
-fi
-
-echo "Creating archive"
-pushd "$OUTPUT_DIRECTORY"
-tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
-python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
-gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
-rm "$RELEASE_TAR_FILE_NAME"
-popd
-
-echo "Done"
diff --git a/docs/README.md b/docs/README.md
index a22da3c7c..80a1cf0be 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -8,7 +8,7 @@ Intro to Ryujinx
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions.
* The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
-* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks.
+* Audio output is entirely supported via C# wrappers for SDL3, with OpenAL & libsoundio as fallbacks.
Getting Started
===============
diff --git a/docs/compatibility.csv b/docs/compatibility.csv
index 2efd1d0d5..e476f9253 100644
--- a/docs/compatibility.csv
+++ b/docs/compatibility.csv
@@ -2275,12 +2275,12 @@
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
+0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
01005D100807A000,"Pokémon™ Quest",,playable,2022-02-22 16:12:32
0100A3D008C5C000,"Pokémon™ Scarlet",gpu;nvdec;ldn-works;amd-vendor-bug,ingame,2023-12-14 13:18:29
01008F6008C5E000,"Pokémon™ Violet",gpu;nvdec;ldn-works;amd-vendor-bug;mac-bug,ingame,2024-07-30 02:51:48
0100187003A36000,"Pokémon™: Let’s Go, Eevee!",crash;nvdec;online-broken;ldn-broken,ingame,2024-06-01 15:03:04
010003F003A34000,"Pokémon™: Let’s Go, Pikachu!",crash;nvdec;online-broken;ldn-broken,ingame,2024-03-15 07:55:41
-0100F43008C44000,"Pokémon Legends: Z-A",gpu;crash;ldn-broken,ingame,2025-10-16 19:13:00
0100B3F000BE2000,"Pokkén Tournament™ DX",nvdec;ldn-works;opengl-backend-bug;LAN;amd-vendor-bug;intel-vendor-bug,playable,2024-07-18 23:11:08
010030D005AE6000,"Pokkén Tournament™ DX Demo",demo;opengl-backend-bug,playable,2022-08-10 12:03:19
0100A3500B4EC000,"Polandball: Can Into Space",,playable,2020-06-25 15:13:26
diff --git a/global.json b/global.json
index cdbb589ed..512142d2b 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "9.0.100",
+ "version": "10.0.100",
"rollForward": "latestFeature"
}
}
diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs
index 5db898591..755e9573a 100644
--- a/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs
@@ -25,9 +25,9 @@ namespace ARMeilleure.CodeGen.Arm64
static class ComparisonArm64Extensions
{
- public static ArmCondition ToArmCondition(this Comparison comp)
+ extension(Comparison comparison)
{
- return comp switch
+ public ArmCondition Arm => comparison switch
{
#pragma warning disable IDE0055 // Disable formatting
Comparison.Equal => ArmCondition.Eq,
@@ -42,7 +42,7 @@ namespace ARMeilleure.CodeGen.Arm64
Comparison.LessUI => ArmCondition.LtUn,
#pragma warning restore IDE0055
- _ => throw new ArgumentException(null, nameof(comp)),
+ _ => throw new ArgumentException(null, nameof(comparison))
};
}
}
diff --git a/src/ARMeilleure/CodeGen/Arm64/Assembler.cs b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs
index 0d493426b..ee696c5f2 100644
--- a/src/ARMeilleure/CodeGen/Arm64/Assembler.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs
@@ -181,10 +181,10 @@ namespace ARMeilleure.CodeGen.Arm64
public void Fmov(Operand rd, Operand rn, bool topHalf)
{
- Debug.Assert(rd.Type.IsInteger() != rn.Type.IsInteger());
+ Debug.Assert(rd.Type.IsInteger != rn.Type.IsInteger);
Debug.Assert(rd.Type == OperandType.I64 || rn.Type == OperandType.I64 || !topHalf);
- uint opcode = rd.Type.IsInteger() ? 0b110u : 0b111u;
+ uint opcode = rd.Type.IsInteger ? 0b110u : 0b111u;
uint rmode = topHalf ? 1u << 19 : 0u;
uint ftype = rd.Type == OperandType.FP64 || rn.Type == OperandType.FP64 ? 1u << 22 : 0u;
@@ -411,7 +411,7 @@ namespace ARMeilleure.CodeGen.Arm64
public void Mov(Operand rd, Operand rn)
{
- if (rd.Type.IsInteger())
+ if (rd.Type.IsInteger)
{
Orr(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn);
}
@@ -973,7 +973,7 @@ namespace ARMeilleure.CodeGen.Arm64
uint instruction;
int scale;
- if (type.IsInteger())
+ if (type.IsInteger)
{
instruction = intInst;
@@ -1009,7 +1009,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
uint instruction;
- if (type.IsInteger())
+ if (type.IsInteger)
{
instruction = intInst;
diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs
index fbf4c1eb4..320e86dc2 100644
--- a/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs
@@ -250,7 +250,7 @@ namespace ARMeilleure.CodeGen.Arm64
// ValidateBinOp(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Add(dest, src1, src2);
}
@@ -268,7 +268,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateBinOp(dest, src1, src2);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.And(dest, src1, src2);
}
@@ -281,7 +281,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateBinOp(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Eor(dest, src1, src2);
}
@@ -298,7 +298,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateUnOp(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Mvn(dest, source);
}
@@ -311,7 +311,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateBinOp(dest, src1, src2);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Orr(dest, src1, src2);
}
@@ -322,7 +322,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(comp.Kind == OperandKind.Constant);
- ArmCondition cond = ((Comparison)comp.AsInt32()).ToArmCondition();
+ ArmCondition cond = ((Comparison)comp.AsInt32()).Arm;
GenerateCompareCommon(context, operation);
@@ -336,7 +336,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateUnOp(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Rev(dest, source);
}
@@ -354,7 +354,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(dest.Type == OperandType.I32);
Debug.Assert(comp.Kind == OperandKind.Constant);
- ArmCondition cond = ((Comparison)comp.AsInt32()).ToArmCondition();
+ ArmCondition cond = ((Comparison)comp.AsInt32()).Arm;
GenerateCompareCommon(context, operation);
@@ -428,7 +428,7 @@ namespace ARMeilleure.CodeGen.Arm64
EnsureSameType(src1, src2);
- Debug.Assert(src1.Type.IsInteger());
+ Debug.Assert(src1.Type.IsInteger);
context.Assembler.Cmp(src1, src2);
}
@@ -442,7 +442,7 @@ namespace ARMeilleure.CodeGen.Arm64
EnsureSameType(dest, src2, src3);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
Debug.Assert(src1.Type == OperandType.I32);
context.Assembler.Cmp(src1, Const(src1.Type, 0));
@@ -468,7 +468,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(dest.Type != source.Type);
Debug.Assert(source.Type != OperandType.V128);
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
context.Assembler.ScvtfScalar(dest, source);
}
@@ -485,7 +485,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(dest.Type is OperandType.FP32 or OperandType.FP64);
Debug.Assert(dest.Type != source.Type);
- Debug.Assert(source.Type.IsInteger());
+ Debug.Assert(source.Type.IsInteger);
context.Assembler.UcvtfScalar(dest, source);
}
@@ -497,7 +497,7 @@ namespace ARMeilleure.CodeGen.Arm64
EnsureSameType(dest, source);
- Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant);
+ Debug.Assert(dest.Type.IsInteger || source.Kind != OperandKind.Constant);
// Moves to the same register are useless.
if (dest.Kind == source.Kind && dest.Value == source.Value)
@@ -529,7 +529,7 @@ namespace ARMeilleure.CodeGen.Arm64
EnsureSameType(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Clz(dest, source);
}
@@ -542,7 +542,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateBinOp(dest, dividend, divisor);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Sdiv(dest, dividend, divisor);
}
@@ -576,7 +576,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand value = operation.Destination;
Operand address = operation.GetSource(0);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.LdrhRiUn(value, address, 0);
}
@@ -586,7 +586,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand value = operation.Destination;
Operand address = operation.GetSource(0);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.LdrbRiUn(value, address, 0);
}
@@ -604,7 +604,7 @@ namespace ARMeilleure.CodeGen.Arm64
EnsureSameType(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Mul(dest, src1, src2);
}
@@ -647,7 +647,7 @@ namespace ARMeilleure.CodeGen.Arm64
ValidateUnOp(dest, source);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Neg(dest, source);
}
@@ -732,7 +732,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Sxth(dest, source);
}
@@ -742,7 +742,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Sxtw(dest, source);
}
@@ -752,7 +752,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Sxtb(dest, source);
}
@@ -823,7 +823,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand value = operation.GetSource(1);
Operand address = operation.GetSource(0);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.StrhRiUn(value, address, 0);
}
@@ -833,7 +833,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand value = operation.GetSource(1);
Operand address = operation.GetSource(0);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.StrbRiUn(value, address, 0);
}
@@ -858,7 +858,7 @@ namespace ARMeilleure.CodeGen.Arm64
// ValidateBinOp(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Sub(dest, src1, src2);
}
@@ -882,7 +882,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (dest != default)
{
- Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger && source.Type.IsInteger);
OperandType destType = source.Type == OperandType.I64 ? OperandType.FP64 : OperandType.FP32;
@@ -901,9 +901,9 @@ namespace ARMeilleure.CodeGen.Arm64
byte index = src2.AsByte();
- Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes());
+ Debug.Assert(index < OperandType.V128.ByteSize / dest.Type.ByteSize);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Umov(dest, src1, index, dest.Type == OperandType.I64 ? 3 : 2);
}
@@ -959,7 +959,7 @@ namespace ARMeilleure.CodeGen.Arm64
byte index = src3.AsByte();
- if (src2.Type.IsInteger())
+ if (src2.Type.IsInteger)
{
context.Assembler.Ins(dest, src2, index, src2.Type == OperandType.I64 ? 3 : 2);
}
@@ -1007,7 +1007,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
Operand dest = operation.Destination;
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
context.Assembler.CmeqVector(dest, dest, dest, 2);
}
@@ -1016,7 +1016,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
Operand dest = operation.Destination;
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
context.Assembler.EorVector(dest, dest, dest);
}
@@ -1046,7 +1046,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Uxth(dest, source);
}
@@ -1056,7 +1056,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
// We can eliminate the move if source is already 32-bit and the registers are the same.
if (dest.Value == source.Value && source.Type == OperandType.I32)
@@ -1072,7 +1072,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Uxtb(dest, source);
}
@@ -1169,7 +1169,7 @@ namespace ARMeilleure.CodeGen.Arm64
context.Assembler.StrRiPre(Register(reg, type), Register(SpRegister), -calleeSaveRegionSize);
}
- offset += type.GetSizeInBytes();
+ offset += type.ByteSize;
}
while (mask != 0)
@@ -1195,7 +1195,7 @@ namespace ARMeilleure.CodeGen.Arm64
context.Assembler.StpRiPre(Register(reg, type), Register(reg2, type), Register(SpRegister), -calleeSaveRegionSize);
}
- offset += type.GetSizeInBytes() * 2;
+ offset += type.ByteSize * 2;
}
}
@@ -1273,7 +1273,7 @@ namespace ARMeilleure.CodeGen.Arm64
mask &= ~(1 << reg2);
- offset -= type.GetSizeInBytes() * 2;
+ offset -= type.ByteSize * 2;
if (offset != 0)
{
@@ -1286,7 +1286,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
else
{
- offset -= type.GetSizeInBytes();
+ offset -= type.ByteSize;
if (offset != 0)
{
@@ -1435,12 +1435,12 @@ namespace ARMeilleure.CodeGen.Arm64
OperandType valueType = GetMemOpValueType(currentOp);
- if (valueType != GetMemOpValueType(nextOp) || op1Offset + valueType.GetSizeInBytes() != op2Offset)
+ if (valueType != GetMemOpValueType(nextOp) || op1Offset + valueType.ByteSize != op2Offset)
{
return false;
}
- if (!CodeGenCommon.ConstFitsOnSImm7(op1Offset, valueType.GetSizeInBytesLog2()))
+ if (!CodeGenCommon.ConstFitsOnSImm7(op1Offset, valueType.ByteSizeLog2))
{
return false;
}
@@ -1549,7 +1549,7 @@ namespace ARMeilleure.CodeGen.Arm64
// EnsureSameReg (dest, src1);
EnsureSameType(dest, src1);
- Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32);
+ Debug.Assert(dest.Type.IsInteger && src2.Type == OperandType.I32);
}
private static void EnsureSameReg(Operand op1, Operand op2)
diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs
index 390dc5b2e..e7871289b 100644
--- a/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs
@@ -462,7 +462,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
instruction |= (sz << 22);
- if (rd.Type.IsInteger())
+ if (rd.Type.IsInteger)
{
context.Assembler.WriteInstructionAuto(instruction, rd, rn);
}
@@ -490,7 +490,7 @@ namespace ARMeilleure.CodeGen.Arm64
instruction |= (sz << 22);
instruction |= (64 - fBits) << 10;
- if (rd.Type.IsInteger())
+ if (rd.Type.IsInteger)
{
Debug.Assert(rd.Type != OperandType.I32 || fBits <= 32);
diff --git a/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs
index 76a231d6c..d52aba162 100644
--- a/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs
@@ -112,7 +112,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (src1.Kind == OperandKind.Constant)
{
- if (!src1.Type.IsInteger())
+ if (!src1.Type.IsInteger)
{
// Handle non-integer types (FP32, FP64 and V128).
// For instructions without an immediate operand, we do the following:
@@ -161,7 +161,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (src2.Kind == OperandKind.Constant)
{
- if (!src2.Type.IsInteger())
+ if (!src2.Type.IsInteger)
{
src2 = AddFloatConstantCopy(constants, nodes, node, src2);
@@ -191,7 +191,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (src.Kind == OperandKind.Constant)
{
- if (!src.Type.IsInteger())
+ if (!src.Type.IsInteger)
{
src = AddFloatConstantCopy(constants, nodes, node, src);
@@ -282,7 +282,7 @@ namespace ARMeilleure.CodeGen.Arm64
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount < intMax;
}
@@ -309,7 +309,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (passOnReg)
{
- Operand argReg = source.Type.IsInteger()
+ Operand argReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
@@ -327,7 +327,7 @@ namespace ARMeilleure.CodeGen.Arm64
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp));
- stackOffset += source.Type.GetSizeInBytes();
+ stackOffset += source.Type.ByteSize;
}
}
@@ -345,7 +345,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
else
{
- Operand retReg = dest.Type.IsInteger()
+ Operand retReg = dest.Type.IsInteger
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
@@ -385,7 +385,7 @@ namespace ARMeilleure.CodeGen.Arm64
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount + 1 < intMax;
}
@@ -408,7 +408,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (passOnReg)
{
- Operand argReg = source.Type.IsInteger()
+ Operand argReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
@@ -521,7 +521,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
else
{
- Operand retReg = source.Type.IsInteger()
+ Operand retReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
@@ -551,7 +551,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
OperandType argType = cctx.FuncArgTypes[cIndex];
- if (argType.IsInteger())
+ if (argType.IsInteger)
{
intCount++;
}
@@ -567,7 +567,7 @@ namespace ARMeilleure.CodeGen.Arm64
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount < CallingConvention.GetArgumentsOnRegsCount();
}
@@ -606,7 +606,7 @@ namespace ARMeilleure.CodeGen.Arm64
{
Operand pArg = Local(dest.Type);
- Operand argReg = dest.Type.IsInteger()
+ Operand argReg = dest.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
diff --git a/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs
index 5f0e37721..4a9f6a834 100644
--- a/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs
+++ b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs
@@ -51,7 +51,7 @@ namespace ARMeilleure.CodeGen.Optimizations
if (trueSucc == block.ListNext)
{
Comparison comp = (Comparison)branchOp.GetSource(2).AsInt32();
- Comparison compInv = comp.Invert();
+ Comparison compInv = comp.Inverse;
branchOp.SetSource(2, Const((int)compInv));
diff --git a/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs
index cbc6ab784..c1de22757 100644
--- a/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs
+++ b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs
@@ -161,7 +161,7 @@ namespace ARMeilleure.CodeGen.Optimizations
}
else if (otherCompType == Comparison.Equal)
{
- propCompType = compType.Invert();
+ propCompType = compType.Inverse;
}
else
{
diff --git a/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs
index 53a7f3ede..a80b4adad 100644
--- a/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs
+++ b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs
@@ -105,7 +105,7 @@ namespace ARMeilleure.CodeGen.Optimizations
Operand x = operation.GetSource(0);
Operand y = operation.GetSource(1);
- if (x == y && x.Type.IsInteger())
+ if (x == y && x.Type.IsInteger)
{
operation.TurnIntoCopy(Const(x.Type, 0));
}
@@ -161,7 +161,7 @@ namespace ARMeilleure.CodeGen.Optimizations
private static bool IsConstEqual(Operand operand, ulong comparand)
{
- if (operand.Kind != OperandKind.Constant || !operand.Type.IsInteger())
+ if (operand.Kind != OperandKind.Constant || !operand.Type.IsInteger)
{
return false;
}
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs
index 8b135afab..574de4cd6 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs
@@ -98,7 +98,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
OperandType type = types[copyDest];
- type = type.IsInteger() ? OperandType.I64 : OperandType.V128;
+ type = type.IsInteger ? OperandType.I64 : OperandType.V128;
EmitXorSwap(sequence, GetRegister(copyDest, type), GetRegister(copySource, type));
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs
index 5f1d6ce89..1e9aee5fd 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs
@@ -178,7 +178,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
else if (dest.Kind == OperandKind.Register)
{
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
intFixedRegisters |= 1 << dest.GetRegister().Index;
}
@@ -236,7 +236,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
Register reg = info.Register.GetRegister();
- if (local.Type.IsInteger())
+ if (local.Type.IsInteger)
{
intLocalFreeRegisters |= 1 << reg.Index;
}
@@ -254,7 +254,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
if (temp == default || info.Sequence != sequence)
{
- temp = local.Type.IsInteger()
+ temp = local.Type.IsInteger
? GetSpillTemp(local, intSpillTempRegisters, ref intLocalUse)
: GetSpillTemp(local, vecSpillTempRegisters, ref vecLocalUse);
@@ -335,7 +335,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
if (info.UsesAllocated == 0)
{
- int mask = dest.Type.IsInteger()
+ int mask = dest.Type.IsInteger
? intLocalFreeRegisters
: vecLocalFreeRegisters;
@@ -343,9 +343,9 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
int selectedReg = BitOperations.TrailingZeroCount(mask);
- info.Register = Register(selectedReg, info.Type.ToRegisterType(), info.Type);
+ info.Register = Register(selectedReg, info.Type.Register, info.Type);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
intLocalFreeRegisters &= ~(1 << selectedReg);
intUsedRegisters |= 1 << selectedReg;
@@ -359,7 +359,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
else
{
info.Register = default;
- info.SpillOffset = Const(stackAlloc.Allocate(dest.Type.GetSizeInBytes()));
+ info.SpillOffset = Const(stackAlloc.Allocate(dest.Type.ByteSize));
}
}
@@ -377,7 +377,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
if (temp == default || info.Sequence != sequence)
{
- temp = dest.Type.IsInteger()
+ temp = dest.Type.IsInteger
? GetSpillTemp(dest, intSpillTempRegisters, ref intLocalAsg)
: GetSpillTemp(dest, vecSpillTempRegisters, ref vecLocalAsg);
@@ -443,7 +443,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
useMask |= 1 << selectedReg;
- return Register(selectedReg, local.Type.ToRegisterType(), local.Type);
+ return Register(selectedReg, local.Type.Register, local.Type);
}
private static int UsesCount(Operand local)
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
index 92fedf7bf..94883b39b 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
@@ -208,7 +208,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private bool TryAllocateRegWithoutSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount)
{
- RegisterType regType = current.Local.Type.ToRegisterType();
+ RegisterType regType = current.Local.Type.Register;
Span freePositions = stackalloc int[registersCount];
@@ -318,7 +318,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private void AllocateRegWithSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount)
{
- RegisterType regType = current.Local.Type.ToRegisterType();
+ RegisterType regType = current.Local.Type.Register;
Span usePositions = stackalloc int[registersCount];
Span blockedPositions = stackalloc int[registersCount];
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs
index 13995bc8d..b89034609 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs
@@ -10,7 +10,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public int Allocate(OperandType type)
{
- return Allocate(type.GetSizeInBytes());
+ return Allocate(type.ByteSize);
}
public int Allocate(int sizeInBytes)
diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs
index c27ee43cb..e12866990 100644
--- a/src/ARMeilleure/CodeGen/X86/Assembler.cs
+++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs
@@ -385,7 +385,7 @@ namespace ARMeilleure.CodeGen.X86
{
ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd];
- if (source.Type.IsInteger() || source.Kind == OperandKind.Memory)
+ if (source.Type.IsInteger || source.Kind == OperandKind.Memory)
{
WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRRM, rrm: true);
}
@@ -416,11 +416,11 @@ namespace ARMeilleure.CodeGen.X86
InstructionFlags flags = info.Flags | InstructionFlags.RexW;
- if (source.Type.IsInteger() || source.Kind == OperandKind.Memory)
+ if (source.Type.IsInteger || source.Kind == OperandKind.Memory)
{
WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRRM, rrm: true);
}
- else if (dest.Type.IsInteger() || dest.Kind == OperandKind.Memory)
+ else if (dest.Type.IsInteger || dest.Kind == OperandKind.Memory)
{
WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRMR);
}
@@ -1107,17 +1107,17 @@ namespace ARMeilleure.CodeGen.X86
}
else
{
- if (flags.HasFlag(InstructionFlags.Prefix66))
+ if ((flags & InstructionFlags.Prefix66) != 0)
{
WriteByte(0x66);
}
- if (flags.HasFlag(InstructionFlags.PrefixF2))
+ if ((flags & InstructionFlags.PrefixF2) != 0f)
{
WriteByte(0xf2);
}
- if (flags.HasFlag(InstructionFlags.PrefixF3))
+ if ((flags & InstructionFlags.PrefixF3) != 0f)
{
WriteByte(0xf3);
}
diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs
index 86acea4a8..ed425f476 100644
--- a/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs
+++ b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs
@@ -289,7 +289,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Popcnt(dest, source, dest.Type);
@@ -303,7 +303,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, source);
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
context.Assembler.WriteInstruction(info.Inst, dest, source);
@@ -315,7 +315,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && !source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && !source.Type.IsInteger);
if (operation.Intrinsic == Intrinsic.X86Cvtsi2si)
{
@@ -349,8 +349,8 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
}
- Debug.Assert(!dest.Type.IsInteger());
- Debug.Assert(!src2.Type.IsInteger() || src2.Kind == OperandKind.Constant);
+ Debug.Assert(!dest.Type.IsInteger);
+ Debug.Assert(!src2.Type.IsInteger || src2.Kind == OperandKind.Constant);
context.Assembler.WriteInstruction(info.Inst, dest, src1, src2);
@@ -370,7 +370,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
}
- Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger && src2.Type.IsInteger);
context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type);
@@ -385,7 +385,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
- Debug.Assert(dest.Type.IsInteger() && src1.Type.IsInteger() && src2.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && src1.Type.IsInteger && src2.Type.IsInteger);
context.Assembler.WriteInstruction(info.Inst, dest, src2, dest.Type);
@@ -405,7 +405,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
}
- Debug.Assert(!dest.Type.IsInteger() && src2.Kind == OperandKind.Constant);
+ Debug.Assert(!dest.Type.IsInteger && src2.Kind == OperandKind.Constant);
context.Assembler.WriteInstruction(info.Inst, dest, src1, src2.AsByte());
@@ -421,7 +421,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, src1, src2, src3);
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
if (info.Inst == X86Instruction.Blendvpd && HardwareCapabilities.SupportsVexEncoding)
{
@@ -461,7 +461,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
}
- Debug.Assert(!dest.Type.IsInteger() && src3.Kind == OperandKind.Constant);
+ Debug.Assert(!dest.Type.IsInteger && src3.Kind == OperandKind.Constant);
context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src3.AsByte());
@@ -512,7 +512,7 @@ namespace ARMeilleure.CodeGen.X86
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
// If Destination and Source 1 Operands are the same, perform a standard add as there are no benefits to using LEA.
if (dest.Kind == src1.Kind && dest.Value == src1.Value)
@@ -567,7 +567,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateBinOp(dest, src1, src2);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
// Note: GenerateCompareCommon makes the assumption that BitwiseAnd will emit only a single `and`
// instruction.
@@ -582,7 +582,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateBinOp(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Xor(dest, src2, dest.Type);
}
@@ -599,7 +599,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateUnOp(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Not(dest);
}
@@ -612,7 +612,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateBinOp(dest, src1, src2);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Or(dest, src2, dest.Type);
}
@@ -623,7 +623,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(comp.Kind == OperandKind.Constant);
- X86Condition cond = ((Comparison)comp.AsInt32()).ToX86Condition();
+ X86Condition cond = ((Comparison)comp.AsInt32()).X86;
GenerateCompareCommon(context, operation);
@@ -637,7 +637,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateUnOp(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Bswap(dest);
}
@@ -661,7 +661,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(dest.Type == OperandType.I32);
Debug.Assert(comp.Kind == OperandKind.Constant);
- X86Condition cond = ((Comparison)comp.AsInt32()).ToX86Condition();
+ X86Condition cond = ((Comparison)comp.AsInt32()).X86;
GenerateCompareCommon(context, operation);
@@ -676,7 +676,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(src1, src2);
- Debug.Assert(src1.Type.IsInteger());
+ Debug.Assert(src1.Type.IsInteger);
if (src2.Kind == OperandKind.Constant && src2.Value == 0)
{
@@ -766,7 +766,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src3);
EnsureSameType(dest, src2, src3);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
Debug.Assert(src1.Type == OperandType.I32);
context.Assembler.Test(src1, src1, src1.Type);
@@ -792,9 +792,9 @@ namespace ARMeilleure.CodeGen.X86
if (dest.Type == OperandType.FP32)
{
- Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP64);
+ Debug.Assert(source.Type.IsInteger || source.Type == OperandType.FP64);
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
context.Assembler.Xorps(dest, dest, dest);
context.Assembler.Cvtsi2ss(dest, dest, source, source.Type);
@@ -808,9 +808,9 @@ namespace ARMeilleure.CodeGen.X86
}
else /* if (dest.Type == OperandType.FP64) */
{
- Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP32);
+ Debug.Assert(source.Type.IsInteger || source.Type == OperandType.FP32);
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
context.Assembler.Xorps(dest, dest, dest);
context.Assembler.Cvtsi2sd(dest, dest, source, source.Type);
@@ -831,7 +831,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, source);
- Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant);
+ Debug.Assert(dest.Type.IsInteger || source.Kind != OperandKind.Constant);
// Moves to the same register are useless.
if (dest.Kind == source.Kind && dest.Value == source.Value)
@@ -845,7 +845,7 @@ namespace ARMeilleure.CodeGen.X86
// Assemble "mov reg, 0" as "xor reg, reg" as the later is more efficient.
context.Assembler.Xor(dest, dest, OperandType.I32);
}
- else if (dest.Type.IsInteger())
+ else if (dest.Type.IsInteger)
{
context.Assembler.Mov(dest, source, dest.Type);
}
@@ -862,7 +862,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Bsr(dest, source, dest.Type);
@@ -894,12 +894,12 @@ namespace ARMeilleure.CodeGen.X86
Operand dividend = operation.GetSource(0);
Operand divisor = operation.GetSource(1);
- if (!dest.Type.IsInteger())
+ if (!dest.Type.IsInteger)
{
ValidateBinOp(dest, dividend, divisor);
}
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
divisor = operation.GetSource(2);
@@ -932,7 +932,7 @@ namespace ARMeilleure.CodeGen.X86
Operand rdx = Register(X86Register.Rdx);
- Debug.Assert(divisor.Type.IsInteger());
+ Debug.Assert(divisor.Type.IsInteger);
context.Assembler.Xor(rdx, rdx, OperandType.I32);
context.Assembler.Div(divisor);
@@ -967,7 +967,7 @@ namespace ARMeilleure.CodeGen.X86
Operand value = operation.Destination;
Operand address = Memory(operation.GetSource(0), value.Type);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.Movzx16(value, address, value.Type);
}
@@ -977,7 +977,7 @@ namespace ARMeilleure.CodeGen.X86
Operand value = operation.Destination;
Operand address = Memory(operation.GetSource(0), value.Type);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.Movzx8(value, address, value.Type);
}
@@ -1000,7 +1000,7 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameType(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
if (src2.Kind == OperandKind.Constant)
{
@@ -1046,7 +1046,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateUnOp(dest, source);
- Debug.Assert(dest.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger);
context.Assembler.Neg(dest);
}
@@ -1107,7 +1107,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Movsx16(dest, source, dest.Type);
}
@@ -1117,7 +1117,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Movsx32(dest, source, dest.Type);
}
@@ -1127,7 +1127,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Movsx8(dest, source, dest.Type);
}
@@ -1187,7 +1187,7 @@ namespace ARMeilleure.CodeGen.X86
Operand value = operation.GetSource(1);
Operand address = Memory(operation.GetSource(0), value.Type);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.Mov16(address, value);
}
@@ -1197,7 +1197,7 @@ namespace ARMeilleure.CodeGen.X86
Operand value = operation.GetSource(1);
Operand address = Memory(operation.GetSource(0), value.Type);
- Debug.Assert(value.Type.IsInteger());
+ Debug.Assert(value.Type.IsInteger);
context.Assembler.Mov8(address, value);
}
@@ -1210,7 +1210,7 @@ namespace ARMeilleure.CodeGen.X86
ValidateBinOp(dest, src1, src2);
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
context.Assembler.Sub(dest, src2, dest.Type);
}
@@ -1236,7 +1236,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger && source.Type.IsInteger);
if (source.Type == OperandType.I32)
{
@@ -1259,7 +1259,7 @@ namespace ARMeilleure.CodeGen.X86
byte index = src2.AsByte();
- Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes());
+ Debug.Assert(index < OperandType.V128.ByteSize / dest.Type.ByteSize);
if (dest.Type == OperandType.I32)
{
@@ -1541,7 +1541,7 @@ namespace ARMeilleure.CodeGen.X86
{
Operand dest = operation.Destination;
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
context.Assembler.Pcmpeqw(dest, dest, dest);
}
@@ -1550,7 +1550,7 @@ namespace ARMeilleure.CodeGen.X86
{
Operand dest = operation.Destination;
- Debug.Assert(!dest.Type.IsInteger());
+ Debug.Assert(!dest.Type.IsInteger);
context.Assembler.Xorps(dest, dest, dest);
}
@@ -1580,7 +1580,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Movzx16(dest, source, OperandType.I32);
}
@@ -1590,7 +1590,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
// We can eliminate the move if source is already 32-bit and the registers are the same.
if (dest.Value == source.Value && source.Type == OperandType.I32)
@@ -1606,7 +1606,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination;
Operand source = operation.GetSource(0);
- Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
+ Debug.Assert(dest.Type.IsInteger && source.Type.IsInteger);
context.Assembler.Movzx8(dest, source, OperandType.I32);
}
@@ -1713,12 +1713,12 @@ namespace ARMeilleure.CodeGen.X86
EnsureSameReg(dest, src1);
EnsureSameType(dest, src1);
- Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32);
+ Debug.Assert(dest.Type.IsInteger && src2.Type == OperandType.I32);
}
private static void EnsureSameReg(Operand op1, Operand op2)
{
- if (!op1.Type.IsInteger() && HardwareCapabilities.SupportsVexEncoding)
+ if (!op1.Type.IsInteger && HardwareCapabilities.SupportsVexEncoding)
{
return;
}
diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocator.cs b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs
index 6b93efdfb..d3bc6be6a 100644
--- a/src/ARMeilleure/CodeGen/X86/PreAllocator.cs
+++ b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs
@@ -86,7 +86,7 @@ namespace ARMeilleure.CodeGen.X86
break;
case Instruction.Negate:
- if (!node.GetSource(0).Type.IsInteger())
+ if (!node.GetSource(0).Type.IsInteger)
{
GenerateNegate(block.Operations, node);
}
@@ -159,7 +159,7 @@ namespace ARMeilleure.CodeGen.X86
if (src1.Kind == OperandKind.Constant)
{
- if (!src1.Type.IsInteger())
+ if (!src1.Type.IsInteger)
{
// Handle non-integer types (FP32, FP64 and V128).
// For instructions without an immediate operand, we do the following:
@@ -208,7 +208,7 @@ namespace ARMeilleure.CodeGen.X86
if (src2.Kind == OperandKind.Constant)
{
- if (!src2.Type.IsInteger())
+ if (!src2.Type.IsInteger)
{
src2 = AddXmmCopy(nodes, node, src2);
@@ -298,7 +298,7 @@ namespace ARMeilleure.CodeGen.X86
// - The dividend is always in RDX:RAX.
// - The result is always in RAX.
// - Additionally it also writes the remainder in RDX.
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
Operand src1 = node.GetSource(0);
@@ -466,7 +466,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = node.Destination;
Operand source = node.GetSource(0);
- Debug.Assert(source.Type.IsInteger(), $"Invalid source type \"{source.Type}\".");
+ Debug.Assert(source.Type.IsInteger, $"Invalid source type \"{source.Type}\".");
Operation currentNode = node;
@@ -654,10 +654,10 @@ namespace ARMeilleure.CodeGen.X86
switch (operation.Instruction)
{
case Instruction.Add:
- return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger();
+ return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger;
case Instruction.Multiply:
case Instruction.Subtract:
- return !HardwareCapabilities.SupportsVexEncoding || operation.Destination.Type.IsInteger();
+ return !HardwareCapabilities.SupportsVexEncoding || operation.Destination.Type.IsInteger;
case Instruction.BitwiseAnd:
case Instruction.BitwiseExclusiveOr:
@@ -672,7 +672,7 @@ namespace ARMeilleure.CodeGen.X86
return true;
case Instruction.Divide:
- return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger();
+ return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger;
case Instruction.VectorInsert:
case Instruction.VectorInsert16:
diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
index cff1c7240..368c53789 100644
--- a/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
+++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
@@ -35,7 +35,7 @@ namespace ARMeilleure.CodeGen.X86
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount < intMax;
}
@@ -62,7 +62,7 @@ namespace ARMeilleure.CodeGen.X86
if (passOnReg)
{
- Operand argReg = source.Type.IsInteger()
+ Operand argReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
@@ -80,7 +80,7 @@ namespace ARMeilleure.CodeGen.X86
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
- stackOffset += source.Type.GetSizeInBytes();
+ stackOffset += source.Type.ByteSize;
}
}
@@ -102,7 +102,7 @@ namespace ARMeilleure.CodeGen.X86
}
else
{
- Operand retReg = dest.Type.IsInteger()
+ Operand retReg = dest.Type.IsInteger
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
@@ -137,7 +137,7 @@ namespace ARMeilleure.CodeGen.X86
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount + 1 < intMax;
}
@@ -160,7 +160,7 @@ namespace ARMeilleure.CodeGen.X86
if (passOnReg)
{
- Operand argReg = source.Type.IsInteger()
+ Operand argReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
@@ -210,7 +210,7 @@ namespace ARMeilleure.CodeGen.X86
{
OperandType argType = cctx.FuncArgTypes[cIndex];
- if (argType.IsInteger())
+ if (argType.IsInteger)
{
intCount++;
}
@@ -226,7 +226,7 @@ namespace ARMeilleure.CodeGen.X86
bool passOnReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
}
@@ -265,7 +265,7 @@ namespace ARMeilleure.CodeGen.X86
{
Operand pArg = Local(dest.Type);
- Operand argReg = dest.Type.IsInteger()
+ Operand argReg = dest.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
@@ -320,7 +320,7 @@ namespace ARMeilleure.CodeGen.X86
}
else
{
- Operand retReg = source.Type.IsInteger()
+ Operand retReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
index 52f72ac69..6f4458d74 100644
--- a/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
+++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
@@ -40,7 +40,7 @@ namespace ARMeilleure.CodeGen.X86
if (dest != default && dest.Type == OperandType.V128)
{
- int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
+ int stackOffset = AllocateOnStack(dest.Type.ByteSize);
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
@@ -76,7 +76,7 @@ namespace ARMeilleure.CodeGen.X86
{
Operand stackAddr = Local(OperandType.I64);
- int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
+ int stackOffset = AllocateOnStack(source.Type.ByteSize);
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
@@ -96,7 +96,7 @@ namespace ARMeilleure.CodeGen.X86
int argIndex = index + retArgs;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
}
@@ -140,7 +140,7 @@ namespace ARMeilleure.CodeGen.X86
}
else
{
- Operand retReg = dest.Type.IsInteger()
+ Operand retReg = dest.Type.IsInteger
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
@@ -171,7 +171,7 @@ namespace ARMeilleure.CodeGen.X86
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(1 + index);
- Operand argReg = source.Type.IsInteger()
+ Operand argReg = source.Type.IsInteger
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
@@ -219,7 +219,7 @@ namespace ARMeilleure.CodeGen.X86
{
Operand argReg, pArg;
- if (dest.Type.IsInteger())
+ if (dest.Type.IsInteger)
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
pArg = Local(dest.Type);
@@ -283,7 +283,7 @@ namespace ARMeilleure.CodeGen.X86
Operand source = node.GetSource(0);
Operand retReg;
- if (source.Type.IsInteger())
+ if (source.Type.IsInteger)
{
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
}
diff --git a/src/ARMeilleure/CodeGen/X86/X86Condition.cs b/src/ARMeilleure/CodeGen/X86/X86Condition.cs
index 70699a207..5153599b1 100644
--- a/src/ARMeilleure/CodeGen/X86/X86Condition.cs
+++ b/src/ARMeilleure/CodeGen/X86/X86Condition.cs
@@ -25,9 +25,9 @@ namespace ARMeilleure.CodeGen.X86
static class ComparisonX86Extensions
{
- public static X86Condition ToX86Condition(this Comparison comp)
+ extension(Comparison comparison)
{
- return comp switch
+ public X86Condition X86 => comparison switch
{
#pragma warning disable IDE0055 // Disable formatting
Comparison.Equal => X86Condition.Equal,
@@ -42,7 +42,7 @@ namespace ARMeilleure.CodeGen.X86
Comparison.LessUI => X86Condition.Below,
#pragma warning restore IDE0055
- _ => throw new ArgumentException(null, nameof(comp)),
+ _ => throw new ArgumentException(null, nameof(comparison))
};
}
}
diff --git a/src/ARMeilleure/Decoders/Condition.cs b/src/ARMeilleure/Decoders/Condition.cs
index 961825a10..bffe61ad5 100644
--- a/src/ARMeilleure/Decoders/Condition.cs
+++ b/src/ARMeilleure/Decoders/Condition.cs
@@ -22,11 +22,11 @@ namespace ARMeilleure.Decoders
static class ConditionExtensions
{
- public static Condition Invert(this Condition cond)
+ extension(Condition condition)
{
// Bit 0 of all conditions is basically a negation bit, so
// inverting this bit has the effect of inverting the condition.
- return (Condition)((int)cond ^ 1);
+ public Condition Inverse => (Condition)((int)condition ^ 1);
}
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs
index d30fb2fbd..a91716c64 100644
--- a/src/ARMeilleure/Instructions/InstEmitException.cs
+++ b/src/ARMeilleure/Instructions/InstEmitException.cs
@@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(op.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
@@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(op.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs
index 57af1522b..e5bad56ef 100644
--- a/src/ARMeilleure/Instructions/InstEmitException32.cs
+++ b/src/ARMeilleure/Instructions/InstEmitException32.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(context.CurrOp.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address));
}
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs
index a986bf66f..cb214d3d5 100644
--- a/src/ARMeilleure/Instructions/InstEmitFlow.cs
+++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs
@@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
{
OpCodeBReg op = (OpCodeBReg)context.CurrOp;
- context.Return(GetIntOrZR(context, op.Rn));
+ EmitReturn(context, GetIntOrZR(context, op.Rn));
}
public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true);
diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
index d0871b29f..74866f982 100644
--- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
+++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
@@ -13,6 +13,10 @@ namespace ARMeilleure.Instructions
{
static class InstEmitFlowHelper
{
+ // How many calls we can have in our call stack before we give up and return to the dispatcher.
+ // This prevents stack overflows caused by deep recursive calls.
+ private const int MaxCallDepth = 200;
+
public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond)
{
if (cond != Condition.Al)
@@ -182,12 +186,7 @@ namespace ARMeilleure.Instructions
{
if (isReturn || context.IsSingleStep)
{
- if (target.Type == OperandType.I32)
- {
- target = context.ZeroExtend32(OperandType.I64, target);
- }
-
- context.Return(target);
+ EmitReturn(context, target);
}
else
{
@@ -195,6 +194,19 @@ namespace ARMeilleure.Instructions
}
}
+ public static void EmitReturn(ArmEmitterContext context, Operand target)
+ {
+ Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
+ DecreaseCallDepth(context, nativeContext);
+
+ if (target.Type == OperandType.I32)
+ {
+ target = context.ZeroExtend32(OperandType.I64, target);
+ }
+
+ context.Return(target);
+ }
+
private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump)
{
context.StoreToContext();
@@ -257,6 +269,8 @@ namespace ARMeilleure.Instructions
if (isJump)
{
+ DecreaseCallDepth(context, nativeContext);
+
context.Tailcall(hostAddress, nativeContext);
}
else
@@ -278,8 +292,42 @@ namespace ARMeilleure.Instructions
Operand lblContinue = context.GetLabel(nextAddr.Value);
context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold);
+ DecreaseCallDepth(context, nativeContext);
+
context.Return(returnAddress);
}
}
+
+ public static void EmitCallDepthCheckAndIncrement(EmitterContext context, Operand guestAddress)
+ {
+ if (!Optimizations.EnableDeepCallRecursionProtection)
+ {
+ return;
+ }
+
+ Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
+ Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
+ Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
+ Operand lblDoCall = Label();
+
+ context.BranchIf(lblDoCall, currentCallDepth, Const(MaxCallDepth), Comparison.LessUI);
+ context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
+ context.Return(guestAddress);
+
+ context.MarkLabel(lblDoCall);
+ context.Store(callDepthAddr, context.Add(currentCallDepth, Const(1)));
+ }
+
+ private static void DecreaseCallDepth(EmitterContext context, Operand nativeContext)
+ {
+ if (!Optimizations.EnableDeepCallRecursionProtection)
+ {
+ return;
+ }
+
+ Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
+ Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
+ context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
+ }
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs
index 19a607dfc..92f9c9c35 100644
--- a/src/ARMeilleure/Instructions/InstEmitHashHelper.cs
+++ b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs
@@ -16,7 +16,7 @@ namespace ARMeilleure.Instructions
public static Operand EmitCrc32(ArmEmitterContext context, Operand crc, Operand value, int size, bool castagnoli)
{
- Debug.Assert(crc.Type.IsInteger() && value.Type.IsInteger());
+ Debug.Assert(crc.Type.IsInteger && value.Type.IsInteger);
Debug.Assert(size is >= 0 and < 4);
Debug.Assert((size < 3) || (value.Type == OperandType.I64));
diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
index bb7e997b2..3874d0464 100644
--- a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
+++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
@@ -157,7 +157,7 @@ namespace ARMeilleure.Instructions
context.Copy(temp, value);
- if (!context.Memory.Type.IsHostMappedOrTracked())
+ if (!context.Memory.Type.IsHostMappedOrTracked)
{
context.Branch(lblEnd);
@@ -198,7 +198,7 @@ namespace ARMeilleure.Instructions
SetInt(context, rt, value);
- if (!context.Memory.Type.IsHostMappedOrTracked())
+ if (!context.Memory.Type.IsHostMappedOrTracked)
{
context.Branch(lblEnd);
@@ -265,7 +265,7 @@ namespace ARMeilleure.Instructions
context.Copy(GetVec(rt), value);
- if (!context.Memory.Type.IsHostMappedOrTracked())
+ if (!context.Memory.Type.IsHostMappedOrTracked)
{
context.Branch(lblEnd);
@@ -312,7 +312,7 @@ namespace ARMeilleure.Instructions
break;
}
- if (!context.Memory.Type.IsHostMappedOrTracked())
+ if (!context.Memory.Type.IsHostMappedOrTracked)
{
context.Branch(lblEnd);
@@ -385,7 +385,7 @@ namespace ARMeilleure.Instructions
break;
}
- if (!context.Memory.Type.IsHostMappedOrTracked())
+ if (!context.Memory.Type.IsHostMappedOrTracked)
{
context.Branch(lblEnd);
@@ -399,11 +399,11 @@ namespace ARMeilleure.Instructions
public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size)
{
- if (context.Memory.Type.IsHostMapped())
+ if (context.Memory.Type.IsHostMapped)
{
return EmitHostMappedPointer(context, address);
}
- else if (context.Memory.Type.IsHostTracked())
+ else if (context.Memory.Type.IsHostTracked)
{
if (address.Type == OperandType.I32)
{
diff --git a/src/ARMeilleure/Instructions/SoftFallback.cs b/src/ARMeilleure/Instructions/SoftFallback.cs
deleted file mode 100644
index c227156e5..000000000
--- a/src/ARMeilleure/Instructions/SoftFallback.cs
+++ /dev/null
@@ -1,692 +0,0 @@
-using ARMeilleure.State;
-using System;
-using System.Runtime.InteropServices;
-
-namespace ARMeilleure.Instructions
-{
- static class SoftFallback
- {
- #region "ShrImm64"
- [UnmanagedCallersOnly]
- public static long SignedShrImm64(long value, long roundConst, int shift)
- {
- if (roundConst == 0L)
- {
- if (shift <= 63)
- {
- return value >> shift;
- }
- else /* if (shift == 64) */
- {
- if (value < 0L)
- {
- return -1L;
- }
- else /* if (value >= 0L) */
- {
- return 0L;
- }
- }
- }
- else /* if (roundConst == 1L << (shift - 1)) */
- {
- if (shift <= 63)
- {
- long add = value + roundConst;
-
- if ((~value & (value ^ add)) < 0L)
- {
- return (long)((ulong)add >> shift);
- }
- else
- {
- return add >> shift;
- }
- }
- else /* if (shift == 64) */
- {
- return 0L;
- }
- }
- }
-
- [UnmanagedCallersOnly]
- public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift)
- {
- if (roundConst == 0L)
- {
- if (shift <= 63)
- {
- return value >> shift;
- }
- else /* if (shift == 64) */
- {
- return 0UL;
- }
- }
- else /* if (roundConst == 1L << (shift - 1)) */
- {
- ulong add = value + (ulong)roundConst;
-
- if ((add < value) && (add < (ulong)roundConst))
- {
- if (shift <= 63)
- {
- return (add >> shift) | (0x8000000000000000UL >> (shift - 1));
- }
- else /* if (shift == 64) */
- {
- return 1UL;
- }
- }
- else
- {
- if (shift <= 63)
- {
- return add >> shift;
- }
- else /* if (shift == 64) */
- {
- return 0UL;
- }
- }
- }
- }
- #endregion
-
- #region "Saturation"
- [UnmanagedCallersOnly]
- public static int SatF32ToS32(float value)
- {
- if (float.IsNaN(value))
- {
- return 0;
- }
-
- return value >= int.MaxValue ? int.MaxValue :
- value <= int.MinValue ? int.MinValue : (int)value;
- }
-
- [UnmanagedCallersOnly]
- public static long SatF32ToS64(float value)
- {
- if (float.IsNaN(value))
- {
- return 0;
- }
-
- return value >= long.MaxValue ? long.MaxValue :
- value <= long.MinValue ? long.MinValue : (long)value;
- }
-
- [UnmanagedCallersOnly]
- public static uint SatF32ToU32(float value)
- {
- if (float.IsNaN(value))
- {
- return 0;
- }
-
- return value >= uint.MaxValue ? uint.MaxValue :
- value <= uint.MinValue ? uint.MinValue : (uint)value;
- }
-
- [UnmanagedCallersOnly]
- public static ulong SatF32ToU64(float value)
- {
- if (float.IsNaN(value))
- {
- return 0;
- }
-
- return value >= ulong.MaxValue ? ulong.MaxValue :
- value <= ulong.MinValue ? ulong.MinValue : (ulong)value;
- }
-
- [UnmanagedCallersOnly]
- public static int SatF64ToS32(double value)
- {
- if (double.IsNaN(value))
- {
- return 0;
- }
-
- return value >= int.MaxValue ? int.MaxValue :
- value <= int.MinValue ? int.MinValue : (int)value;
- }
-
- [UnmanagedCallersOnly]
- public static long SatF64ToS64(double value)
- {
- if (double.IsNaN(value))
- {
- return 0;
- }
-
- return value >= long.MaxValue ? long.MaxValue :
- value <= long.MinValue ? long.MinValue : (long)value;
- }
-
- [UnmanagedCallersOnly]
- public static uint SatF64ToU32(double value)
- {
- if (double.IsNaN(value))
- {
- return 0;
- }
-
- return value >= uint.MaxValue ? uint.MaxValue :
- value <= uint.MinValue ? uint.MinValue : (uint)value;
- }
-
- [UnmanagedCallersOnly]
- public static ulong SatF64ToU64(double value)
- {
- if (double.IsNaN(value))
- {
- return 0;
- }
-
- return value >= ulong.MaxValue ? ulong.MaxValue :
- value <= ulong.MinValue ? ulong.MinValue : (ulong)value;
- }
- #endregion
-
- #region "Count"
- [UnmanagedCallersOnly]
- public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
- {
- value ^= value >> 1;
-
- int highBit = size - 2;
-
- for (int bit = highBit; bit >= 0; bit--)
- {
- if (((int)(value >> bit) & 0b1) != 0)
- {
- return (ulong)(highBit - bit);
- }
- }
-
- return (ulong)(size - 1);
- }
-
- private static ReadOnlySpan ClzNibbleTbl => [4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0];
-
- [UnmanagedCallersOnly]
- public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
- {
- if (value == 0ul)
- {
- return (ulong)size;
- }
-
- int nibbleIdx = size;
- int preCount, count = 0;
-
- do
- {
- nibbleIdx -= 4;
- preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111];
- count += preCount;
- }
- while (preCount == 4);
-
- return (ulong)count;
- }
- #endregion
-
- #region "Table"
- [UnmanagedCallersOnly]
- public static V128 Tbl1(V128 vector, int bytes, V128 tb0)
- {
- return TblOrTbx(default, vector, bytes, tb0);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1)
- {
- return TblOrTbx(default, vector, bytes, tb0, tb1);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
- {
- return TblOrTbx(default, vector, bytes, tb0, tb1, tb2);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
- {
- return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0)
- {
- return TblOrTbx(dest, vector, bytes, tb0);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1)
- {
- return TblOrTbx(dest, vector, bytes, tb0, tb1);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
- {
- return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
- {
- return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3);
- }
-
- private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params ReadOnlySpan tb)
- {
- byte[] res = new byte[16];
-
- if (dest != default)
- {
- Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes);
- }
-
- byte[] table = new byte[tb.Length * 16];
-
- for (byte index = 0; index < tb.Length; index++)
- {
- Buffer.BlockCopy(tb[index].ToArray(), 0, table, index * 16, 16);
- }
-
- byte[] v = vector.ToArray();
-
- for (byte index = 0; index < bytes; index++)
- {
- byte tblIndex = v[index];
-
- if (tblIndex < table.Length)
- {
- res[index] = table[tblIndex];
- }
- }
-
- return new V128(res);
- }
- #endregion
-
- #region "Crc32"
- private const uint Crc32RevPoly = 0xedb88320;
- private const uint Crc32cRevPoly = 0x82f63b78;
-
- [UnmanagedCallersOnly]
- public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value);
-
- [UnmanagedCallersOnly]
- public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value);
- [UnmanagedCallersOnly]
- public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value);
-
- private static uint Crc32h(uint crc, uint poly, ushort val)
- {
- crc = Crc32(crc, poly, (byte)(val >> 0));
- crc = Crc32(crc, poly, (byte)(val >> 8));
-
- return crc;
- }
-
- private static uint Crc32w(uint crc, uint poly, uint val)
- {
- crc = Crc32(crc, poly, (byte)(val >> 0));
- crc = Crc32(crc, poly, (byte)(val >> 8));
- crc = Crc32(crc, poly, (byte)(val >> 16));
- crc = Crc32(crc, poly, (byte)(val >> 24));
-
- return crc;
- }
-
- private static uint Crc32x(uint crc, uint poly, ulong val)
- {
- crc = Crc32(crc, poly, (byte)(val >> 0));
- crc = Crc32(crc, poly, (byte)(val >> 8));
- crc = Crc32(crc, poly, (byte)(val >> 16));
- crc = Crc32(crc, poly, (byte)(val >> 24));
- crc = Crc32(crc, poly, (byte)(val >> 32));
- crc = Crc32(crc, poly, (byte)(val >> 40));
- crc = Crc32(crc, poly, (byte)(val >> 48));
- crc = Crc32(crc, poly, (byte)(val >> 56));
-
- return crc;
- }
-
- private static uint Crc32(uint crc, uint poly, byte val)
- {
- crc ^= val;
-
- for (int bit = 7; bit >= 0; bit--)
- {
- uint mask = (uint)(-(int)(crc & 1));
-
- crc = (crc >> 1) ^ (poly & mask);
- }
-
- return crc;
- }
- #endregion
-
- #region "Aes"
- [UnmanagedCallersOnly]
- public static V128 Decrypt(V128 value, V128 roundKey)
- {
- return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey));
- }
-
- [UnmanagedCallersOnly]
- public static V128 Encrypt(V128 value, V128 roundKey)
- {
- return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey));
- }
-
- [UnmanagedCallersOnly]
- public static V128 InverseMixColumns(V128 value)
- {
- return CryptoHelper.AesInvMixColumns(value);
- }
-
- [UnmanagedCallersOnly]
- public static V128 MixColumns(V128 value)
- {
- return CryptoHelper.AesMixColumns(value);
- }
- #endregion
-
- #region "Sha1"
- [UnmanagedCallersOnly]
- public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk)
- {
- for (int e = 0; e <= 3; e++)
- {
- uint t = ShaChoose(hash_abcd.Extract(1),
- hash_abcd.Extract(2),
- hash_abcd.Extract(3));
-
- hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
-
- t = Rol(hash_abcd.Extract(1), 30);
-
- hash_abcd.Insert(1, t);
-
- Rol32_160(ref hash_e, ref hash_abcd);
- }
-
- return hash_abcd;
- }
-
- [UnmanagedCallersOnly]
- public static uint FixedRotate(uint hash_e)
- {
- return hash_e.Rol(30);
- }
-
- [UnmanagedCallersOnly]
- public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk)
- {
- for (int e = 0; e <= 3; e++)
- {
- uint t = ShaMajority(hash_abcd.Extract(1),
- hash_abcd.Extract(2),
- hash_abcd.Extract(3));
-
- hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
-
- t = Rol(hash_abcd.Extract(1), 30);
-
- hash_abcd.Insert(1, t);
-
- Rol32_160(ref hash_e, ref hash_abcd);
- }
-
- return hash_abcd;
- }
-
- [UnmanagedCallersOnly]
- public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk)
- {
- for (int e = 0; e <= 3; e++)
- {
- uint t = ShaParity(hash_abcd.Extract(1),
- hash_abcd.Extract(2),
- hash_abcd.Extract(3));
-
- hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
-
- t = Rol(hash_abcd.Extract(1), 30);
-
- hash_abcd.Insert(1, t);
-
- Rol32_160(ref hash_e, ref hash_abcd);
- }
-
- return hash_abcd;
- }
-
- [UnmanagedCallersOnly]
- public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11)
- {
- ulong t2 = w4_7.Extract(0);
- ulong t1 = w0_3.Extract(1);
-
- V128 result = new(t1, t2);
-
- return result ^ (w0_3 ^ w8_11);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15)
- {
- V128 t = tw0_3 ^ (w12_15 >> 32);
-
- uint tE0 = t.Extract(0);
- uint tE1 = t.Extract(1);
- uint tE2 = t.Extract(2);
- uint tE3 = t.Extract(3);
-
- return new V128(tE0.Rol(1), tE1.Rol(1), tE2.Rol(1), tE3.Rol(1) ^ tE0.Rol(2));
- }
-
- private static void Rol32_160(ref uint y, ref V128 x)
- {
- uint xE3 = x.Extract(3);
-
- x <<= 32;
- x.Insert(0, y);
-
- y = xE3;
- }
-
- private static uint ShaChoose(uint x, uint y, uint z)
- {
- return ((y ^ z) & x) ^ z;
- }
-
- private static uint ShaMajority(uint x, uint y, uint z)
- {
- return (x & y) | ((x | y) & z);
- }
-
- private static uint ShaParity(uint x, uint y, uint z)
- {
- return x ^ y ^ z;
- }
-
- private static uint Rol(this uint value, int count)
- {
- return (value << count) | (value >> (32 - count));
- }
- #endregion
-
- #region "Sha256"
- [UnmanagedCallersOnly]
- public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk)
- {
- return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true);
- }
-
- [UnmanagedCallersOnly]
- public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk)
- {
- return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false);
- }
-
- [UnmanagedCallersOnly]
- public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7)
- {
- V128 result = new();
-
- for (int e = 0; e <= 3; e++)
- {
- uint elt = (e <= 2 ? w0_3 : w4_7).Extract(e <= 2 ? e + 1 : 0);
-
- elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3);
-
- elt += w0_3.Extract(e);
-
- result.Insert(e, elt);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15)
- {
- V128 result = new();
-
- ulong t1 = w12_15.Extract(1);
-
- for (int e = 0; e <= 1; e++)
- {
- uint elt = t1.ULongPart(e);
-
- elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10);
-
- elt += w0_3.Extract(e) + w8_11.Extract(e + 1);
-
- result.Insert(e, elt);
- }
-
- t1 = result.Extract(0);
-
- for (int e = 2; e <= 3; e++)
- {
- uint elt = t1.ULongPart(e - 2);
-
- elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10);
-
- elt += w0_3.Extract(e) + (e == 2 ? w8_11 : w12_15).Extract(e == 2 ? 3 : 0);
-
- result.Insert(e, elt);
- }
-
- return result;
- }
-
- private static V128 Sha256Hash(V128 x, V128 y, V128 w, bool part1)
- {
- for (int e = 0; e <= 3; e++)
- {
- uint chs = ShaChoose(y.Extract(0),
- y.Extract(1),
- y.Extract(2));
-
- uint maj = ShaMajority(x.Extract(0),
- x.Extract(1),
- x.Extract(2));
-
- uint t1 = y.Extract(3) + ShaHashSigma1(y.Extract(0)) + chs + w.Extract(e);
-
- uint t2 = t1 + x.Extract(3);
-
- x.Insert(3, t2);
-
- t2 = t1 + ShaHashSigma0(x.Extract(0)) + maj;
-
- y.Insert(3, t2);
-
- Rol32_256(ref y, ref x);
- }
-
- return part1 ? x : y;
- }
-
- private static void Rol32_256(ref V128 y, ref V128 x)
- {
- uint yE3 = y.Extract(3);
- uint xE3 = x.Extract(3);
-
- y <<= 32;
- x <<= 32;
-
- y.Insert(0, xE3);
- x.Insert(0, yE3);
- }
-
- private static uint ShaHashSigma0(uint x)
- {
- return x.Ror(2) ^ x.Ror(13) ^ x.Ror(22);
- }
-
- private static uint ShaHashSigma1(uint x)
- {
- return x.Ror(6) ^ x.Ror(11) ^ x.Ror(25);
- }
-
- private static uint Ror(this uint value, int count)
- {
- return (value >> count) | (value << (32 - count));
- }
-
- private static uint Lsr(this uint value, int count)
- {
- return value >> count;
- }
-
- private static uint ULongPart(this ulong value, int part)
- {
- return part == 0
- ? (uint)(value & 0xFFFFFFFFUL)
- : (uint)(value >> 32);
- }
- #endregion
-
- [UnmanagedCallersOnly]
- public static V128 PolynomialMult64_128(ulong op1, ulong op2)
- {
- V128 result = V128.Zero;
-
- V128 op2_128 = new(op2, 0);
-
- for (int i = 0; i < 64; i++)
- {
- if (((op1 >> i) & 1) == 1)
- {
- result ^= op2_128 << i;
- }
- }
-
- return result;
- }
- }
-}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Aes.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Aes.cs
new file mode 100644
index 000000000..fe133929d
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Aes.cs
@@ -0,0 +1,32 @@
+using ARMeilleure.State;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static V128 Decrypt(V128 value, V128 roundKey)
+ {
+ return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey));
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Encrypt(V128 value, V128 roundKey)
+ {
+ return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey));
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 InverseMixColumns(V128 value)
+ {
+ return CryptoHelper.AesInvMixColumns(value);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 MixColumns(V128 value)
+ {
+ return CryptoHelper.AesMixColumns(value);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Count.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Count.cs
new file mode 100644
index 000000000..cbe2a79ca
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Count.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
+ {
+ value ^= value >> 1;
+
+ int highBit = size - 2;
+
+ for (int bit = highBit; bit >= 0; bit--)
+ {
+ if (((int)(value >> bit) & 0b1) != 0)
+ {
+ return (ulong)(highBit - bit);
+ }
+ }
+
+ return (ulong)(size - 1);
+ }
+
+ private static ReadOnlySpan ClzNibbleTbl => [4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0];
+
+ [UnmanagedCallersOnly]
+ public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
+ {
+ if (value == 0ul)
+ {
+ return (ulong)size;
+ }
+
+ int nibbleIdx = size;
+ int preCount, count = 0;
+
+ do
+ {
+ nibbleIdx -= 4;
+ preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111];
+ count += preCount;
+ }
+ while (preCount == 4);
+
+ return (ulong)count;
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Crc32.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Crc32.cs
new file mode 100644
index 000000000..505cb032f
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Crc32.cs
@@ -0,0 +1,74 @@
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ private const uint Crc32RevPoly = 0xedb88320;
+ private const uint Crc32cRevPoly = 0x82f63b78;
+
+ [UnmanagedCallersOnly]
+ public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value);
+
+ [UnmanagedCallersOnly]
+ public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value);
+ [UnmanagedCallersOnly]
+ public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value);
+
+ private static uint Crc32h(uint crc, uint poly, ushort val)
+ {
+ crc = Crc32(crc, poly, (byte)(val >> 0));
+ crc = Crc32(crc, poly, (byte)(val >> 8));
+
+ return crc;
+ }
+
+ private static uint Crc32w(uint crc, uint poly, uint val)
+ {
+ crc = Crc32(crc, poly, (byte)(val >> 0));
+ crc = Crc32(crc, poly, (byte)(val >> 8));
+ crc = Crc32(crc, poly, (byte)(val >> 16));
+ crc = Crc32(crc, poly, (byte)(val >> 24));
+
+ return crc;
+ }
+
+ private static uint Crc32x(uint crc, uint poly, ulong val)
+ {
+ crc = Crc32(crc, poly, (byte)(val >> 0));
+ crc = Crc32(crc, poly, (byte)(val >> 8));
+ crc = Crc32(crc, poly, (byte)(val >> 16));
+ crc = Crc32(crc, poly, (byte)(val >> 24));
+ crc = Crc32(crc, poly, (byte)(val >> 32));
+ crc = Crc32(crc, poly, (byte)(val >> 40));
+ crc = Crc32(crc, poly, (byte)(val >> 48));
+ crc = Crc32(crc, poly, (byte)(val >> 56));
+
+ return crc;
+ }
+
+ private static uint Crc32(uint crc, uint poly, byte val)
+ {
+ crc ^= val;
+
+ for (int bit = 7; bit >= 0; bit--)
+ {
+ uint mask = (uint)(-(int)(crc & 1));
+
+ crc = (crc >> 1) ^ (poly & mask);
+ }
+
+ return crc;
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Saturation.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Saturation.cs
new file mode 100644
index 000000000..2cbea1560
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Saturation.cs
@@ -0,0 +1,103 @@
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static int SatF32ToS32(float value)
+ {
+ if (float.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= int.MaxValue ? int.MaxValue :
+ value <= int.MinValue ? int.MinValue : (int)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static long SatF32ToS64(float value)
+ {
+ if (float.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= long.MaxValue ? long.MaxValue :
+ value <= long.MinValue ? long.MinValue : (long)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static uint SatF32ToU32(float value)
+ {
+ if (float.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= uint.MaxValue ? uint.MaxValue :
+ value <= uint.MinValue ? uint.MinValue : (uint)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static ulong SatF32ToU64(float value)
+ {
+ if (float.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= ulong.MaxValue ? ulong.MaxValue :
+ value <= ulong.MinValue ? ulong.MinValue : (ulong)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static int SatF64ToS32(double value)
+ {
+ if (double.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= int.MaxValue ? int.MaxValue :
+ value <= int.MinValue ? int.MinValue : (int)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static long SatF64ToS64(double value)
+ {
+ if (double.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= long.MaxValue ? long.MaxValue :
+ value <= long.MinValue ? long.MinValue : (long)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static uint SatF64ToU32(double value)
+ {
+ if (double.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= uint.MaxValue ? uint.MaxValue :
+ value <= uint.MinValue ? uint.MinValue : (uint)value;
+ }
+
+ [UnmanagedCallersOnly]
+ public static ulong SatF64ToU64(double value)
+ {
+ if (double.IsNaN(value))
+ {
+ return 0;
+ }
+
+ return value >= ulong.MaxValue ? ulong.MaxValue :
+ value <= ulong.MinValue ? ulong.MinValue : (ulong)value;
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha1.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha1.cs
new file mode 100644
index 000000000..62aecced7
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha1.cs
@@ -0,0 +1,131 @@
+using ARMeilleure.State;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk)
+ {
+ for (int e = 0; e <= 3; e++)
+ {
+ uint t = ShaChoose(hash_abcd.Extract(1),
+ hash_abcd.Extract(2),
+ hash_abcd.Extract(3));
+
+ hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
+
+ t = Rol(hash_abcd.Extract(1), 30);
+
+ hash_abcd.Insert(1, t);
+
+ Rol32_160(ref hash_e, ref hash_abcd);
+ }
+
+ return hash_abcd;
+ }
+
+ [UnmanagedCallersOnly]
+ public static uint FixedRotate(uint hash_e)
+ {
+ return hash_e.Rol(30);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk)
+ {
+ for (int e = 0; e <= 3; e++)
+ {
+ uint t = ShaMajority(hash_abcd.Extract(1),
+ hash_abcd.Extract(2),
+ hash_abcd.Extract(3));
+
+ hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
+
+ t = Rol(hash_abcd.Extract(1), 30);
+
+ hash_abcd.Insert(1, t);
+
+ Rol32_160(ref hash_e, ref hash_abcd);
+ }
+
+ return hash_abcd;
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk)
+ {
+ for (int e = 0; e <= 3; e++)
+ {
+ uint t = ShaParity(hash_abcd.Extract(1),
+ hash_abcd.Extract(2),
+ hash_abcd.Extract(3));
+
+ hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e);
+
+ t = Rol(hash_abcd.Extract(1), 30);
+
+ hash_abcd.Insert(1, t);
+
+ Rol32_160(ref hash_e, ref hash_abcd);
+ }
+
+ return hash_abcd;
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11)
+ {
+ ulong t2 = w4_7.Extract(0);
+ ulong t1 = w0_3.Extract(1);
+
+ V128 result = new(t1, t2);
+
+ return result ^ (w0_3 ^ w8_11);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15)
+ {
+ V128 t = tw0_3 ^ (w12_15 >> 32);
+
+ uint tE0 = t.Extract(0);
+ uint tE1 = t.Extract(1);
+ uint tE2 = t.Extract(2);
+ uint tE3 = t.Extract(3);
+
+ return new V128(tE0.Rol(1), tE1.Rol(1), tE2.Rol(1), tE3.Rol(1) ^ tE0.Rol(2));
+ }
+
+ private static void Rol32_160(ref uint y, ref V128 x)
+ {
+ uint xE3 = x.Extract(3);
+
+ x <<= 32;
+ x.Insert(0, y);
+
+ y = xE3;
+ }
+
+ private static uint ShaChoose(uint x, uint y, uint z)
+ {
+ return ((y ^ z) & x) ^ z;
+ }
+
+ private static uint ShaMajority(uint x, uint y, uint z)
+ {
+ return (x & y) | ((x | y) & z);
+ }
+
+ private static uint ShaParity(uint x, uint y, uint z)
+ {
+ return x ^ y ^ z;
+ }
+
+ private static uint Rol(this uint value, int count)
+ {
+ return (value << count) | (value >> (32 - count));
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha256.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha256.cs
new file mode 100644
index 000000000..9ce5ec5f9
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Sha256.cs
@@ -0,0 +1,140 @@
+using ARMeilleure.State;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk)
+ {
+ return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk)
+ {
+ return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7)
+ {
+ V128 result = new();
+
+ for (int e = 0; e <= 3; e++)
+ {
+ uint elt = (e <= 2 ? w0_3 : w4_7).Extract(e <= 2 ? e + 1 : 0);
+
+ elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3);
+
+ elt += w0_3.Extract(e);
+
+ result.Insert(e, elt);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15)
+ {
+ V128 result = new();
+
+ ulong t1 = w12_15.Extract(1);
+
+ for (int e = 0; e <= 1; e++)
+ {
+ uint elt = t1.ULongPart(e);
+
+ elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10);
+
+ elt += w0_3.Extract(e) + w8_11.Extract(e + 1);
+
+ result.Insert(e, elt);
+ }
+
+ t1 = result.Extract(0);
+
+ for (int e = 2; e <= 3; e++)
+ {
+ uint elt = t1.ULongPart(e - 2);
+
+ elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10);
+
+ elt += w0_3.Extract(e) + (e == 2 ? w8_11 : w12_15).Extract(e == 2 ? 3 : 0);
+
+ result.Insert(e, elt);
+ }
+
+ return result;
+ }
+
+ private static V128 Sha256Hash(V128 x, V128 y, V128 w, bool part1)
+ {
+ for (int e = 0; e <= 3; e++)
+ {
+ uint chs = ShaChoose(y.Extract(0),
+ y.Extract(1),
+ y.Extract(2));
+
+ uint maj = ShaMajority(x.Extract(0),
+ x.Extract(1),
+ x.Extract(2));
+
+ uint t1 = y.Extract(3) + ShaHashSigma1(y.Extract(0)) + chs + w.Extract(e);
+
+ uint t2 = t1 + x.Extract(3);
+
+ x.Insert(3, t2);
+
+ t2 = t1 + ShaHashSigma0(x.Extract(0)) + maj;
+
+ y.Insert(3, t2);
+
+ Rol32_256(ref y, ref x);
+ }
+
+ return part1 ? x : y;
+ }
+
+ private static void Rol32_256(ref V128 y, ref V128 x)
+ {
+ uint yE3 = y.Extract(3);
+ uint xE3 = x.Extract(3);
+
+ y <<= 32;
+ x <<= 32;
+
+ y.Insert(0, xE3);
+ x.Insert(0, yE3);
+ }
+
+ private static uint ShaHashSigma0(uint x)
+ {
+ return x.Ror(2) ^ x.Ror(13) ^ x.Ror(22);
+ }
+
+ private static uint ShaHashSigma1(uint x)
+ {
+ return x.Ror(6) ^ x.Ror(11) ^ x.Ror(25);
+ }
+
+ private static uint Ror(this uint value, int count)
+ {
+ return (value >> count) | (value << (32 - count));
+ }
+
+ private static uint Lsr(this uint value, int count)
+ {
+ return value >> count;
+ }
+
+ private static uint ULongPart(this ulong value, int part)
+ {
+ return part == 0
+ ? (uint)(value & 0xFFFFFFFFUL)
+ : (uint)(value >> 32);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.ShrImm64.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.ShrImm64.cs
new file mode 100644
index 000000000..063b2c939
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.ShrImm64.cs
@@ -0,0 +1,93 @@
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static long SignedShrImm64(long value, long roundConst, int shift)
+ {
+ if (roundConst == 0L)
+ {
+ if (shift <= 63)
+ {
+ return value >> shift;
+ }
+ else /* if (shift == 64) */
+ {
+ if (value < 0L)
+ {
+ return -1L;
+ }
+ else /* if (value >= 0L) */
+ {
+ return 0L;
+ }
+ }
+ }
+ else /* if (roundConst == 1L << (shift - 1)) */
+ {
+ if (shift <= 63)
+ {
+ long add = value + roundConst;
+
+ if ((~value & (value ^ add)) < 0L)
+ {
+ return (long)((ulong)add >> shift);
+ }
+ else
+ {
+ return add >> shift;
+ }
+ }
+ else /* if (shift == 64) */
+ {
+ return 0L;
+ }
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift)
+ {
+ if (roundConst == 0L)
+ {
+ if (shift <= 63)
+ {
+ return value >> shift;
+ }
+ else /* if (shift == 64) */
+ {
+ return 0UL;
+ }
+ }
+ else /* if (roundConst == 1L << (shift - 1)) */
+ {
+ ulong add = value + (ulong)roundConst;
+
+ if ((add < value) && (add < (ulong)roundConst))
+ {
+ if (shift <= 63)
+ {
+ return (add >> shift) | (0x8000000000000000UL >> (shift - 1));
+ }
+ else /* if (shift == 64) */
+ {
+ return 1UL;
+ }
+ }
+ else
+ {
+ if (shift <= 63)
+ {
+ return add >> shift;
+ }
+ else /* if (shift == 64) */
+ {
+ return 0UL;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Table.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Table.cs
new file mode 100644
index 000000000..61fa178df
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.Table.cs
@@ -0,0 +1,88 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static V128 Tbl1(V128 vector, int bytes, V128 tb0)
+ {
+ return TblOrTbx(default, vector, bytes, tb0);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1)
+ {
+ return TblOrTbx(default, vector, bytes, tb0, tb1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
+ {
+ return TblOrTbx(default, vector, bytes, tb0, tb1, tb2);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
+ {
+ return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0)
+ {
+ return TblOrTbx(dest, vector, bytes, tb0);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1)
+ {
+ return TblOrTbx(dest, vector, bytes, tb0, tb1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
+ {
+ return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2);
+ }
+
+ [UnmanagedCallersOnly]
+ public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
+ {
+ return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3);
+ }
+
+ private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params ReadOnlySpan tb)
+ {
+ byte[] res = new byte[16];
+
+ if (dest != default)
+ {
+ Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes);
+ }
+
+ byte[] table = new byte[tb.Length * 16];
+
+ for (byte index = 0; index < tb.Length; index++)
+ {
+ Buffer.BlockCopy(tb[index].ToArray(), 0, table, index * 16, 16);
+ }
+
+ byte[] v = vector.ToArray();
+
+ for (byte index = 0; index < bytes; index++)
+ {
+ byte tblIndex = v[index];
+
+ if (tblIndex < table.Length)
+ {
+ res[index] = table[tblIndex];
+ }
+ }
+
+ return new V128(res);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.cs b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.cs
new file mode 100644
index 000000000..a5baae782
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFallback/SoftFallback.cs
@@ -0,0 +1,26 @@
+using ARMeilleure.State;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static partial class SoftFallback
+ {
+ [UnmanagedCallersOnly]
+ public static V128 PolynomialMult64_128(ulong op1, ulong op2)
+ {
+ V128 result = V128.Zero;
+
+ V128 op2_128 = new(op2, 0);
+
+ for (int i = 0; i < 64; i++)
+ {
+ if (((op1 >> i) & 1) == 1)
+ {
+ result ^= op2_128 << i;
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat.cs b/src/ARMeilleure/Instructions/SoftFloat.cs
deleted file mode 100644
index ccc45cc64..000000000
--- a/src/ARMeilleure/Instructions/SoftFloat.cs
+++ /dev/null
@@ -1,3735 +0,0 @@
-using ARMeilleure.State;
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace ARMeilleure.Instructions
-{
- static class SoftFloat
- {
- static SoftFloat()
- {
- RecipEstimateTable = BuildRecipEstimateTable();
- RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable();
- }
-
- public static readonly byte[] RecipEstimateTable;
- public static readonly byte[] RecipSqrtEstimateTable;
-
- private static byte[] BuildRecipEstimateTable()
- {
- byte[] tbl = new byte[256];
-
- for (int idx = 0; idx < 256; idx++)
- {
- uint src = (uint)idx + 256u;
-
- Debug.Assert(src is >= 256u and < 512u);
-
- src = (src << 1) + 1u;
-
- uint aux = (1u << 19) / src;
-
- uint dst = (aux + 1u) >> 1;
-
- Debug.Assert(dst is >= 256u and < 512u);
-
- tbl[idx] = (byte)(dst - 256u);
- }
-
- return tbl;
- }
-
- private static byte[] BuildRecipSqrtEstimateTable()
- {
- byte[] tbl = new byte[384];
-
- for (int idx = 0; idx < 384; idx++)
- {
- uint src = (uint)idx + 128u;
-
- Debug.Assert(src is >= 128u and < 512u);
-
- if (src < 256u)
- {
- src = (src << 1) + 1u;
- }
- else
- {
- src = (src >> 1) << 1;
- src = (src + 1u) << 1;
- }
-
- uint aux = 512u;
-
- while (src * (aux + 1u) * (aux + 1u) < (1u << 28))
- {
- aux++;
- }
-
- uint dst = (aux + 1u) >> 1;
-
- Debug.Assert(dst is >= 256u and < 512u);
-
- tbl[idx] = (byte)(dst - 256u);
- }
-
- return tbl;
- }
-
- public static void FPProcessException(FPException exc, ExecutionContext context)
- {
- FPProcessException(exc, context, context.Fpcr);
- }
-
- public static void FPProcessException(FPException exc, ExecutionContext context, FPCR fpcr)
- {
- int enable = (int)exc + 8;
-
- if ((fpcr & (FPCR)(1 << enable)) != 0)
- {
- throw new NotImplementedException("Floating-point trap handling.");
- }
- else
- {
- context.Fpsr |= (FPSR)(1 << (int)exc);
- }
- }
-
- public static FPRoundingMode GetRoundingMode(this FPCR fpcr)
- {
- const int RModeShift = 22;
-
- return (FPRoundingMode)(((uint)fpcr >> RModeShift) & 3u);
- }
- }
-
- static class SoftFloat16
- {
- public static ushort FPDefaultNaN()
- {
- return (ushort)0x7E00u;
- }
-
- public static ushort FPInfinity(bool sign)
- {
- return sign ? (ushort)0xFC00u : (ushort)0x7C00u;
- }
-
- public static ushort FPZero(bool sign)
- {
- return sign ? (ushort)0x8000u : (ushort)0x0000u;
- }
-
- public static ushort FPMaxNormal(bool sign)
- {
- return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu;
- }
-
- public static double FPUnpackCv(
- this ushort valueBits,
- out FPType type,
- out bool sign,
- ExecutionContext context)
- {
- sign = (~(uint)valueBits & 0x8000u) == 0u;
-
- uint exp16 = ((uint)valueBits & 0x7C00u) >> 10;
- uint frac16 = (uint)valueBits & 0x03FFu;
-
- double real;
-
- if (exp16 == 0u)
- {
- if (frac16 == 0u)
- {
- type = FPType.Zero;
- real = 0d;
- }
- else
- {
- type = FPType.Nonzero; // Subnormal.
- real = Math.Pow(2d, -14) * ((double)frac16 * Math.Pow(2d, -10));
- }
- }
- else if (exp16 == 0x1Fu && (context.Fpcr & FPCR.Ahp) == 0)
- {
- if (frac16 == 0u)
- {
- type = FPType.Infinity;
- real = Math.Pow(2d, 1000);
- }
- else
- {
- type = (~frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN;
- real = 0d;
- }
- }
- else
- {
- type = FPType.Nonzero; // Normal.
- real = Math.Pow(2d, (int)exp16 - 15) * (1d + (double)frac16 * Math.Pow(2d, -10));
- }
-
- return sign ? -real : real;
- }
-
- public static ushort FPRoundCv(double real, ExecutionContext context)
- {
- const int MinimumExp = -14;
-
- const int E = 5;
- const int F = 10;
-
- bool sign;
- double mantissa;
-
- if (real < 0d)
- {
- sign = true;
- mantissa = -real;
- }
- else
- {
- sign = false;
- mantissa = real;
- }
-
- int exponent = 0;
-
- while (mantissa < 1d)
- {
- mantissa *= 2d;
- exponent--;
- }
-
- while (mantissa >= 2d)
- {
- mantissa /= 2d;
- exponent++;
- }
-
- uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
-
- if (biasedExp == 0u)
- {
- mantissa /= Math.Pow(2d, MinimumExp - exponent);
- }
-
- uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F));
- double error = mantissa * Math.Pow(2d, F) - (double)intMant;
-
- if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
- {
- SoftFloat.FPProcessException(FPException.Underflow, context);
- }
-
- bool overflowToInf;
- bool roundUp;
-
- switch (context.Fpcr.GetRoundingMode())
- {
- case FPRoundingMode.ToNearest:
- roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
- overflowToInf = true;
- break;
-
- case FPRoundingMode.TowardsPlusInfinity:
- roundUp = (error != 0d && !sign);
- overflowToInf = !sign;
- break;
-
- case FPRoundingMode.TowardsMinusInfinity:
- roundUp = (error != 0d && sign);
- overflowToInf = sign;
- break;
-
- case FPRoundingMode.TowardsZero:
- roundUp = false;
- overflowToInf = false;
- break;
-
- default:
- throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\".");
- }
-
- if (roundUp)
- {
- intMant++;
-
- if (intMant == 1u << F)
- {
- biasedExp = 1u;
- }
-
- if (intMant == 1u << (F + 1))
- {
- biasedExp++;
- intMant >>= 1;
- }
- }
-
- ushort resultBits;
-
- if ((context.Fpcr & FPCR.Ahp) == 0)
- {
- if (biasedExp >= (1u << E) - 1u)
- {
- resultBits = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
-
- SoftFloat.FPProcessException(FPException.Overflow, context);
-
- error = 1d;
- }
- else
- {
- resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu));
- }
- }
- else
- {
- if (biasedExp >= 1u << E)
- {
- resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
-
- error = 0d;
- }
- else
- {
- resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu));
- }
- }
-
- if (error != 0d)
- {
- SoftFloat.FPProcessException(FPException.Inexact, context);
- }
-
- return resultBits;
- }
- }
-
- static class SoftFloat16_32
- {
- [UnmanagedCallersOnly]
- public static float FPConvert(ushort valueBits)
- {
- ExecutionContext context = NativeInterface.GetContext();
-
- double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context);
-
- float result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- if ((context.Fpcr & FPCR.Dn) != 0)
- {
- result = SoftFloat32.FPDefaultNaN();
- }
- else
- {
- result = FPConvertNaN(valueBits);
- }
-
- if (type == FPType.SNaN)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- }
- else if (type == FPType.Infinity)
- {
- result = SoftFloat32.FPInfinity(sign);
- }
- else if (type == FPType.Zero)
- {
- result = SoftFloat32.FPZero(sign);
- }
- else
- {
- result = FPRoundCv(real, context);
- }
-
- return result;
- }
-
- private static float FPRoundCv(double real, ExecutionContext context)
- {
- const int MinimumExp = -126;
-
- const int E = 8;
- const int F = 23;
-
- bool sign;
- double mantissa;
-
- if (real < 0d)
- {
- sign = true;
- mantissa = -real;
- }
- else
- {
- sign = false;
- mantissa = real;
- }
-
- int exponent = 0;
-
- while (mantissa < 1d)
- {
- mantissa *= 2d;
- exponent--;
- }
-
- while (mantissa >= 2d)
- {
- mantissa /= 2d;
- exponent++;
- }
-
- if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp)
- {
- context.Fpsr |= FPSR.Ufc;
-
- return SoftFloat32.FPZero(sign);
- }
-
- uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
-
- if (biasedExp == 0u)
- {
- mantissa /= Math.Pow(2d, MinimumExp - exponent);
- }
-
- uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F));
- double error = mantissa * Math.Pow(2d, F) - (double)intMant;
-
- if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
- {
- SoftFloat.FPProcessException(FPException.Underflow, context);
- }
-
- bool overflowToInf;
- bool roundUp;
-
- switch (context.Fpcr.GetRoundingMode())
- {
- case FPRoundingMode.ToNearest:
- roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
- overflowToInf = true;
- break;
-
- case FPRoundingMode.TowardsPlusInfinity:
- roundUp = (error != 0d && !sign);
- overflowToInf = !sign;
- break;
-
- case FPRoundingMode.TowardsMinusInfinity:
- roundUp = (error != 0d && sign);
- overflowToInf = sign;
- break;
-
- case FPRoundingMode.TowardsZero:
- roundUp = false;
- overflowToInf = false;
- break;
-
- default:
- throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\".");
- }
-
- if (roundUp)
- {
- intMant++;
-
- if (intMant == 1u << F)
- {
- biasedExp = 1u;
- }
-
- if (intMant == 1u << (F + 1))
- {
- biasedExp++;
- intMant >>= 1;
- }
- }
-
- float result;
-
- if (biasedExp >= (1u << E) - 1u)
- {
- result = overflowToInf ? SoftFloat32.FPInfinity(sign) : SoftFloat32.FPMaxNormal(sign);
-
- SoftFloat.FPProcessException(FPException.Overflow, context);
-
- error = 1d;
- }
- else
- {
- result = BitConverter.Int32BitsToSingle(
- (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu)));
- }
-
- if (error != 0d)
- {
- SoftFloat.FPProcessException(FPException.Inexact, context);
- }
-
- return result;
- }
-
- private static float FPConvertNaN(ushort valueBits)
- {
- return BitConverter.Int32BitsToSingle(
- (int)(((uint)valueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)valueBits & 0x01FFu) << 13));
- }
- }
-
- static class SoftFloat16_64
- {
- [UnmanagedCallersOnly]
- public static double FPConvert(ushort valueBits)
- {
- ExecutionContext context = NativeInterface.GetContext();
-
- double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context);
-
- double result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- if ((context.Fpcr & FPCR.Dn) != 0)
- {
- result = SoftFloat64.FPDefaultNaN();
- }
- else
- {
- result = FPConvertNaN(valueBits);
- }
-
- if (type == FPType.SNaN)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- }
- else if (type == FPType.Infinity)
- {
- result = SoftFloat64.FPInfinity(sign);
- }
- else if (type == FPType.Zero)
- {
- result = SoftFloat64.FPZero(sign);
- }
- else
- {
- result = FPRoundCv(real, context);
- }
-
- return result;
- }
-
- private static double FPRoundCv(double real, ExecutionContext context)
- {
- const int MinimumExp = -1022;
-
- const int E = 11;
- const int F = 52;
-
- bool sign;
- double mantissa;
-
- if (real < 0d)
- {
- sign = true;
- mantissa = -real;
- }
- else
- {
- sign = false;
- mantissa = real;
- }
-
- int exponent = 0;
-
- while (mantissa < 1d)
- {
- mantissa *= 2d;
- exponent--;
- }
-
- while (mantissa >= 2d)
- {
- mantissa /= 2d;
- exponent++;
- }
-
- if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp)
- {
- context.Fpsr |= FPSR.Ufc;
-
- return SoftFloat64.FPZero(sign);
- }
-
- uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
-
- if (biasedExp == 0u)
- {
- mantissa /= Math.Pow(2d, MinimumExp - exponent);
- }
-
- ulong intMant = (ulong)Math.Floor(mantissa * Math.Pow(2d, F));
- double error = mantissa * Math.Pow(2d, F) - (double)intMant;
-
- if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
- {
- SoftFloat.FPProcessException(FPException.Underflow, context);
- }
-
- bool overflowToInf;
- bool roundUp;
-
- switch (context.Fpcr.GetRoundingMode())
- {
- case FPRoundingMode.ToNearest:
- roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
- overflowToInf = true;
- break;
-
- case FPRoundingMode.TowardsPlusInfinity:
- roundUp = (error != 0d && !sign);
- overflowToInf = !sign;
- break;
-
- case FPRoundingMode.TowardsMinusInfinity:
- roundUp = (error != 0d && sign);
- overflowToInf = sign;
- break;
-
- case FPRoundingMode.TowardsZero:
- roundUp = false;
- overflowToInf = false;
- break;
-
- default:
- throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\".");
- }
-
- if (roundUp)
- {
- intMant++;
-
- if (intMant == 1ul << F)
- {
- biasedExp = 1u;
- }
-
- if (intMant == 1ul << (F + 1))
- {
- biasedExp++;
- intMant >>= 1;
- }
- }
-
- double result;
-
- if (biasedExp >= (1u << E) - 1u)
- {
- result = overflowToInf ? SoftFloat64.FPInfinity(sign) : SoftFloat64.FPMaxNormal(sign);
-
- SoftFloat.FPProcessException(FPException.Overflow, context);
-
- error = 1d;
- }
- else
- {
- result = BitConverter.Int64BitsToDouble(
- (long)((sign ? 1ul : 0ul) << 63 | (biasedExp & 0x7FFul) << 52 | (intMant & 0x000FFFFFFFFFFFFFul)));
- }
-
- if (error != 0d)
- {
- SoftFloat.FPProcessException(FPException.Inexact, context);
- }
-
- return result;
- }
-
- private static double FPConvertNaN(ushort valueBits)
- {
- return BitConverter.Int64BitsToDouble(
- (long)(((ulong)valueBits & 0x8000ul) << 48 | 0x7FF8000000000000ul | ((ulong)valueBits & 0x01FFul) << 42));
- }
- }
-
- static class SoftFloat32_16
- {
- [UnmanagedCallersOnly]
- public static ushort FPConvert(float value)
- {
- ExecutionContext context = NativeInterface.GetContext();
-
- double real = value.FPUnpackCv(out FPType type, out bool sign, out uint valueBits, context);
-
- bool altHp = (context.Fpcr & FPCR.Ahp) != 0;
-
- ushort resultBits;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- if (altHp)
- {
- resultBits = SoftFloat16.FPZero(sign);
- }
- else if ((context.Fpcr & FPCR.Dn) != 0)
- {
- resultBits = SoftFloat16.FPDefaultNaN();
- }
- else
- {
- resultBits = FPConvertNaN(valueBits);
- }
-
- if (type == FPType.SNaN || altHp)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- }
- else if (type == FPType.Infinity)
- {
- if (altHp)
- {
- resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- else
- {
- resultBits = SoftFloat16.FPInfinity(sign);
- }
- }
- else if (type == FPType.Zero)
- {
- resultBits = SoftFloat16.FPZero(sign);
- }
- else
- {
- resultBits = SoftFloat16.FPRoundCv(real, context);
- }
-
- return resultBits;
- }
-
- private static double FPUnpackCv(
- this float value,
- out FPType type,
- out bool sign,
- out uint valueBits,
- ExecutionContext context)
- {
- valueBits = (uint)BitConverter.SingleToInt32Bits(value);
-
- sign = (~valueBits & 0x80000000u) == 0u;
-
- uint exp32 = (valueBits & 0x7F800000u) >> 23;
- uint frac32 = valueBits & 0x007FFFFFu;
-
- double real;
-
- if (exp32 == 0u)
- {
- if (frac32 == 0u || (context.Fpcr & FPCR.Fz) != 0)
- {
- type = FPType.Zero;
- real = 0d;
-
- if (frac32 != 0u)
- {
- SoftFloat.FPProcessException(FPException.InputDenorm, context);
- }
- }
- else
- {
- type = FPType.Nonzero; // Subnormal.
- real = Math.Pow(2d, -126) * ((double)frac32 * Math.Pow(2d, -23));
- }
- }
- else if (exp32 == 0xFFu)
- {
- if (frac32 == 0u)
- {
- type = FPType.Infinity;
- real = Math.Pow(2d, 1000);
- }
- else
- {
- type = (~frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN;
- real = 0d;
- }
- }
- else
- {
- type = FPType.Nonzero; // Normal.
- real = Math.Pow(2d, (int)exp32 - 127) * (1d + (double)frac32 * Math.Pow(2d, -23));
- }
-
- return sign ? -real : real;
- }
-
- private static ushort FPConvertNaN(uint valueBits)
- {
- return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13);
- }
- }
-
- static class SoftFloat32
- {
- [UnmanagedCallersOnly]
- public static float FPAdd(float value1, float value2)
- {
- return FPAddFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPAddFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPAddFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPAddFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == !sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = value1 + value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static int FPCompare(float value1, float value2, byte signalNaNs)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- int result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = 0b0011;
-
- if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs == 1)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- }
- else
- {
- if (value1 == value2)
- {
- result = 0b0110;
- }
- else if (value1 < value2)
- {
- result = 0b1000;
- }
- else
- {
- result = 0b0010;
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareEQ(float value1, float value2)
- {
- return FPCompareEQFpscrImpl(value1, value2, false);
- }
-
- private static float FPCompareEQFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- float result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- if (type1 == FPType.SNaN || type2 == FPType.SNaN)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- }
- else
- {
- result = ZerosOrOnes(value1 == value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareEQFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPCompareEQFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareGE(float value1, float value2)
- {
- return FPCompareGEFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareGEFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPCompareGEFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPCompareGEFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- float result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = ZerosOrOnes(value1 >= value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareGT(float value1, float value2)
- {
- return FPCompareGTFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareGTFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPCompareGTFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPCompareGTFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- float result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = ZerosOrOnes(value1 > value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareLE(float value1, float value2)
- {
- return FPCompareGEFpscrImpl(value2, value1, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareLT(float value1, float value2)
- {
- return FPCompareGTFpscrImpl(value2, value1, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareLEFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static float FPCompareLTFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static float FPDiv(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && inf2) || (zero1 && zero2))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (inf1 || zero2)
- {
- result = FPInfinity(sign1 ^ sign2);
-
- if (!inf1)
- {
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- }
- else if (zero1 || inf2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 / value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPMax(float value1, float value2)
- {
- return FPMaxFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMaxFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPMaxFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPMaxFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- if (value1 > value2)
- {
- if (type1 == FPType.Infinity)
- {
- result = FPInfinity(sign1);
- }
- else if (type1 == FPType.Zero)
- {
- result = FPZero(sign1 && sign2);
- }
- else
- {
- result = value1;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
- else
- {
- if (type2 == FPType.Infinity)
- {
- result = FPInfinity(sign2);
- }
- else if (type2 == FPType.Zero)
- {
- result = FPZero(sign1 && sign2);
- }
- else
- {
- result = value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPMaxNum(float value1, float value2)
- {
- return FPMaxNumFpscrImpl(value1, value2, false);
- }
-
- private static float FPMaxNumFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- if (type1 == FPType.QNaN && type2 != FPType.QNaN)
- {
- value1 = FPInfinity(true);
- }
- else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
- {
- value2 = FPInfinity(true);
- }
-
- return FPMaxFpscrImpl(value1, value2, standardFpscr);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMaxNumFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPMaxNumFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMin(float value1, float value2)
- {
- return FPMinFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMinFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPMinFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPMinFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- if (value1 < value2)
- {
- if (type1 == FPType.Infinity)
- {
- result = FPInfinity(sign1);
- }
- else if (type1 == FPType.Zero)
- {
- result = FPZero(sign1 || sign2);
- }
- else
- {
- result = value1;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
- else
- {
- if (type2 == FPType.Infinity)
- {
- result = FPInfinity(sign2);
- }
- else if (type2 == FPType.Zero)
- {
- result = FPZero(sign1 || sign2);
- }
- else
- {
- result = value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPMinNum(float value1, float value2)
- {
- return FPMinNumFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMinNumFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPMinNumFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPMinNumFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- if (type1 == FPType.QNaN && type2 != FPType.QNaN)
- {
- value1 = FPInfinity(false);
- }
- else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
- {
- value2 = FPInfinity(false);
- }
-
- return FPMinFpscrImpl(value1, value2, standardFpscr);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMul(float value1, float value2)
- {
- return FPMulFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulFpscr(float value1, float value2, byte standardFpscr)
- {
- return FPMulFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static float FPMulFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else if (zero1 || zero2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 * value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulAdd(float valueA, float value1, float value2)
- {
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulAddFpscr(float valueA, float value1, float value2, byte standardFpscr)
- {
- return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
- }
-
- private static float FPMulAddFpscrImpl(float valueA, float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context, fpcr);
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- float result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr);
-
- if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2)))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
-
- if (!done)
- {
- bool infA = typeA == FPType.Infinity;
- bool zeroA = typeA == FPType.Zero;
-
- bool signP = sign1 ^ sign2;
- bool infP = inf1 || inf2;
- bool zeroP = zero1 || zero2;
-
- if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((infA && !signA) || (infP && !signP))
- {
- result = FPInfinity(false);
- }
- else if ((infA && signA) || (infP && signP))
- {
- result = FPInfinity(true);
- }
- else if (zeroA && zeroP && signA == signP)
- {
- result = FPZero(signA);
- }
- else
- {
- result = MathF.FusedMultiplyAdd(value1, value2, valueA);
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulSub(float valueA, float value1, float value2)
- {
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulSubFpscr(float valueA, float value1, float value2, byte standardFpscr)
- {
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static float FPMulX(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPTwo(sign1 ^ sign2);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else if (zero1 || zero2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 * value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPNegMulAdd(float valueA, float value1, float value2)
- {
- valueA = valueA.FPNeg();
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPNegMulSub(float valueA, float value1, float value2)
- {
- valueA = valueA.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPRecipEstimate(float value)
- {
- return FPRecipEstimateFpscrImpl(value, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPRecipEstimateFpscr(float value, byte standardFpscr)
- {
- return FPRecipEstimateFpscrImpl(value, standardFpscr == 1);
- }
-
- private static float FPRecipEstimateFpscrImpl(float value, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
-
- float result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Infinity)
- {
- result = FPZero(sign);
- }
- else if (type == FPType.Zero)
- {
- result = FPInfinity(sign);
-
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- else if (MathF.Abs(value) < MathF.Pow(2f, -128))
- {
- bool overflowToInf = fpcr.GetRoundingMode() switch
- {
- FPRoundingMode.ToNearest => true,
- FPRoundingMode.TowardsPlusInfinity => !sign,
- FPRoundingMode.TowardsMinusInfinity => sign,
- FPRoundingMode.TowardsZero => false,
- _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."),
- };
- result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
-
- SoftFloat.FPProcessException(FPException.Overflow, context, fpcr);
- SoftFloat.FPProcessException(FPException.Inexact, context, fpcr);
- }
- else if ((fpcr & FPCR.Fz) != 0 && (MathF.Abs(value) >= MathF.Pow(2f, 126)))
- {
- result = FPZero(sign);
-
- context.Fpsr |= FPSR.Ufc;
- }
- else
- {
- ulong fraction = (ulong)(op & 0x007FFFFFu) << 29;
- uint exp = (op & 0x7F800000u) >> 23;
-
- if (exp == 0u)
- {
- if ((fraction & 0x0008000000000000ul) == 0ul)
- {
- fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2;
- exp -= 1u;
- }
- else
- {
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- }
- }
-
- uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
-
- uint resultExp = 253u - exp;
-
- uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u;
-
- fraction = (ulong)(estimate & 0xFFu) << 44;
-
- if (resultExp == 0u)
- {
- fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1;
- }
- else if (resultExp + 1u == 0u)
- {
- fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2;
- resultExp = 0u;
- }
-
- result = BitConverter.Int32BitsToSingle(
- (int)((sign ? 1u : 0u) << 31 | (resultExp & 0xFFu) << 23 | (uint)(fraction >> 29) & 0x007FFFFFu));
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRecipStep(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.StandardFpcrValue;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- float product;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- product = FPZero(false);
- }
- else
- {
- product = FPMulFpscrImpl(value1, value2, true);
- }
-
- result = FPSubFpscrImpl(FPTwo(false), product, true);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRecipStepFused(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPNeg();
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPTwo(false);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else
- {
- result = MathF.FusedMultiplyAdd(value1, value2, 2f);
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRecpX(float value)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
-
- float result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else
- {
- uint notExp = (~op >> 23) & 0xFFu;
- uint maxExp = 0xFEu;
-
- result = BitConverter.Int32BitsToSingle(
- (int)((sign ? 1u : 0u) << 31 | (notExp == 0xFFu ? maxExp : notExp) << 23));
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRSqrtEstimate(float value)
- {
- return FPRSqrtEstimateFpscrImpl(value, false);
- }
-
- [UnmanagedCallersOnly]
- public static float FPRSqrtEstimateFpscr(float value, byte standardFpscr)
- {
- return FPRSqrtEstimateFpscrImpl(value, standardFpscr == 1);
- }
-
- private static float FPRSqrtEstimateFpscrImpl(float value, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
-
- float result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Zero)
- {
- result = FPInfinity(sign);
-
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- else if (sign)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (type == FPType.Infinity)
- {
- result = FPZero(false);
- }
- else
- {
- ulong fraction = (ulong)(op & 0x007FFFFFu) << 29;
- uint exp = (op & 0x7F800000u) >> 23;
-
- if (exp == 0u)
- {
- while ((fraction & 0x0008000000000000ul) == 0ul)
- {
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- exp -= 1u;
- }
-
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- }
-
- uint scaled;
-
- if ((exp & 1u) == 0u)
- {
- scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
- }
- else
- {
- scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45);
- }
-
- uint resultExp = (380u - exp) >> 1;
-
- uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u;
-
- result = BitConverter.Int32BitsToSingle((int)((resultExp & 0xFFu) << 23 | (estimate & 0xFFu) << 15));
- }
-
- return result;
- }
-
- public static float FPHalvedSub(float value1, float value2, ExecutionContext context, FPCR fpcr)
- {
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == !sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = (value1 - value2) / 2.0f;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRSqrtStep(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.StandardFpcrValue;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- float product;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- product = FPZero(false);
- }
- else
- {
- product = FPMulFpscrImpl(value1, value2, true);
- }
-
- result = FPHalvedSub(FPThree(false), product, context, fpcr);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPRSqrtStepFused(float value1, float value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPNeg();
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPOnePointFive(false);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else
- {
- result = MathF.FusedMultiplyAdd(value1, value2, 3f) / 2f;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPSqrt(float value)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value = value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
-
- float result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Zero)
- {
- result = FPZero(sign);
- }
- else if (type == FPType.Infinity && !sign)
- {
- result = FPInfinity(sign);
- }
- else if (sign)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = MathF.Sqrt(value);
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static float FPSub(float value1, float value2)
- {
- return FPSubFpscrImpl(value1, value2, false);
- }
-
- private static float FPSubFpscrImpl(float value1, float value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
-
- float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == !sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = value1 - value2;
-
- if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0f);
- }
- }
- }
-
- return result;
- }
-
- public static float FPDefaultNaN()
- {
- return BitConverter.Int32BitsToSingle(0x7fc00000);
- }
-
- public static float FPInfinity(bool sign)
- {
- return sign ? float.NegativeInfinity : float.PositiveInfinity;
- }
-
- public static float FPZero(bool sign)
- {
- return sign ? -0f : +0f;
- }
-
- public static float FPMaxNormal(bool sign)
- {
- return sign ? float.MinValue : float.MaxValue;
- }
-
- private static float FPTwo(bool sign)
- {
- return sign ? -2f : +2f;
- }
-
- private static float FPThree(bool sign)
- {
- return sign ? -3f : +3f;
- }
-
- private static float FPOnePointFive(bool sign)
- {
- return sign ? -1.5f : +1.5f;
- }
-
- private static float FPNeg(this float value)
- {
- return -value;
- }
-
- private static float ZerosOrOnes(bool ones)
- {
- return BitConverter.Int32BitsToSingle(ones ? -1 : 0);
- }
-
- private static float FPUnpack(
- this float value,
- out FPType type,
- out bool sign,
- out uint valueBits,
- ExecutionContext context,
- FPCR fpcr)
- {
- valueBits = (uint)BitConverter.SingleToInt32Bits(value);
-
- sign = (~valueBits & 0x80000000u) == 0u;
-
- if ((valueBits & 0x7F800000u) == 0u)
- {
- if ((valueBits & 0x007FFFFFu) == 0u || (fpcr & FPCR.Fz) != 0)
- {
- type = FPType.Zero;
- value = FPZero(sign);
-
- if ((valueBits & 0x007FFFFFu) != 0u)
- {
- SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr);
- }
- }
- else
- {
- type = FPType.Nonzero;
- }
- }
- else if ((~valueBits & 0x7F800000u) == 0u)
- {
- if ((valueBits & 0x007FFFFFu) == 0u)
- {
- type = FPType.Infinity;
- }
- else
- {
- type = (~valueBits & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN;
- value = FPZero(sign);
- }
- }
- else
- {
- type = FPType.Nonzero;
- }
-
- return value;
- }
-
- private static float FPProcessNaNs(
- FPType type1,
- FPType type2,
- uint op1,
- uint op2,
- out bool done,
- ExecutionContext context,
- FPCR fpcr)
- {
- done = true;
-
- if (type1 == FPType.SNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.SNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type1 == FPType.QNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.QNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
-
- done = false;
-
- return FPZero(false);
- }
-
- private static float FPProcessNaNs3(
- FPType type1,
- FPType type2,
- FPType type3,
- uint op1,
- uint op2,
- uint op3,
- out bool done,
- ExecutionContext context,
- FPCR fpcr)
- {
- done = true;
-
- if (type1 == FPType.SNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.SNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type3 == FPType.SNaN)
- {
- return FPProcessNaN(type3, op3, context, fpcr);
- }
- else if (type1 == FPType.QNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.QNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type3 == FPType.QNaN)
- {
- return FPProcessNaN(type3, op3, context, fpcr);
- }
-
- done = false;
-
- return FPZero(false);
- }
-
- private static float FPProcessNaN(FPType type, uint op, ExecutionContext context, FPCR fpcr)
- {
- if (type == FPType.SNaN)
- {
- op |= 1u << 22;
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
-
- if ((fpcr & FPCR.Dn) != 0)
- {
- return FPDefaultNaN();
- }
-
- return BitConverter.Int32BitsToSingle((int)op);
- }
- }
-
- static class SoftFloat64_16
- {
- [UnmanagedCallersOnly]
- public static ushort FPConvert(double value)
- {
- ExecutionContext context = NativeInterface.GetContext();
-
- double real = value.FPUnpackCv(out FPType type, out bool sign, out ulong valueBits, context);
-
- bool altHp = (context.Fpcr & FPCR.Ahp) != 0;
-
- ushort resultBits;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- if (altHp)
- {
- resultBits = SoftFloat16.FPZero(sign);
- }
- else if ((context.Fpcr & FPCR.Dn) != 0)
- {
- resultBits = SoftFloat16.FPDefaultNaN();
- }
- else
- {
- resultBits = FPConvertNaN(valueBits);
- }
-
- if (type == FPType.SNaN || altHp)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- }
- else if (type == FPType.Infinity)
- {
- if (altHp)
- {
- resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context);
- }
- else
- {
- resultBits = SoftFloat16.FPInfinity(sign);
- }
- }
- else if (type == FPType.Zero)
- {
- resultBits = SoftFloat16.FPZero(sign);
- }
- else
- {
- resultBits = SoftFloat16.FPRoundCv(real, context);
- }
-
- return resultBits;
- }
-
- private static double FPUnpackCv(
- this double value,
- out FPType type,
- out bool sign,
- out ulong valueBits,
- ExecutionContext context)
- {
- valueBits = (ulong)BitConverter.DoubleToInt64Bits(value);
-
- sign = (~valueBits & 0x8000000000000000ul) == 0u;
-
- ulong exp64 = (valueBits & 0x7FF0000000000000ul) >> 52;
- ulong frac64 = valueBits & 0x000FFFFFFFFFFFFFul;
-
- double real;
-
- if (exp64 == 0u)
- {
- if (frac64 == 0u || (context.Fpcr & FPCR.Fz) != 0)
- {
- type = FPType.Zero;
- real = 0d;
-
- if (frac64 != 0u)
- {
- SoftFloat.FPProcessException(FPException.InputDenorm, context);
- }
- }
- else
- {
- type = FPType.Nonzero; // Subnormal.
- real = Math.Pow(2d, -1022) * ((double)frac64 * Math.Pow(2d, -52));
- }
- }
- else if (exp64 == 0x7FFul)
- {
- if (frac64 == 0u)
- {
- type = FPType.Infinity;
- real = Math.Pow(2d, 1000000);
- }
- else
- {
- type = (~frac64 & 0x0008000000000000ul) == 0u ? FPType.QNaN : FPType.SNaN;
- real = 0d;
- }
- }
- else
- {
- type = FPType.Nonzero; // Normal.
- real = Math.Pow(2d, (int)exp64 - 1023) * (1d + (double)frac64 * Math.Pow(2d, -52));
- }
-
- return sign ? -real : real;
- }
-
- private static ushort FPConvertNaN(ulong valueBits)
- {
- return (ushort)((valueBits & 0x8000000000000000ul) >> 48 | 0x7E00u | (valueBits & 0x0007FC0000000000ul) >> 42);
- }
- }
-
- static class SoftFloat64
- {
- [UnmanagedCallersOnly]
- public static double FPAdd(double value1, double value2)
- {
- return FPAddFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPAddFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPAddFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPAddFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == !sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = value1 + value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static int FPCompare(double value1, double value2, byte signalNaNs)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- int result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = 0b0011;
-
- if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs == 1)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- }
- else
- {
- if (value1 == value2)
- {
- result = 0b0110;
- }
- else if (value1 < value2)
- {
- result = 0b1000;
- }
- else
- {
- result = 0b0010;
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareEQ(double value1, double value2)
- {
- return FPCompareEQFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareEQFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPCompareEQFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPCompareEQFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- double result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- if (type1 == FPType.SNaN || type2 == FPType.SNaN)
- {
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- }
- else
- {
- result = ZerosOrOnes(value1 == value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareGE(double value1, double value2)
- {
- return FPCompareGEFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareGEFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPCompareGEFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPCompareGEFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- double result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = ZerosOrOnes(value1 >= value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareGT(double value1, double value2)
- {
- return FPCompareGTFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareGTFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPCompareGTFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPCompareGTFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- double result;
-
- if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
- {
- result = ZerosOrOnes(false);
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = ZerosOrOnes(value1 > value2);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareLE(double value1, double value2)
- {
- return FPCompareGEFpscrImpl(value2, value1, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareLT(double value1, double value2)
- {
- return FPCompareGTFpscrImpl(value2, value1, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareLEFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static double FPCompareLTFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPCompareGTFpscrImpl(value2, value1, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static double FPDiv(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && inf2) || (zero1 && zero2))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (inf1 || zero2)
- {
- result = FPInfinity(sign1 ^ sign2);
-
- if (!inf1)
- {
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- }
- else if (zero1 || inf2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 / value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPMax(double value1, double value2)
- {
- return FPMaxFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMaxFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPMaxFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPMaxFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- if (value1 > value2)
- {
- if (type1 == FPType.Infinity)
- {
- result = FPInfinity(sign1);
- }
- else if (type1 == FPType.Zero)
- {
- result = FPZero(sign1 && sign2);
- }
- else
- {
- result = value1;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
- else
- {
- if (type2 == FPType.Infinity)
- {
- result = FPInfinity(sign2);
- }
- else if (type2 == FPType.Zero)
- {
- result = FPZero(sign1 && sign2);
- }
- else
- {
- result = value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPMaxNum(double value1, double value2)
- {
- return FPMaxNumFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMaxNumFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPMaxNumFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPMaxNumFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- if (type1 == FPType.QNaN && type2 != FPType.QNaN)
- {
- value1 = FPInfinity(true);
- }
- else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
- {
- value2 = FPInfinity(true);
- }
-
- return FPMaxFpscrImpl(value1, value2, standardFpscr);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMin(double value1, double value2)
- {
- return FPMinFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMinFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPMinFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPMinFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- if (value1 < value2)
- {
- if (type1 == FPType.Infinity)
- {
- result = FPInfinity(sign1);
- }
- else if (type1 == FPType.Zero)
- {
- result = FPZero(sign1 || sign2);
- }
- else
- {
- result = value1;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
- else
- {
- if (type2 == FPType.Infinity)
- {
- result = FPInfinity(sign2);
- }
- else if (type2 == FPType.Zero)
- {
- result = FPZero(sign1 || sign2);
- }
- else
- {
- result = value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPMinNum(double value1, double value2)
- {
- return FPMinNumFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMinNumFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPMinNumFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPMinNumFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
- value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
-
- if (type1 == FPType.QNaN && type2 != FPType.QNaN)
- {
- value1 = FPInfinity(false);
- }
- else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
- {
- value2 = FPInfinity(false);
- }
-
- return FPMinFpscrImpl(value1, value2, standardFpscr);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMul(double value1, double value2)
- {
- return FPMulFpscrImpl(value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulFpscr(double value1, double value2, byte standardFpscr)
- {
- return FPMulFpscrImpl(value1, value2, standardFpscr == 1);
- }
-
- private static double FPMulFpscrImpl(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else if (zero1 || zero2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 * value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulAdd(double valueA, double value1, double value2)
- {
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulAddFpscr(double valueA, double value1, double value2, byte standardFpscr)
- {
- return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
- }
-
- private static double FPMulAddFpscrImpl(double valueA, double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context, fpcr);
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- double result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr);
-
- if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2)))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
-
- if (!done)
- {
- bool infA = typeA == FPType.Infinity;
- bool zeroA = typeA == FPType.Zero;
-
- bool signP = sign1 ^ sign2;
- bool infP = inf1 || inf2;
- bool zeroP = zero1 || zero2;
-
- if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP))
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((infA && !signA) || (infP && !signP))
- {
- result = FPInfinity(false);
- }
- else if ((infA && signA) || (infP && signP))
- {
- result = FPInfinity(true);
- }
- else if (zeroA && zeroP && signA == signP)
- {
- result = FPZero(signA);
- }
- else
- {
- result = Math.FusedMultiplyAdd(value1, value2, valueA);
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulSub(double valueA, double value1, double value2)
- {
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulSubFpscr(double valueA, double value1, double value2, byte standardFpscr)
- {
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
- }
-
- [UnmanagedCallersOnly]
- public static double FPMulX(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPTwo(sign1 ^ sign2);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else if (zero1 || zero2)
- {
- result = FPZero(sign1 ^ sign2);
- }
- else
- {
- result = value1 * value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPNegMulAdd(double valueA, double value1, double value2)
- {
- valueA = valueA.FPNeg();
- value1 = value1.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPNegMulSub(double valueA, double value1, double value2)
- {
- valueA = valueA.FPNeg();
-
- return FPMulAddFpscrImpl(valueA, value1, value2, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPRecipEstimate(double value)
- {
- return FPRecipEstimateFpscrImpl(value, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPRecipEstimateFpscr(double value, byte standardFpscr)
- {
- return FPRecipEstimateFpscrImpl(value, standardFpscr == 1);
- }
-
- private static double FPRecipEstimateFpscrImpl(double value, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
-
- double result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Infinity)
- {
- result = FPZero(sign);
- }
- else if (type == FPType.Zero)
- {
- result = FPInfinity(sign);
-
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- else if (Math.Abs(value) < Math.Pow(2d, -1024))
- {
- bool overflowToInf = fpcr.GetRoundingMode() switch
- {
- FPRoundingMode.ToNearest => true,
- FPRoundingMode.TowardsPlusInfinity => !sign,
- FPRoundingMode.TowardsMinusInfinity => sign,
- FPRoundingMode.TowardsZero => false,
- _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."),
- };
- result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
-
- SoftFloat.FPProcessException(FPException.Overflow, context, fpcr);
- SoftFloat.FPProcessException(FPException.Inexact, context, fpcr);
- }
- else if ((fpcr & FPCR.Fz) != 0 && (Math.Abs(value) >= Math.Pow(2d, 1022)))
- {
- result = FPZero(sign);
-
- context.Fpsr |= FPSR.Ufc;
- }
- else
- {
- ulong fraction = op & 0x000FFFFFFFFFFFFFul;
- uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52);
-
- if (exp == 0u)
- {
- if ((fraction & 0x0008000000000000ul) == 0ul)
- {
- fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2;
- exp -= 1u;
- }
- else
- {
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- }
- }
-
- uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
-
- uint resultExp = 2045u - exp;
-
- uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u;
-
- fraction = (ulong)(estimate & 0xFFu) << 44;
-
- if (resultExp == 0u)
- {
- fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1;
- }
- else if (resultExp + 1u == 0u)
- {
- fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2;
- resultExp = 0u;
- }
-
- result = BitConverter.Int64BitsToDouble(
- (long)((sign ? 1ul : 0ul) << 63 | (resultExp & 0x7FFul) << 52 | (fraction & 0x000FFFFFFFFFFFFFul)));
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRecipStep(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.StandardFpcrValue;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- double product;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- product = FPZero(false);
- }
- else
- {
- product = FPMulFpscrImpl(value1, value2, true);
- }
-
- result = FPSubFpscr(FPTwo(false), product, true);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRecipStepFused(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPNeg();
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPTwo(false);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else
- {
- result = Math.FusedMultiplyAdd(value1, value2, 2d);
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRecpX(double value)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
-
- double result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else
- {
- ulong notExp = (~op >> 52) & 0x7FFul;
- ulong maxExp = 0x7FEul;
-
- result = BitConverter.Int64BitsToDouble(
- (long)((sign ? 1ul : 0ul) << 63 | (notExp == 0x7FFul ? maxExp : notExp) << 52));
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRSqrtEstimate(double value)
- {
- return FPRSqrtEstimateFpscrImpl(value, false);
- }
-
- [UnmanagedCallersOnly]
- public static double FPRSqrtEstimateFpscr(double value, byte standardFpscr)
- {
- return FPRSqrtEstimateFpscrImpl(value, standardFpscr == 1);
- }
-
- private static double FPRSqrtEstimateFpscrImpl(double value, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
-
- double result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Zero)
- {
- result = FPInfinity(sign);
-
- SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
- }
- else if (sign)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if (type == FPType.Infinity)
- {
- result = FPZero(false);
- }
- else
- {
- ulong fraction = op & 0x000FFFFFFFFFFFFFul;
- uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52);
-
- if (exp == 0u)
- {
- while ((fraction & 0x0008000000000000ul) == 0ul)
- {
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- exp -= 1u;
- }
-
- fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
- }
-
- uint scaled;
-
- if ((exp & 1u) == 0u)
- {
- scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
- }
- else
- {
- scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45);
- }
-
- uint resultExp = (3068u - exp) >> 1;
-
- uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u;
-
- result = BitConverter.Int64BitsToDouble((long)((resultExp & 0x7FFul) << 52 | (estimate & 0xFFul) << 44));
- }
-
- return result;
- }
-
- public static double FPHalvedSub(double value1, double value2, ExecutionContext context, FPCR fpcr)
- {
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == !sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = (value1 - value2) / 2.0;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRSqrtStep(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.StandardFpcrValue;
-
- value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- double product;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- product = FPZero(false);
- }
- else
- {
- product = FPMulFpscrImpl(value1, value2, true);
- }
-
- result = FPHalvedSub(FPThree(false), product, context, fpcr);
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPRSqrtStepFused(double value1, double value2)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value1 = value1.FPNeg();
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if ((inf1 && zero2) || (zero1 && inf2))
- {
- result = FPOnePointFive(false);
- }
- else if (inf1 || inf2)
- {
- result = FPInfinity(sign1 ^ sign2);
- }
- else
- {
- result = Math.FusedMultiplyAdd(value1, value2, 3d) / 2d;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPSqrt(double value)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = context.Fpcr;
-
- value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
-
- double result;
-
- if (type is FPType.SNaN or FPType.QNaN)
- {
- result = FPProcessNaN(type, op, context, fpcr);
- }
- else if (type == FPType.Zero)
- {
- result = FPZero(sign);
- }
- else if (type == FPType.Infinity && !sign)
- {
- result = FPInfinity(sign);
- }
- else if (sign)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else
- {
- result = Math.Sqrt(value);
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
-
- return result;
- }
-
- [UnmanagedCallersOnly]
- public static double FPSub(double value1, double value2)
- {
- return FPSubFpscr(value1, value2, false);
- }
-
- public static double FPSubFpscr(double value1, double value2, bool standardFpscr)
- {
- ExecutionContext context = NativeInterface.GetContext();
- FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
-
- value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
- value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
-
- double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
-
- if (!done)
- {
- bool inf1 = type1 == FPType.Infinity;
- bool zero1 = type1 == FPType.Zero;
- bool inf2 = type2 == FPType.Infinity;
- bool zero2 = type2 == FPType.Zero;
-
- if (inf1 && inf2 && sign1 == sign2)
- {
- result = FPDefaultNaN();
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
- else if ((inf1 && !sign1) || (inf2 && sign2))
- {
- result = FPInfinity(false);
- }
- else if ((inf1 && sign1) || (inf2 && !sign2))
- {
- result = FPInfinity(true);
- }
- else if (zero1 && zero2 && sign1 == !sign2)
- {
- result = FPZero(sign1);
- }
- else
- {
- result = value1 - value2;
-
- if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
- {
- context.Fpsr |= FPSR.Ufc;
-
- result = FPZero(result < 0d);
- }
- }
- }
-
- return result;
- }
-
- public static double FPDefaultNaN()
- {
- return BitConverter.Int64BitsToDouble(0x7ff8000000000000);
- }
-
- public static double FPInfinity(bool sign)
- {
- return sign ? double.NegativeInfinity : double.PositiveInfinity;
- }
-
- public static double FPZero(bool sign)
- {
- return sign ? -0d : +0d;
- }
-
- public static double FPMaxNormal(bool sign)
- {
- return sign ? double.MinValue : double.MaxValue;
- }
-
- private static double FPTwo(bool sign)
- {
- return sign ? -2d : +2d;
- }
-
- private static double FPThree(bool sign)
- {
- return sign ? -3d : +3d;
- }
-
- private static double FPOnePointFive(bool sign)
- {
- return sign ? -1.5d : +1.5d;
- }
-
- private static double FPNeg(this double value)
- {
- return -value;
- }
-
- private static double ZerosOrOnes(bool ones)
- {
- return BitConverter.Int64BitsToDouble(ones ? -1L : 0L);
- }
-
- private static double FPUnpack(
- this double value,
- out FPType type,
- out bool sign,
- out ulong valueBits,
- ExecutionContext context,
- FPCR fpcr)
- {
- valueBits = (ulong)BitConverter.DoubleToInt64Bits(value);
-
- sign = (~valueBits & 0x8000000000000000ul) == 0ul;
-
- if ((valueBits & 0x7FF0000000000000ul) == 0ul)
- {
- if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (fpcr & FPCR.Fz) != 0)
- {
- type = FPType.Zero;
- value = FPZero(sign);
-
- if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul)
- {
- SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr);
- }
- }
- else
- {
- type = FPType.Nonzero;
- }
- }
- else if ((~valueBits & 0x7FF0000000000000ul) == 0ul)
- {
- if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul)
- {
- type = FPType.Infinity;
- }
- else
- {
- type = (~valueBits & 0x0008000000000000ul) == 0ul ? FPType.QNaN : FPType.SNaN;
- value = FPZero(sign);
- }
- }
- else
- {
- type = FPType.Nonzero;
- }
-
- return value;
- }
-
- private static double FPProcessNaNs(
- FPType type1,
- FPType type2,
- ulong op1,
- ulong op2,
- out bool done,
- ExecutionContext context,
- FPCR fpcr)
- {
- done = true;
-
- if (type1 == FPType.SNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.SNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type1 == FPType.QNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.QNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
-
- done = false;
-
- return FPZero(false);
- }
-
- private static double FPProcessNaNs3(
- FPType type1,
- FPType type2,
- FPType type3,
- ulong op1,
- ulong op2,
- ulong op3,
- out bool done,
- ExecutionContext context,
- FPCR fpcr)
- {
- done = true;
-
- if (type1 == FPType.SNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.SNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type3 == FPType.SNaN)
- {
- return FPProcessNaN(type3, op3, context, fpcr);
- }
- else if (type1 == FPType.QNaN)
- {
- return FPProcessNaN(type1, op1, context, fpcr);
- }
- else if (type2 == FPType.QNaN)
- {
- return FPProcessNaN(type2, op2, context, fpcr);
- }
- else if (type3 == FPType.QNaN)
- {
- return FPProcessNaN(type3, op3, context, fpcr);
- }
-
- done = false;
-
- return FPZero(false);
- }
-
- private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context, FPCR fpcr)
- {
- if (type == FPType.SNaN)
- {
- op |= 1ul << 51;
-
- SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
- }
-
- if ((fpcr & FPCR.Dn) != 0)
- {
- return FPDefaultNaN();
- }
-
- return BitConverter.Int64BitsToDouble((long)op);
- }
- }
-}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat.cs
new file mode 100644
index 000000000..366dd543e
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat.cs
@@ -0,0 +1,111 @@
+using ARMeilleure.State;
+using System;
+using System.Diagnostics;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat
+ {
+ static SoftFloat()
+ {
+ RecipEstimateTable = BuildRecipEstimateTable();
+ RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable();
+ }
+
+ public static readonly byte[] RecipEstimateTable;
+ public static readonly byte[] RecipSqrtEstimateTable;
+
+ private static byte[] BuildRecipEstimateTable()
+ {
+ byte[] tbl = new byte[256];
+
+ for (int idx = 0; idx < 256; idx++)
+ {
+ uint src = (uint)idx + 256u;
+
+ Debug.Assert(src is >= 256u and < 512u);
+
+ src = (src << 1) + 1u;
+
+ uint aux = (1u << 19) / src;
+
+ uint dst = (aux + 1u) >> 1;
+
+ Debug.Assert(dst is >= 256u and < 512u);
+
+ tbl[idx] = (byte)(dst - 256u);
+ }
+
+ return tbl;
+ }
+
+ private static byte[] BuildRecipSqrtEstimateTable()
+ {
+ byte[] tbl = new byte[384];
+
+ for (int idx = 0; idx < 384; idx++)
+ {
+ uint src = (uint)idx + 128u;
+
+ Debug.Assert(src is >= 128u and < 512u);
+
+ if (src < 256u)
+ {
+ src = (src << 1) + 1u;
+ }
+ else
+ {
+ src = (src >> 1) << 1;
+ src = (src + 1u) << 1;
+ }
+
+ uint aux = 512u;
+
+ while (src * (aux + 1u) * (aux + 1u) < (1u << 28))
+ {
+ aux++;
+ }
+
+ uint dst = (aux + 1u) >> 1;
+
+ Debug.Assert(dst is >= 256u and < 512u);
+
+ tbl[idx] = (byte)(dst - 256u);
+ }
+
+ return tbl;
+ }
+
+ public static void FPProcessException(FPException exc, ExecutionContext context)
+ {
+ FPProcessException(exc, context, context.Fpcr);
+ }
+
+ public static void FPProcessException(FPException exc, ExecutionContext context, FPCR fpcr)
+ {
+ int enable = (int)exc + 8;
+
+ if ((fpcr & (FPCR)(1 << enable)) != 0)
+ {
+ throw new NotImplementedException("Floating-point trap handling.");
+ }
+ else
+ {
+ context.Fpsr |= (FPSR)(1 << (int)exc);
+ }
+ }
+
+ extension(FPCR fpcr)
+ {
+ public FPRoundingMode RoundingMode
+ {
+ get
+ {
+ const int RModeShift = 22;
+
+ return (FPRoundingMode)(((uint)fpcr >> RModeShift) & 3u);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16.cs
new file mode 100644
index 000000000..4038aceb6
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16.cs
@@ -0,0 +1,212 @@
+using ARMeilleure.State;
+using System;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat16
+ {
+ public static ushort FPDefaultNaN()
+ {
+ return (ushort)0x7E00u;
+ }
+
+ public static ushort FPInfinity(bool sign)
+ {
+ return sign ? (ushort)0xFC00u : (ushort)0x7C00u;
+ }
+
+ public static ushort FPZero(bool sign)
+ {
+ return sign ? (ushort)0x8000u : (ushort)0x0000u;
+ }
+
+ public static ushort FPMaxNormal(bool sign)
+ {
+ return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu;
+ }
+
+ public static double FPUnpackCv(
+ this ushort valueBits,
+ out FPType type,
+ out bool sign,
+ ExecutionContext context)
+ {
+ sign = (~(uint)valueBits & 0x8000u) == 0u;
+
+ uint exp16 = ((uint)valueBits & 0x7C00u) >> 10;
+ uint frac16 = (uint)valueBits & 0x03FFu;
+
+ double real;
+
+ if (exp16 == 0u)
+ {
+ if (frac16 == 0u)
+ {
+ type = FPType.Zero;
+ real = 0d;
+ }
+ else
+ {
+ type = FPType.Nonzero; // Subnormal.
+ real = Math.Pow(2d, -14) * ((double)frac16 * Math.Pow(2d, -10));
+ }
+ }
+ else if (exp16 == 0x1Fu && (context.Fpcr & FPCR.Ahp) == 0)
+ {
+ if (frac16 == 0u)
+ {
+ type = FPType.Infinity;
+ real = Math.Pow(2d, 1000);
+ }
+ else
+ {
+ type = (~frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN;
+ real = 0d;
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero; // Normal.
+ real = Math.Pow(2d, (int)exp16 - 15) * (1d + (double)frac16 * Math.Pow(2d, -10));
+ }
+
+ return sign ? -real : real;
+ }
+
+ public static ushort FPRoundCv(double real, ExecutionContext context)
+ {
+ const int MinimumExp = -14;
+
+ const int E = 5;
+ const int F = 10;
+
+ bool sign;
+ double mantissa;
+
+ if (real < 0d)
+ {
+ sign = true;
+ mantissa = -real;
+ }
+ else
+ {
+ sign = false;
+ mantissa = real;
+ }
+
+ int exponent = 0;
+
+ while (mantissa < 1d)
+ {
+ mantissa *= 2d;
+ exponent--;
+ }
+
+ while (mantissa >= 2d)
+ {
+ mantissa /= 2d;
+ exponent++;
+ }
+
+ uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
+
+ if (biasedExp == 0u)
+ {
+ mantissa /= Math.Pow(2d, MinimumExp - exponent);
+ }
+
+ uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F));
+ double error = mantissa * Math.Pow(2d, F) - (double)intMant;
+
+ if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
+ {
+ SoftFloat.FPProcessException(FPException.Underflow, context);
+ }
+
+ bool overflowToInf;
+ bool roundUp;
+
+ switch (context.Fpcr.RoundingMode)
+ {
+ case FPRoundingMode.ToNearest:
+ roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
+ overflowToInf = true;
+ break;
+
+ case FPRoundingMode.TowardsPlusInfinity:
+ roundUp = (error != 0d && !sign);
+ overflowToInf = !sign;
+ break;
+
+ case FPRoundingMode.TowardsMinusInfinity:
+ roundUp = (error != 0d && sign);
+ overflowToInf = sign;
+ break;
+
+ case FPRoundingMode.TowardsZero:
+ roundUp = false;
+ overflowToInf = false;
+ break;
+
+ default:
+ throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.RoundingMode}\".");
+ }
+
+ if (roundUp)
+ {
+ intMant++;
+
+ if (intMant == 1u << F)
+ {
+ biasedExp = 1u;
+ }
+
+ if (intMant == 1u << (F + 1))
+ {
+ biasedExp++;
+ intMant >>= 1;
+ }
+ }
+
+ ushort resultBits;
+
+ if ((context.Fpcr & FPCR.Ahp) == 0)
+ {
+ if (biasedExp >= (1u << E) - 1u)
+ {
+ resultBits = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
+
+ SoftFloat.FPProcessException(FPException.Overflow, context);
+
+ error = 1d;
+ }
+ else
+ {
+ resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu));
+ }
+ }
+ else
+ {
+ if (biasedExp >= 1u << E)
+ {
+ resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+
+ error = 0d;
+ }
+ else
+ {
+ resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu));
+ }
+ }
+
+ if (error != 0d)
+ {
+ SoftFloat.FPProcessException(FPException.Inexact, context);
+ }
+
+ return resultBits;
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_32.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_32.cs
new file mode 100644
index 000000000..f2d0f1eb4
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_32.cs
@@ -0,0 +1,182 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat16_32
+ {
+ [UnmanagedCallersOnly]
+ public static float FPConvert(ushort valueBits)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+
+ double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context);
+
+ float result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ if ((context.Fpcr & FPCR.Dn) != 0)
+ {
+ result = SoftFloat32.FPDefaultNaN();
+ }
+ else
+ {
+ result = FPConvertNaN(valueBits);
+ }
+
+ if (type == FPType.SNaN)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = SoftFloat32.FPInfinity(sign);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = SoftFloat32.FPZero(sign);
+ }
+ else
+ {
+ result = FPRoundCv(real, context);
+ }
+
+ return result;
+ }
+
+ private static float FPRoundCv(double real, ExecutionContext context)
+ {
+ const int MinimumExp = -126;
+
+ const int E = 8;
+ const int F = 23;
+
+ bool sign;
+ double mantissa;
+
+ if (real < 0d)
+ {
+ sign = true;
+ mantissa = -real;
+ }
+ else
+ {
+ sign = false;
+ mantissa = real;
+ }
+
+ int exponent = 0;
+
+ while (mantissa < 1d)
+ {
+ mantissa *= 2d;
+ exponent--;
+ }
+
+ while (mantissa >= 2d)
+ {
+ mantissa /= 2d;
+ exponent++;
+ }
+
+ if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp)
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ return SoftFloat32.FPZero(sign);
+ }
+
+ uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
+
+ if (biasedExp == 0u)
+ {
+ mantissa /= Math.Pow(2d, MinimumExp - exponent);
+ }
+
+ uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F));
+ double error = mantissa * Math.Pow(2d, F) - (double)intMant;
+
+ if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
+ {
+ SoftFloat.FPProcessException(FPException.Underflow, context);
+ }
+
+ bool overflowToInf;
+ bool roundUp;
+
+ switch (context.Fpcr.RoundingMode)
+ {
+ case FPRoundingMode.ToNearest:
+ roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
+ overflowToInf = true;
+ break;
+
+ case FPRoundingMode.TowardsPlusInfinity:
+ roundUp = (error != 0d && !sign);
+ overflowToInf = !sign;
+ break;
+
+ case FPRoundingMode.TowardsMinusInfinity:
+ roundUp = (error != 0d && sign);
+ overflowToInf = sign;
+ break;
+
+ case FPRoundingMode.TowardsZero:
+ roundUp = false;
+ overflowToInf = false;
+ break;
+
+ default:
+ throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.RoundingMode}\".");
+ }
+
+ if (roundUp)
+ {
+ intMant++;
+
+ if (intMant == 1u << F)
+ {
+ biasedExp = 1u;
+ }
+
+ if (intMant == 1u << (F + 1))
+ {
+ biasedExp++;
+ intMant >>= 1;
+ }
+ }
+
+ float result;
+
+ if (biasedExp >= (1u << E) - 1u)
+ {
+ result = overflowToInf ? SoftFloat32.FPInfinity(sign) : SoftFloat32.FPMaxNormal(sign);
+
+ SoftFloat.FPProcessException(FPException.Overflow, context);
+
+ error = 1d;
+ }
+ else
+ {
+ result = BitConverter.Int32BitsToSingle(
+ (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu)));
+ }
+
+ if (error != 0d)
+ {
+ SoftFloat.FPProcessException(FPException.Inexact, context);
+ }
+
+ return result;
+ }
+
+ private static float FPConvertNaN(ushort valueBits)
+ {
+ return BitConverter.Int32BitsToSingle(
+ (int)(((uint)valueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)valueBits & 0x01FFu) << 13));
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_64.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_64.cs
new file mode 100644
index 000000000..5167c1908
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat16_64.cs
@@ -0,0 +1,182 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat16_64
+ {
+ [UnmanagedCallersOnly]
+ public static double FPConvert(ushort valueBits)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+
+ double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context);
+
+ double result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ if ((context.Fpcr & FPCR.Dn) != 0)
+ {
+ result = SoftFloat64.FPDefaultNaN();
+ }
+ else
+ {
+ result = FPConvertNaN(valueBits);
+ }
+
+ if (type == FPType.SNaN)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = SoftFloat64.FPInfinity(sign);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = SoftFloat64.FPZero(sign);
+ }
+ else
+ {
+ result = FPRoundCv(real, context);
+ }
+
+ return result;
+ }
+
+ private static double FPRoundCv(double real, ExecutionContext context)
+ {
+ const int MinimumExp = -1022;
+
+ const int E = 11;
+ const int F = 52;
+
+ bool sign;
+ double mantissa;
+
+ if (real < 0d)
+ {
+ sign = true;
+ mantissa = -real;
+ }
+ else
+ {
+ sign = false;
+ mantissa = real;
+ }
+
+ int exponent = 0;
+
+ while (mantissa < 1d)
+ {
+ mantissa *= 2d;
+ exponent--;
+ }
+
+ while (mantissa >= 2d)
+ {
+ mantissa /= 2d;
+ exponent++;
+ }
+
+ if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp)
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ return SoftFloat64.FPZero(sign);
+ }
+
+ uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0);
+
+ if (biasedExp == 0u)
+ {
+ mantissa /= Math.Pow(2d, MinimumExp - exponent);
+ }
+
+ ulong intMant = (ulong)Math.Floor(mantissa * Math.Pow(2d, F));
+ double error = mantissa * Math.Pow(2d, F) - (double)intMant;
+
+ if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0))
+ {
+ SoftFloat.FPProcessException(FPException.Underflow, context);
+ }
+
+ bool overflowToInf;
+ bool roundUp;
+
+ switch (context.Fpcr.RoundingMode)
+ {
+ case FPRoundingMode.ToNearest:
+ roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u));
+ overflowToInf = true;
+ break;
+
+ case FPRoundingMode.TowardsPlusInfinity:
+ roundUp = (error != 0d && !sign);
+ overflowToInf = !sign;
+ break;
+
+ case FPRoundingMode.TowardsMinusInfinity:
+ roundUp = (error != 0d && sign);
+ overflowToInf = sign;
+ break;
+
+ case FPRoundingMode.TowardsZero:
+ roundUp = false;
+ overflowToInf = false;
+ break;
+
+ default:
+ throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.RoundingMode}\".");
+ }
+
+ if (roundUp)
+ {
+ intMant++;
+
+ if (intMant == 1ul << F)
+ {
+ biasedExp = 1u;
+ }
+
+ if (intMant == 1ul << (F + 1))
+ {
+ biasedExp++;
+ intMant >>= 1;
+ }
+ }
+
+ double result;
+
+ if (biasedExp >= (1u << E) - 1u)
+ {
+ result = overflowToInf ? SoftFloat64.FPInfinity(sign) : SoftFloat64.FPMaxNormal(sign);
+
+ SoftFloat.FPProcessException(FPException.Overflow, context);
+
+ error = 1d;
+ }
+ else
+ {
+ result = BitConverter.Int64BitsToDouble(
+ (long)((sign ? 1ul : 0ul) << 63 | (biasedExp & 0x7FFul) << 52 | (intMant & 0x000FFFFFFFFFFFFFul)));
+ }
+
+ if (error != 0d)
+ {
+ SoftFloat.FPProcessException(FPException.Inexact, context);
+ }
+
+ return result;
+ }
+
+ private static double FPConvertNaN(ushort valueBits)
+ {
+ return BitConverter.Int64BitsToDouble(
+ (long)(((ulong)valueBits & 0x8000ul) << 48 | 0x7FF8000000000000ul | ((ulong)valueBits & 0x01FFul) << 42));
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32.cs
new file mode 100644
index 000000000..a7ab054be
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32.cs
@@ -0,0 +1,1421 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+static class SoftFloat32
+ {
+ [UnmanagedCallersOnly]
+ public static float FPAdd(float value1, float value2)
+ {
+ return FPAddFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPAddFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPAddFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPAddFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == !sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = value1 + value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static int FPCompare(float value1, float value2, byte signalNaNs)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ int result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = 0b0011;
+
+ if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs == 1)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ }
+ else
+ {
+ if (value1 == value2)
+ {
+ result = 0b0110;
+ }
+ else if (value1 < value2)
+ {
+ result = 0b1000;
+ }
+ else
+ {
+ result = 0b0010;
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareEQ(float value1, float value2)
+ {
+ return FPCompareEQFpscrImpl(value1, value2, false);
+ }
+
+ private static float FPCompareEQFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ float result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ if (type1 == FPType.SNaN || type2 == FPType.SNaN)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 == value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareEQFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPCompareEQFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareGE(float value1, float value2)
+ {
+ return FPCompareGEFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareGEFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPCompareGEFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPCompareGEFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ float result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 >= value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareGT(float value1, float value2)
+ {
+ return FPCompareGTFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareGTFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPCompareGTFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPCompareGTFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ float result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 > value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareLE(float value1, float value2)
+ {
+ return FPCompareGEFpscrImpl(value2, value1, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareLT(float value1, float value2)
+ {
+ return FPCompareGTFpscrImpl(value2, value1, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareLEFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPCompareLTFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPDiv(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && inf2) || (zero1 && zero2))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (inf1 || zero2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+
+ if (!inf1)
+ {
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ }
+ else if (zero1 || inf2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 / value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMax(float value1, float value2)
+ {
+ return FPMaxFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMaxFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPMaxFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPMaxFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ if (value1 > value2)
+ {
+ if (type1 == FPType.Infinity)
+ {
+ result = FPInfinity(sign1);
+ }
+ else if (type1 == FPType.Zero)
+ {
+ result = FPZero(sign1 && sign2);
+ }
+ else
+ {
+ result = value1;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+ else
+ {
+ if (type2 == FPType.Infinity)
+ {
+ result = FPInfinity(sign2);
+ }
+ else if (type2 == FPType.Zero)
+ {
+ result = FPZero(sign1 && sign2);
+ }
+ else
+ {
+ result = value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMaxNum(float value1, float value2)
+ {
+ return FPMaxNumFpscrImpl(value1, value2, false);
+ }
+
+ private static float FPMaxNumFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ if (type1 == FPType.QNaN && type2 != FPType.QNaN)
+ {
+ value1 = FPInfinity(true);
+ }
+ else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
+ {
+ value2 = FPInfinity(true);
+ }
+
+ return FPMaxFpscrImpl(value1, value2, standardFpscr);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMaxNumFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPMaxNumFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMin(float value1, float value2)
+ {
+ return FPMinFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMinFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPMinFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPMinFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ if (value1 < value2)
+ {
+ if (type1 == FPType.Infinity)
+ {
+ result = FPInfinity(sign1);
+ }
+ else if (type1 == FPType.Zero)
+ {
+ result = FPZero(sign1 || sign2);
+ }
+ else
+ {
+ result = value1;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+ else
+ {
+ if (type2 == FPType.Infinity)
+ {
+ result = FPInfinity(sign2);
+ }
+ else if (type2 == FPType.Zero)
+ {
+ result = FPZero(sign1 || sign2);
+ }
+ else
+ {
+ result = value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMinNum(float value1, float value2)
+ {
+ return FPMinNumFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMinNumFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPMinNumFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPMinNumFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ if (type1 == FPType.QNaN && type2 != FPType.QNaN)
+ {
+ value1 = FPInfinity(false);
+ }
+ else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
+ {
+ value2 = FPInfinity(false);
+ }
+
+ return FPMinFpscrImpl(value1, value2, standardFpscr);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMul(float value1, float value2)
+ {
+ return FPMulFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulFpscr(float value1, float value2, byte standardFpscr)
+ {
+ return FPMulFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPMulFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else if (zero1 || zero2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 * value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulAdd(float valueA, float value1, float value2)
+ {
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulAddFpscr(float valueA, float value1, float value2, byte standardFpscr)
+ {
+ return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
+ }
+
+ private static float FPMulAddFpscrImpl(float valueA, float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context, fpcr);
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ float result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr);
+
+ if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2)))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+
+ if (!done)
+ {
+ bool infA = typeA == FPType.Infinity;
+ bool zeroA = typeA == FPType.Zero;
+
+ bool signP = sign1 ^ sign2;
+ bool infP = inf1 || inf2;
+ bool zeroP = zero1 || zero2;
+
+ if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((infA && !signA) || (infP && !signP))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((infA && signA) || (infP && signP))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zeroA && zeroP && signA == signP)
+ {
+ result = FPZero(signA);
+ }
+ else
+ {
+ result = MathF.FusedMultiplyAdd(value1, value2, valueA);
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulSub(float valueA, float value1, float value2)
+ {
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulSubFpscr(float valueA, float value1, float value2, byte standardFpscr)
+ {
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPMulX(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPTwo(sign1 ^ sign2);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else if (zero1 || zero2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 * value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPNegMulAdd(float valueA, float value1, float value2)
+ {
+ valueA = valueA.FPNeg();
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPNegMulSub(float valueA, float value1, float value2)
+ {
+ valueA = valueA.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRecipEstimate(float value)
+ {
+ return FPRecipEstimateFpscrImpl(value, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRecipEstimateFpscr(float value, byte standardFpscr)
+ {
+ return FPRecipEstimateFpscrImpl(value, standardFpscr == 1);
+ }
+
+ private static float FPRecipEstimateFpscrImpl(float value, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
+
+ float result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = FPZero(sign);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPInfinity(sign);
+
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ else if (MathF.Abs(value) < MathF.Pow(2f, -128))
+ {
+ bool overflowToInf = fpcr.RoundingMode switch
+ {
+ FPRoundingMode.ToNearest => true,
+ FPRoundingMode.TowardsPlusInfinity => !sign,
+ FPRoundingMode.TowardsMinusInfinity => sign,
+ FPRoundingMode.TowardsZero => false,
+ _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.RoundingMode}\"."),
+ };
+ result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
+
+ SoftFloat.FPProcessException(FPException.Overflow, context, fpcr);
+ SoftFloat.FPProcessException(FPException.Inexact, context, fpcr);
+ }
+ else if ((fpcr & FPCR.Fz) != 0 && (MathF.Abs(value) >= MathF.Pow(2f, 126)))
+ {
+ result = FPZero(sign);
+
+ context.Fpsr |= FPSR.Ufc;
+ }
+ else
+ {
+ ulong fraction = (ulong)(op & 0x007FFFFFu) << 29;
+ uint exp = (op & 0x7F800000u) >> 23;
+
+ if (exp == 0u)
+ {
+ if ((fraction & 0x0008000000000000ul) == 0ul)
+ {
+ fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2;
+ exp -= 1u;
+ }
+ else
+ {
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ }
+ }
+
+ uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
+
+ uint resultExp = 253u - exp;
+
+ uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u;
+
+ fraction = (ulong)(estimate & 0xFFu) << 44;
+
+ if (resultExp == 0u)
+ {
+ fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1;
+ }
+ else if (resultExp + 1u == 0u)
+ {
+ fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2;
+ resultExp = 0u;
+ }
+
+ result = BitConverter.Int32BitsToSingle(
+ (int)((sign ? 1u : 0u) << 31 | (resultExp & 0xFFu) << 23 | (uint)(fraction >> 29) & 0x007FFFFFu));
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRecipStep(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.StandardFpcrValue;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ float product;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ product = FPZero(false);
+ }
+ else
+ {
+ product = FPMulFpscrImpl(value1, value2, true);
+ }
+
+ result = FPSubFpscrImpl(FPTwo(false), product, true);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRecipStepFused(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPNeg();
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPTwo(false);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else
+ {
+ result = MathF.FusedMultiplyAdd(value1, value2, 2f);
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRecpX(float value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
+
+ float result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else
+ {
+ uint notExp = (~op >> 23) & 0xFFu;
+ uint maxExp = 0xFEu;
+
+ result = BitConverter.Int32BitsToSingle(
+ (int)((sign ? 1u : 0u) << 31 | (notExp == 0xFFu ? maxExp : notExp) << 23));
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRSqrtEstimate(float value)
+ {
+ return FPRSqrtEstimateFpscrImpl(value, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRSqrtEstimateFpscr(float value, byte standardFpscr)
+ {
+ return FPRSqrtEstimateFpscrImpl(value, standardFpscr == 1);
+ }
+
+ private static float FPRSqrtEstimateFpscrImpl(float value, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
+
+ float result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPInfinity(sign);
+
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ else if (sign)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = FPZero(false);
+ }
+ else
+ {
+ ulong fraction = (ulong)(op & 0x007FFFFFu) << 29;
+ uint exp = (op & 0x7F800000u) >> 23;
+
+ if (exp == 0u)
+ {
+ while ((fraction & 0x0008000000000000ul) == 0ul)
+ {
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ exp -= 1u;
+ }
+
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ }
+
+ uint scaled;
+
+ if ((exp & 1u) == 0u)
+ {
+ scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
+ }
+ else
+ {
+ scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45);
+ }
+
+ uint resultExp = (380u - exp) >> 1;
+
+ uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u;
+
+ result = BitConverter.Int32BitsToSingle((int)((resultExp & 0xFFu) << 23 | (estimate & 0xFFu) << 15));
+ }
+
+ return result;
+ }
+
+ public static float FPHalvedSub(float value1, float value2, ExecutionContext context, FPCR fpcr)
+ {
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == !sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = (value1 - value2) / 2.0f;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRSqrtStep(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.StandardFpcrValue;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ float product;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ product = FPZero(false);
+ }
+ else
+ {
+ product = FPMulFpscrImpl(value1, value2, true);
+ }
+
+ result = FPHalvedSub(FPThree(false), product, context, fpcr);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPRSqrtStepFused(float value1, float value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPNeg();
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPOnePointFive(false);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else
+ {
+ result = MathF.FusedMultiplyAdd(value1, value2, 3f) / 2f;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPSqrt(float value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value = value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr);
+
+ float result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPZero(sign);
+ }
+ else if (type == FPType.Infinity && !sign)
+ {
+ result = FPInfinity(sign);
+ }
+ else if (sign)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = MathF.Sqrt(value);
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static float FPSub(float value1, float value2)
+ {
+ return FPSubFpscrImpl(value1, value2, false);
+ }
+
+ private static float FPSubFpscrImpl(float value1, float value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr);
+
+ float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == !sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = value1 - value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0f);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public static float FPDefaultNaN()
+ {
+ return BitConverter.Int32BitsToSingle(0x7fc00000);
+ }
+
+ public static float FPInfinity(bool sign)
+ {
+ return sign ? float.NegativeInfinity : float.PositiveInfinity;
+ }
+
+ public static float FPZero(bool sign)
+ {
+ return sign ? -0f : +0f;
+ }
+
+ public static float FPMaxNormal(bool sign)
+ {
+ return sign ? float.MinValue : float.MaxValue;
+ }
+
+ private static float FPTwo(bool sign)
+ {
+ return sign ? -2f : +2f;
+ }
+
+ private static float FPThree(bool sign)
+ {
+ return sign ? -3f : +3f;
+ }
+
+ private static float FPOnePointFive(bool sign)
+ {
+ return sign ? -1.5f : +1.5f;
+ }
+
+ private static float FPNeg(this float value)
+ {
+ return -value;
+ }
+
+ private static float ZerosOrOnes(bool ones)
+ {
+ return BitConverter.Int32BitsToSingle(ones ? -1 : 0);
+ }
+
+ private static float FPUnpack(
+ this float value,
+ out FPType type,
+ out bool sign,
+ out uint valueBits,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ valueBits = (uint)BitConverter.SingleToInt32Bits(value);
+
+ sign = (~valueBits & 0x80000000u) == 0u;
+
+ if ((valueBits & 0x7F800000u) == 0u)
+ {
+ if ((valueBits & 0x007FFFFFu) == 0u || (fpcr & FPCR.Fz) != 0)
+ {
+ type = FPType.Zero;
+ value = FPZero(sign);
+
+ if ((valueBits & 0x007FFFFFu) != 0u)
+ {
+ SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero;
+ }
+ }
+ else if ((~valueBits & 0x7F800000u) == 0u)
+ {
+ if ((valueBits & 0x007FFFFFu) == 0u)
+ {
+ type = FPType.Infinity;
+ }
+ else
+ {
+ type = (~valueBits & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN;
+ value = FPZero(sign);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero;
+ }
+
+ return value;
+ }
+
+ private static float FPProcessNaNs(
+ FPType type1,
+ FPType type2,
+ uint op1,
+ uint op2,
+ out bool done,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ done = true;
+
+ if (type1 == FPType.SNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.SNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type1 == FPType.QNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.QNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+
+ done = false;
+
+ return FPZero(false);
+ }
+
+ private static float FPProcessNaNs3(
+ FPType type1,
+ FPType type2,
+ FPType type3,
+ uint op1,
+ uint op2,
+ uint op3,
+ out bool done,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ done = true;
+
+ if (type1 == FPType.SNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.SNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type3 == FPType.SNaN)
+ {
+ return FPProcessNaN(type3, op3, context, fpcr);
+ }
+ else if (type1 == FPType.QNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.QNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type3 == FPType.QNaN)
+ {
+ return FPProcessNaN(type3, op3, context, fpcr);
+ }
+
+ done = false;
+
+ return FPZero(false);
+ }
+
+ private static float FPProcessNaN(FPType type, uint op, ExecutionContext context, FPCR fpcr)
+ {
+ if (type == FPType.SNaN)
+ {
+ op |= 1u << 22;
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+
+ if ((fpcr & FPCR.Dn) != 0)
+ {
+ return FPDefaultNaN();
+ }
+
+ return BitConverter.Int32BitsToSingle((int)op);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32_16.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32_16.cs
new file mode 100644
index 000000000..67bc5149d
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat32_16.cs
@@ -0,0 +1,126 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat32_16
+ {
+ [UnmanagedCallersOnly]
+ public static ushort FPConvert(float value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+
+ double real = value.FPUnpackCv(out FPType type, out bool sign, out uint valueBits, context);
+
+ bool altHp = (context.Fpcr & FPCR.Ahp) != 0;
+
+ ushort resultBits;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ if (altHp)
+ {
+ resultBits = SoftFloat16.FPZero(sign);
+ }
+ else if ((context.Fpcr & FPCR.Dn) != 0)
+ {
+ resultBits = SoftFloat16.FPDefaultNaN();
+ }
+ else
+ {
+ resultBits = FPConvertNaN(valueBits);
+ }
+
+ if (type == FPType.SNaN || altHp)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ }
+ else if (type == FPType.Infinity)
+ {
+ if (altHp)
+ {
+ resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ else
+ {
+ resultBits = SoftFloat16.FPInfinity(sign);
+ }
+ }
+ else if (type == FPType.Zero)
+ {
+ resultBits = SoftFloat16.FPZero(sign);
+ }
+ else
+ {
+ resultBits = SoftFloat16.FPRoundCv(real, context);
+ }
+
+ return resultBits;
+ }
+
+ private static double FPUnpackCv(
+ this float value,
+ out FPType type,
+ out bool sign,
+ out uint valueBits,
+ ExecutionContext context)
+ {
+ valueBits = (uint)BitConverter.SingleToInt32Bits(value);
+
+ sign = (~valueBits & 0x80000000u) == 0u;
+
+ uint exp32 = (valueBits & 0x7F800000u) >> 23;
+ uint frac32 = valueBits & 0x007FFFFFu;
+
+ double real;
+
+ if (exp32 == 0u)
+ {
+ if (frac32 == 0u || (context.Fpcr & FPCR.Fz) != 0)
+ {
+ type = FPType.Zero;
+ real = 0d;
+
+ if (frac32 != 0u)
+ {
+ SoftFloat.FPProcessException(FPException.InputDenorm, context);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero; // Subnormal.
+ real = Math.Pow(2d, -126) * ((double)frac32 * Math.Pow(2d, -23));
+ }
+ }
+ else if (exp32 == 0xFFu)
+ {
+ if (frac32 == 0u)
+ {
+ type = FPType.Infinity;
+ real = Math.Pow(2d, 1000);
+ }
+ else
+ {
+ type = (~frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN;
+ real = 0d;
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero; // Normal.
+ real = Math.Pow(2d, (int)exp32 - 127) * (1d + (double)frac32 * Math.Pow(2d, -23));
+ }
+
+ return sign ? -real : real;
+ }
+
+ private static ushort FPConvertNaN(uint valueBits)
+ {
+ return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64.cs
new file mode 100644
index 000000000..cad132e3a
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64.cs
@@ -0,0 +1,1421 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat64
+ {
+ [UnmanagedCallersOnly]
+ public static double FPAdd(double value1, double value2)
+ {
+ return FPAddFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPAddFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPAddFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPAddFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == !sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = value1 + value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static int FPCompare(double value1, double value2, byte signalNaNs)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ int result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = 0b0011;
+
+ if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs == 1)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ }
+ else
+ {
+ if (value1 == value2)
+ {
+ result = 0b0110;
+ }
+ else if (value1 < value2)
+ {
+ result = 0b1000;
+ }
+ else
+ {
+ result = 0b0010;
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareEQ(double value1, double value2)
+ {
+ return FPCompareEQFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareEQFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPCompareEQFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPCompareEQFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ double result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ if (type1 == FPType.SNaN || type2 == FPType.SNaN)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 == value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareGE(double value1, double value2)
+ {
+ return FPCompareGEFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareGEFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPCompareGEFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPCompareGEFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ double result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 >= value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareGT(double value1, double value2)
+ {
+ return FPCompareGTFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareGTFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPCompareGTFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPCompareGTFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ double result;
+
+ if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN)
+ {
+ result = ZerosOrOnes(false);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = ZerosOrOnes(value1 > value2);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareLE(double value1, double value2)
+ {
+ return FPCompareGEFpscrImpl(value2, value1, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareLT(double value1, double value2)
+ {
+ return FPCompareGTFpscrImpl(value2, value1, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareLEFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPCompareGEFpscrImpl(value2, value1, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPCompareLTFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPCompareGTFpscrImpl(value2, value1, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPDiv(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && inf2) || (zero1 && zero2))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (inf1 || zero2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+
+ if (!inf1)
+ {
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ }
+ else if (zero1 || inf2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 / value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMax(double value1, double value2)
+ {
+ return FPMaxFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMaxFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPMaxFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMaxFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ if (value1 > value2)
+ {
+ if (type1 == FPType.Infinity)
+ {
+ result = FPInfinity(sign1);
+ }
+ else if (type1 == FPType.Zero)
+ {
+ result = FPZero(sign1 && sign2);
+ }
+ else
+ {
+ result = value1;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+ else
+ {
+ if (type2 == FPType.Infinity)
+ {
+ result = FPInfinity(sign2);
+ }
+ else if (type2 == FPType.Zero)
+ {
+ result = FPZero(sign1 && sign2);
+ }
+ else
+ {
+ result = value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMaxNum(double value1, double value2)
+ {
+ return FPMaxNumFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMaxNumFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPMaxNumFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMaxNumFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ if (type1 == FPType.QNaN && type2 != FPType.QNaN)
+ {
+ value1 = FPInfinity(true);
+ }
+ else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
+ {
+ value2 = FPInfinity(true);
+ }
+
+ return FPMaxFpscrImpl(value1, value2, standardFpscr);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMin(double value1, double value2)
+ {
+ return FPMinFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMinFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPMinFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMinFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ if (value1 < value2)
+ {
+ if (type1 == FPType.Infinity)
+ {
+ result = FPInfinity(sign1);
+ }
+ else if (type1 == FPType.Zero)
+ {
+ result = FPZero(sign1 || sign2);
+ }
+ else
+ {
+ result = value1;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+ else
+ {
+ if (type2 == FPType.Infinity)
+ {
+ result = FPInfinity(sign2);
+ }
+ else if (type2 == FPType.Zero)
+ {
+ result = FPZero(sign1 || sign2);
+ }
+ else
+ {
+ result = value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMinNum(double value1, double value2)
+ {
+ return FPMinNumFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMinNumFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPMinNumFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMinNumFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1.FPUnpack(out FPType type1, out _, out _, context, fpcr);
+ value2.FPUnpack(out FPType type2, out _, out _, context, fpcr);
+
+ if (type1 == FPType.QNaN && type2 != FPType.QNaN)
+ {
+ value1 = FPInfinity(false);
+ }
+ else if (type1 != FPType.QNaN && type2 == FPType.QNaN)
+ {
+ value2 = FPInfinity(false);
+ }
+
+ return FPMinFpscrImpl(value1, value2, standardFpscr);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMul(double value1, double value2)
+ {
+ return FPMulFpscrImpl(value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulFpscr(double value1, double value2, byte standardFpscr)
+ {
+ return FPMulFpscrImpl(value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMulFpscrImpl(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else if (zero1 || zero2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 * value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulAdd(double valueA, double value1, double value2)
+ {
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulAddFpscr(double valueA, double value1, double value2, byte standardFpscr)
+ {
+ return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
+ }
+
+ private static double FPMulAddFpscrImpl(double valueA, double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context, fpcr);
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ double result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr);
+
+ if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2)))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+
+ if (!done)
+ {
+ bool infA = typeA == FPType.Infinity;
+ bool zeroA = typeA == FPType.Zero;
+
+ bool signP = sign1 ^ sign2;
+ bool infP = inf1 || inf2;
+ bool zeroP = zero1 || zero2;
+
+ if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP))
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((infA && !signA) || (infP && !signP))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((infA && signA) || (infP && signP))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zeroA && zeroP && signA == signP)
+ {
+ result = FPZero(signA);
+ }
+ else
+ {
+ result = Math.FusedMultiplyAdd(value1, value2, valueA);
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulSub(double valueA, double value1, double value2)
+ {
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulSubFpscr(double valueA, double value1, double value2, byte standardFpscr)
+ {
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, standardFpscr == 1);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPMulX(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPTwo(sign1 ^ sign2);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else if (zero1 || zero2)
+ {
+ result = FPZero(sign1 ^ sign2);
+ }
+ else
+ {
+ result = value1 * value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPNegMulAdd(double valueA, double value1, double value2)
+ {
+ valueA = valueA.FPNeg();
+ value1 = value1.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPNegMulSub(double valueA, double value1, double value2)
+ {
+ valueA = valueA.FPNeg();
+
+ return FPMulAddFpscrImpl(valueA, value1, value2, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRecipEstimate(double value)
+ {
+ return FPRecipEstimateFpscrImpl(value, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRecipEstimateFpscr(double value, byte standardFpscr)
+ {
+ return FPRecipEstimateFpscrImpl(value, standardFpscr == 1);
+ }
+
+ private static double FPRecipEstimateFpscrImpl(double value, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
+
+ double result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = FPZero(sign);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPInfinity(sign);
+
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ else if (Math.Abs(value) < Math.Pow(2d, -1024))
+ {
+ bool overflowToInf = fpcr.RoundingMode switch
+ {
+ FPRoundingMode.ToNearest => true,
+ FPRoundingMode.TowardsPlusInfinity => !sign,
+ FPRoundingMode.TowardsMinusInfinity => sign,
+ FPRoundingMode.TowardsZero => false,
+ _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.RoundingMode}\"."),
+ };
+ result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign);
+
+ SoftFloat.FPProcessException(FPException.Overflow, context, fpcr);
+ SoftFloat.FPProcessException(FPException.Inexact, context, fpcr);
+ }
+ else if ((fpcr & FPCR.Fz) != 0 && (Math.Abs(value) >= Math.Pow(2d, 1022)))
+ {
+ result = FPZero(sign);
+
+ context.Fpsr |= FPSR.Ufc;
+ }
+ else
+ {
+ ulong fraction = op & 0x000FFFFFFFFFFFFFul;
+ uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52);
+
+ if (exp == 0u)
+ {
+ if ((fraction & 0x0008000000000000ul) == 0ul)
+ {
+ fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2;
+ exp -= 1u;
+ }
+ else
+ {
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ }
+ }
+
+ uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
+
+ uint resultExp = 2045u - exp;
+
+ uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u;
+
+ fraction = (ulong)(estimate & 0xFFu) << 44;
+
+ if (resultExp == 0u)
+ {
+ fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1;
+ }
+ else if (resultExp + 1u == 0u)
+ {
+ fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2;
+ resultExp = 0u;
+ }
+
+ result = BitConverter.Int64BitsToDouble(
+ (long)((sign ? 1ul : 0ul) << 63 | (resultExp & 0x7FFul) << 52 | (fraction & 0x000FFFFFFFFFFFFFul)));
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRecipStep(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.StandardFpcrValue;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ double product;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ product = FPZero(false);
+ }
+ else
+ {
+ product = FPMulFpscrImpl(value1, value2, true);
+ }
+
+ result = FPSubFpscr(FPTwo(false), product, true);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRecipStepFused(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPNeg();
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPTwo(false);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else
+ {
+ result = Math.FusedMultiplyAdd(value1, value2, 2d);
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRecpX(double value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
+
+ double result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else
+ {
+ ulong notExp = (~op >> 52) & 0x7FFul;
+ ulong maxExp = 0x7FEul;
+
+ result = BitConverter.Int64BitsToDouble(
+ (long)((sign ? 1ul : 0ul) << 63 | (notExp == 0x7FFul ? maxExp : notExp) << 52));
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRSqrtEstimate(double value)
+ {
+ return FPRSqrtEstimateFpscrImpl(value, false);
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRSqrtEstimateFpscr(double value, byte standardFpscr)
+ {
+ return FPRSqrtEstimateFpscrImpl(value, standardFpscr == 1);
+ }
+
+ private static double FPRSqrtEstimateFpscrImpl(double value, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
+
+ double result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPInfinity(sign);
+
+ SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr);
+ }
+ else if (sign)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if (type == FPType.Infinity)
+ {
+ result = FPZero(false);
+ }
+ else
+ {
+ ulong fraction = op & 0x000FFFFFFFFFFFFFul;
+ uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52);
+
+ if (exp == 0u)
+ {
+ while ((fraction & 0x0008000000000000ul) == 0ul)
+ {
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ exp -= 1u;
+ }
+
+ fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1;
+ }
+
+ uint scaled;
+
+ if ((exp & 1u) == 0u)
+ {
+ scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44);
+ }
+ else
+ {
+ scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45);
+ }
+
+ uint resultExp = (3068u - exp) >> 1;
+
+ uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u;
+
+ result = BitConverter.Int64BitsToDouble((long)((resultExp & 0x7FFul) << 52 | (estimate & 0xFFul) << 44));
+ }
+
+ return result;
+ }
+
+ public static double FPHalvedSub(double value1, double value2, ExecutionContext context, FPCR fpcr)
+ {
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == !sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = (value1 - value2) / 2.0;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRSqrtStep(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.StandardFpcrValue;
+
+ value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ double product;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ product = FPZero(false);
+ }
+ else
+ {
+ product = FPMulFpscrImpl(value1, value2, true);
+ }
+
+ result = FPHalvedSub(FPThree(false), product, context, fpcr);
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPRSqrtStepFused(double value1, double value2)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value1 = value1.FPNeg();
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if ((inf1 && zero2) || (zero1 && inf2))
+ {
+ result = FPOnePointFive(false);
+ }
+ else if (inf1 || inf2)
+ {
+ result = FPInfinity(sign1 ^ sign2);
+ }
+ else
+ {
+ result = Math.FusedMultiplyAdd(value1, value2, 3d) / 2d;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPSqrt(double value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = context.Fpcr;
+
+ value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr);
+
+ double result;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ result = FPProcessNaN(type, op, context, fpcr);
+ }
+ else if (type == FPType.Zero)
+ {
+ result = FPZero(sign);
+ }
+ else if (type == FPType.Infinity && !sign)
+ {
+ result = FPInfinity(sign);
+ }
+ else if (sign)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else
+ {
+ result = Math.Sqrt(value);
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly]
+ public static double FPSub(double value1, double value2)
+ {
+ return FPSubFpscr(value1, value2, false);
+ }
+
+ public static double FPSubFpscr(double value1, double value2, bool standardFpscr)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+ FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr;
+
+ value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr);
+ value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr);
+
+ double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr);
+
+ if (!done)
+ {
+ bool inf1 = type1 == FPType.Infinity;
+ bool zero1 = type1 == FPType.Zero;
+ bool inf2 = type2 == FPType.Infinity;
+ bool zero2 = type2 == FPType.Zero;
+
+ if (inf1 && inf2 && sign1 == sign2)
+ {
+ result = FPDefaultNaN();
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+ else if ((inf1 && !sign1) || (inf2 && sign2))
+ {
+ result = FPInfinity(false);
+ }
+ else if ((inf1 && sign1) || (inf2 && !sign2))
+ {
+ result = FPInfinity(true);
+ }
+ else if (zero1 && zero2 && sign1 == !sign2)
+ {
+ result = FPZero(sign1);
+ }
+ else
+ {
+ result = value1 - value2;
+
+ if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result))
+ {
+ context.Fpsr |= FPSR.Ufc;
+
+ result = FPZero(result < 0d);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public static double FPDefaultNaN()
+ {
+ return BitConverter.Int64BitsToDouble(0x7ff8000000000000);
+ }
+
+ public static double FPInfinity(bool sign)
+ {
+ return sign ? double.NegativeInfinity : double.PositiveInfinity;
+ }
+
+ public static double FPZero(bool sign)
+ {
+ return sign ? -0d : +0d;
+ }
+
+ public static double FPMaxNormal(bool sign)
+ {
+ return sign ? double.MinValue : double.MaxValue;
+ }
+
+ private static double FPTwo(bool sign)
+ {
+ return sign ? -2d : +2d;
+ }
+
+ private static double FPThree(bool sign)
+ {
+ return sign ? -3d : +3d;
+ }
+
+ private static double FPOnePointFive(bool sign)
+ {
+ return sign ? -1.5d : +1.5d;
+ }
+
+ private static double FPNeg(this double value)
+ {
+ return -value;
+ }
+
+ private static double ZerosOrOnes(bool ones)
+ {
+ return BitConverter.Int64BitsToDouble(ones ? -1L : 0L);
+ }
+
+ private static double FPUnpack(
+ this double value,
+ out FPType type,
+ out bool sign,
+ out ulong valueBits,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ valueBits = (ulong)BitConverter.DoubleToInt64Bits(value);
+
+ sign = (~valueBits & 0x8000000000000000ul) == 0ul;
+
+ if ((valueBits & 0x7FF0000000000000ul) == 0ul)
+ {
+ if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (fpcr & FPCR.Fz) != 0)
+ {
+ type = FPType.Zero;
+ value = FPZero(sign);
+
+ if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul)
+ {
+ SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero;
+ }
+ }
+ else if ((~valueBits & 0x7FF0000000000000ul) == 0ul)
+ {
+ if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul)
+ {
+ type = FPType.Infinity;
+ }
+ else
+ {
+ type = (~valueBits & 0x0008000000000000ul) == 0ul ? FPType.QNaN : FPType.SNaN;
+ value = FPZero(sign);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero;
+ }
+
+ return value;
+ }
+
+ private static double FPProcessNaNs(
+ FPType type1,
+ FPType type2,
+ ulong op1,
+ ulong op2,
+ out bool done,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ done = true;
+
+ if (type1 == FPType.SNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.SNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type1 == FPType.QNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.QNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+
+ done = false;
+
+ return FPZero(false);
+ }
+
+ private static double FPProcessNaNs3(
+ FPType type1,
+ FPType type2,
+ FPType type3,
+ ulong op1,
+ ulong op2,
+ ulong op3,
+ out bool done,
+ ExecutionContext context,
+ FPCR fpcr)
+ {
+ done = true;
+
+ if (type1 == FPType.SNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.SNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type3 == FPType.SNaN)
+ {
+ return FPProcessNaN(type3, op3, context, fpcr);
+ }
+ else if (type1 == FPType.QNaN)
+ {
+ return FPProcessNaN(type1, op1, context, fpcr);
+ }
+ else if (type2 == FPType.QNaN)
+ {
+ return FPProcessNaN(type2, op2, context, fpcr);
+ }
+ else if (type3 == FPType.QNaN)
+ {
+ return FPProcessNaN(type3, op3, context, fpcr);
+ }
+
+ done = false;
+
+ return FPZero(false);
+ }
+
+ private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context, FPCR fpcr)
+ {
+ if (type == FPType.SNaN)
+ {
+ op |= 1ul << 51;
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr);
+ }
+
+ if ((fpcr & FPCR.Dn) != 0)
+ {
+ return FPDefaultNaN();
+ }
+
+ return BitConverter.Int64BitsToDouble((long)op);
+ }
+ }
+}
diff --git a/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64_16.cs b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64_16.cs
new file mode 100644
index 000000000..71bc84da5
--- /dev/null
+++ b/src/ARMeilleure/Instructions/SoftFloat/SoftFloat64_16.cs
@@ -0,0 +1,127 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Instructions
+{
+ static class SoftFloat64_16
+ {
+ [UnmanagedCallersOnly]
+ public static ushort FPConvert(double value)
+ {
+ ExecutionContext context = NativeInterface.GetContext();
+
+ double real = value.FPUnpackCv(out FPType type, out bool sign, out ulong valueBits, context);
+
+ bool altHp = (context.Fpcr & FPCR.Ahp) != 0;
+
+ ushort resultBits;
+
+ if (type is FPType.SNaN or FPType.QNaN)
+ {
+ if (altHp)
+ {
+ resultBits = SoftFloat16.FPZero(sign);
+ }
+ else if ((context.Fpcr & FPCR.Dn) != 0)
+ {
+ resultBits = SoftFloat16.FPDefaultNaN();
+ }
+ else
+ {
+ resultBits = FPConvertNaN(valueBits);
+ }
+
+ if (type == FPType.SNaN || altHp)
+ {
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ }
+ else if (type == FPType.Infinity)
+ {
+ if (altHp)
+ {
+ resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu);
+
+ SoftFloat.FPProcessException(FPException.InvalidOp, context);
+ }
+ else
+ {
+ resultBits = SoftFloat16.FPInfinity(sign);
+ }
+ }
+ else if (type == FPType.Zero)
+ {
+ resultBits = SoftFloat16.FPZero(sign);
+ }
+ else
+ {
+ resultBits = SoftFloat16.FPRoundCv(real, context);
+ }
+
+ return resultBits;
+ }
+
+ private static double FPUnpackCv(
+ this double value,
+ out FPType type,
+ out bool sign,
+ out ulong valueBits,
+ ExecutionContext context)
+ {
+ valueBits = (ulong)BitConverter.DoubleToInt64Bits(value);
+
+ sign = (~valueBits & 0x8000000000000000ul) == 0u;
+
+ ulong exp64 = (valueBits & 0x7FF0000000000000ul) >> 52;
+ ulong frac64 = valueBits & 0x000FFFFFFFFFFFFFul;
+
+ double real;
+
+ if (exp64 == 0u)
+ {
+ if (frac64 == 0u || (context.Fpcr & FPCR.Fz) != 0)
+ {
+ type = FPType.Zero;
+ real = 0d;
+
+ if (frac64 != 0u)
+ {
+ SoftFloat.FPProcessException(FPException.InputDenorm, context);
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero; // Subnormal.
+ real = Math.Pow(2d, -1022) * ((double)frac64 * Math.Pow(2d, -52));
+ }
+ }
+ else if (exp64 == 0x7FFul)
+ {
+ if (frac64 == 0u)
+ {
+ type = FPType.Infinity;
+ real = Math.Pow(2d, 1000000);
+ }
+ else
+ {
+ type = (~frac64 & 0x0008000000000000ul) == 0u ? FPType.QNaN : FPType.SNaN;
+ real = 0d;
+ }
+ }
+ else
+ {
+ type = FPType.Nonzero; // Normal.
+ real = Math.Pow(2d, (int)exp64 - 1023) * (1d + (double)frac64 * Math.Pow(2d, -52));
+ }
+
+ return sign ? -real : real;
+ }
+
+ private static ushort FPConvertNaN(ulong valueBits)
+ {
+ return (ushort)((valueBits & 0x8000000000000000ul) >> 48 | 0x7E00u |
+ (valueBits & 0x0007FC0000000000ul) >> 42);
+ }
+ }
+}
diff --git a/src/ARMeilleure/IntermediateRepresentation/Comparison.cs b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs
index 3d6a9d818..c9d3b5c76 100644
--- a/src/ARMeilleure/IntermediateRepresentation/Comparison.cs
+++ b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs
@@ -16,9 +16,9 @@ namespace ARMeilleure.IntermediateRepresentation
static class ComparisonExtensions
{
- public static Comparison Invert(this Comparison comp)
+ extension(Comparison comparison)
{
- return (Comparison)((int)comp ^ 1);
+ public Comparison Inverse => (Comparison)((int)comparison ^ 1);
}
}
}
diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandType.cs b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs
index fec22eed6..027be8cac 100644
--- a/src/ARMeilleure/IntermediateRepresentation/OperandType.cs
+++ b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs
@@ -14,48 +14,38 @@ namespace ARMeilleure.IntermediateRepresentation
static class OperandTypeExtensions
{
- public static bool IsInteger(this OperandType type)
+ extension(OperandType type)
{
- return type is OperandType.I32 or
- OperandType.I64;
- }
-
- public static RegisterType ToRegisterType(this OperandType type)
- {
- return type switch
+ public bool IsInteger => type is OperandType.I32 or OperandType.I64;
+
+ public RegisterType Register => type switch
{
OperandType.FP32 => RegisterType.Vector,
OperandType.FP64 => RegisterType.Vector,
OperandType.I32 => RegisterType.Integer,
OperandType.I64 => RegisterType.Integer,
OperandType.V128 => RegisterType.Vector,
- _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."),
+ _ => throw new InvalidOperationException($"Invalid operand type \"{type}\".")
};
- }
-
- public static int GetSizeInBytes(this OperandType type)
- {
- return type switch
+
+ public int ByteSize => type switch
{
OperandType.FP32 => 4,
OperandType.FP64 => 8,
OperandType.I32 => 4,
OperandType.I64 => 8,
OperandType.V128 => 16,
- _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."),
+ _ => throw new InvalidOperationException($"Invalid operand type \"{type}\".")
};
- }
-
- public static int GetSizeInBytesLog2(this OperandType type)
- {
- return type switch
+
+ public int ByteSizeLog2 => type switch
{
OperandType.FP32 => 2,
OperandType.FP64 => 3,
OperandType.I32 => 2,
OperandType.I64 => 3,
OperandType.V128 => 4,
- _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."),
+ _ => throw new InvalidOperationException($"Invalid operand type \"{type}\".")
};
}
}
diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs
index cad7c3558..ce417ee7e 100644
--- a/src/ARMeilleure/Memory/MemoryManagerType.cs
+++ b/src/ARMeilleure/Memory/MemoryManagerType.cs
@@ -45,19 +45,12 @@ namespace ARMeilleure.Memory
public static class MemoryManagerTypeExtensions
{
- public static bool IsHostMapped(this MemoryManagerType type)
+ extension(MemoryManagerType type)
{
- return type is MemoryManagerType.HostMapped or MemoryManagerType.HostMappedUnsafe;
- }
-
- public static bool IsHostTracked(this MemoryManagerType type)
- {
- return type is MemoryManagerType.HostTracked or MemoryManagerType.HostTrackedUnsafe;
- }
-
- public static bool IsHostMappedOrTracked(this MemoryManagerType type)
- {
- return type.IsHostMapped() || type.IsHostTracked();
+ public bool IsHostMapped => type is MemoryManagerType.HostMapped or MemoryManagerType.HostMappedUnsafe;
+ public bool IsHostTracked => type is MemoryManagerType.HostTracked or MemoryManagerType.HostTrackedUnsafe;
+
+ public bool IsHostMappedOrTracked => type.IsHostMapped || type.IsHostTracked;
}
}
}
diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs
index 6dd7befe7..3a76b6d93 100644
--- a/src/ARMeilleure/Optimizations.cs
+++ b/src/ARMeilleure/Optimizations.cs
@@ -13,6 +13,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool EnableDebugging { get; set; } = false;
+ public static bool EnableDeepCallRecursionProtection { get; set; } = true;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index fa1a4a032..365805e45 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -134,6 +134,11 @@ namespace ARMeilleure.State
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
+ internal void ResetCallDepth()
+ {
+ _nativeContext.ResetCallDepth();
+ }
+
internal void CheckInterrupt()
{
if (Interrupted)
diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs
index a9f1c3dab..bc9e31888 100644
--- a/src/ARMeilleure/State/NativeContext.cs
+++ b/src/ARMeilleure/State/NativeContext.cs
@@ -22,6 +22,7 @@ namespace ARMeilleure.State
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
+ public int CallDepth;
///
/// Precise PC value used for debugging.
@@ -199,6 +200,8 @@ namespace ARMeilleure.State
public bool GetRunning() => GetStorage().Running != 0;
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
+ public void ResetCallDepth() => GetStorage().CallDepth = 0;
+
public unsafe static int GetRegisterOffset(Register reg)
{
if (reg.Type == RegisterType.Integer)
@@ -284,6 +287,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
}
+ public static int GetCallDepthOffset()
+ {
+ return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
+ }
+
private static int StorageOffset(ref NativeCtxStorage storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target);
diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs
index f4abe3bd5..5af487e28 100644
--- a/src/ARMeilleure/Translation/IntervalTree.cs
+++ b/src/ARMeilleure/Translation/IntervalTree.cs
@@ -361,10 +361,7 @@ namespace ARMeilleure.Translation
IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
- if (tmp != null)
- {
- tmp.Parent = ParentOf(replacementNode);
- }
+ tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{
@@ -582,10 +579,7 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode right = RightOf(node);
node.Right = LeftOf(right);
- if (node.Right != null)
- {
- node.Right.Parent = node;
- }
+ node.Right?.Parent = node;
IntervalTreeNode nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -615,10 +609,7 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode left = LeftOf(node);
node.Left = RightOf(left);
- if (node.Left != null)
- {
- node.Left.Parent = node;
- }
+ node.Left?.Parent = node;
IntervalTreeNode nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -667,10 +658,7 @@ namespace ARMeilleure.Translation
/// Color (Boolean)
private static void SetColor(IntervalTreeNode node, bool color)
{
- if (node != null)
- {
- node.Color = color;
- }
+ node?.Color = color;
}
///
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index c69ebcadb..cfa4377db 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 073b7ffe2..b098ff4cf 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -186,6 +186,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer();
+ context.ResetCallDepth();
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address);
@@ -260,6 +261,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Translation);
+ InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address));
EmitSynchronization(context);
if (blocks[0].Address != address)
@@ -412,7 +414,7 @@ namespace ARMeilleure.Translation
{
context.SyncQcFlag();
- if (block.Branch != null && !block.Branch.Exit && block.Branch.Address <= block.Address)
+ if (block.Branch is { Exit: false } && block.Branch.Address <= block.Address)
{
EmitSynchronization(context);
}
@@ -429,14 +431,14 @@ namespace ARMeilleure.Translation
{
lblPredicateSkip = Label();
- InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, context.CurrentIfThenBlockCond.Invert());
+ InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, context.CurrentIfThenBlockCond.Inverse);
}
- if (opCode is OpCode32 op && op.Cond < Condition.Al)
+ if (opCode is OpCode32 { Cond: < Condition.Al } op)
{
lblPredicateSkip = Label();
- InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, op.Cond.Invert());
+ InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, op.Cond.Inverse);
}
if (opCode.Instruction.Emitter != null)
diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs
index 458a42434..2d95ceb99 100644
--- a/src/ARMeilleure/Translation/TranslatorStubs.cs
+++ b/src/ARMeilleure/Translation/TranslatorStubs.cs
@@ -262,10 +262,18 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
+ Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
+
+ if (Optimizations.EnableDeepCallRecursionProtection)
+ {
+ // Reset the call depth counter, since this is our first guest function call.
+ context.Store(callDepthAddress, Const(0));
+ }
+
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
context.BranchIfFalse(endLbl, guestAddress);
diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj
similarity index 78%
rename from src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
rename to src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj
index d0d45122e..094a81594 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
+++ b/src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3AudioBuffer.cs
similarity index 69%
rename from src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
rename to src/Ryujinx.Audio.Backends.SDL3/SDL3AudioBuffer.cs
index a390c5467..55a4a60e1 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
+++ b/src/Ryujinx.Audio.Backends.SDL3/SDL3AudioBuffer.cs
@@ -1,12 +1,12 @@
-namespace Ryujinx.Audio.Backends.SDL2
+namespace Ryujinx.Audio.Backends.SDL3
{
- class SDL2AudioBuffer
+ class SDL3AudioBuffer
{
public readonly ulong DriverIdentifier;
public readonly ulong SampleCount;
public ulong SamplePlayed;
- public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount)
+ public SDL3AudioBuffer(ulong driverIdentifier, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
SampleCount = sampleCount;
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs
similarity index 58%
rename from src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
rename to src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs
index d5381209e..bdc9f02f4 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs
@@ -2,42 +2,41 @@ using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
-using Ryujinx.SDL2.Common;
+using Ryujinx.SDL3.Common;
using System;
using System.Collections.Concurrent;
-using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
-using static SDL2.SDL;
+using SDL;
+using static SDL.SDL3;
+using System.Runtime.InteropServices;
-namespace Ryujinx.Audio.Backends.SDL2
+
+namespace Ryujinx.Audio.Backends.SDL3
{
- public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
+
+ using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl];
+
+ public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
- private readonly ConcurrentDictionary _sessions;
+ private readonly ConcurrentDictionary _sessions;
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
- // TODO: Add this to SDL2-CS
- // NOTE: We use a DllImport here because of marshaling issue for spec.
- [DllImport("SDL2")]
- private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture);
-
- public SDL2HardwareDeviceDriver()
+ public unsafe SDL3HardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
- _sessions = new ConcurrentDictionary();
+ _sessions = new ConcurrentDictionary();
- SDL2Driver.Instance.Initialize();
+ SDL3Driver.Instance.Initialize();
- int res = SDL_GetDefaultAudioInfo(nint.Zero, out SDL_AudioSpec spec, 0);
-
- if (res != 0)
+ SDL_AudioSpec spec;
+ if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, null))
{
Logger.Error?.Print(LogClass.Application,
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
@@ -54,16 +53,16 @@ namespace Ryujinx.Audio.Backends.SDL2
public static bool IsSupported => IsSupportedInternal();
- private static bool IsSupportedInternal()
+ private unsafe static bool IsSupportedInternal()
{
- uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
+ SDL_AudioStream* device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
- if (device != 0)
+ if (device != null)
{
- SDL_CloseAudioDevice(device);
+ SDL_DestroyAudioStream(device);
}
- return device != 0;
+ return device != null;
}
public ManualResetEvent GetUpdateRequiredEvent()
@@ -90,67 +89,68 @@ namespace Ryujinx.Audio.Backends.SDL2
if (direction != Direction.Output)
{
- throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
+ throw new NotImplementedException("Input direction is currently not implemented on SDL3 backend!");
}
- SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
+ SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);
return session;
}
- internal bool Unregister(SDL2HardwareDeviceSession session)
+ internal bool Unregister(SDL3HardwareDeviceSession session)
{
return _sessions.TryRemove(session, out _);
}
- private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
+ private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
{
return new SDL_AudioSpec
{
channels = (byte)requestedChannelCount,
- format = GetSDL2Format(requestedSampleFormat),
+ format = GetSDL3Format(requestedSampleFormat),
freq = (int)requestedSampleRate,
- samples = (ushort)sampleCount,
};
}
- internal static ushort GetSDL2Format(SampleFormat format)
+ internal static SDL_AudioFormat GetSDL3Format(SampleFormat format)
{
return format switch
{
- SampleFormat.PcmInt8 => AUDIO_S8,
- SampleFormat.PcmInt16 => AUDIO_S16,
- SampleFormat.PcmInt32 => AUDIO_S32,
- SampleFormat.PcmFloat => AUDIO_F32,
+ SampleFormat.PcmInt8 => SDL_AudioFormat.SDL_AUDIO_S8,
+ SampleFormat.PcmInt16 => SDL_AudioFormat.SDL_AUDIO_S16LE,
+ SampleFormat.PcmInt32 => SDL_AudioFormat.SDL_AUDIO_S32LE,
+ SampleFormat.PcmFloat => SDL_AudioFormat.SDL_AUDIO_F32LE,
_ => throw new ArgumentException($"Unsupported sample format {format}"),
};
}
- internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
+ internal unsafe static SDL_AudioStream* OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL3HardwareDeviceSession.SDL_AudioStreamCallback callback)
{
- SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
+ SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount);
+ SDL_AudioSpec got = desired;
+ var pCallback = callback != null ? (SDL_AudioStreamCallbackPointer)Marshal.GetFunctionPointerForDelegate(callback) : null;
- desired.callback = callback;
+ // From SDL 3 and on, SDL requires us to set this as a hint
+ SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, $"{sampleCount}");
+ SDL_AudioStream* device = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &got, pCallback, 0);
- uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
-
- if (device == 0)
+ if (device == null)
{
- Logger.Error?.Print(LogClass.Application, $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
+ Logger.Error?.Print(LogClass.Application, $"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\"");
- return 0;
+ return null;
}
bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
if (!isValid)
{
- Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
- SDL_CloseAudioDevice(device);
+ Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid");
+ SDL_DestroyAudioStream(device);
- return 0;
+ return null;
}
return device;
@@ -166,12 +166,12 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (disposing)
{
- foreach (SDL2HardwareDeviceSession session in _sessions.Keys)
+ foreach (SDL3HardwareDeviceSession session in _sessions.Keys)
{
session.Dispose();
}
- SDL2Driver.Instance.Dispose();
+ SDL3Driver.Instance.Dispose();
_pauseEvent.Dispose();
}
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs
similarity index 64%
rename from src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
rename to src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs
index 9170b73c7..377d86d2b 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs
@@ -6,36 +6,43 @@ using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Threading;
+using SDL;
+using static SDL.SDL3;
+using System.Runtime.InteropServices;
-using static SDL2.SDL;
-
-namespace Ryujinx.Audio.Backends.SDL2
+namespace Ryujinx.Audio.Backends.SDL3
{
- class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase
+
+
+
+ unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
{
- private readonly SDL2HardwareDeviceDriver _driver;
- private readonly ConcurrentQueue _queuedBuffers;
+ private readonly SDL3HardwareDeviceDriver _driver;
+ private readonly ConcurrentQueue _queuedBuffers;
private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent;
- private uint _outputStream;
+ private SDL_AudioStream* _outputStream;
private bool _hasSetupError;
- private readonly SDL_AudioCallback _callbackDelegate;
+ private readonly SDL_AudioStreamCallback _callbackDelegate;
private readonly int _bytesPerFrame;
private uint _sampleCount;
private bool _started;
private float _volume;
- private readonly ushort _nativeSampleFormat;
+ private readonly SDL_AudioFormat _nativeSampleFormat;
- public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void SDL_AudioStreamCallback(nint session, SDL_AudioStream* stream, int stream_count, int device_count);
+
+ public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
- _queuedBuffers = new ConcurrentQueue();
+ _queuedBuffers = new ConcurrentQueue();
_ringBuffer = new DynamicRingBuffer();
_callbackDelegate = Update;
_bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
- _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
+ _nativeSampleFormat = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue;
_started = false;
_volume = 1f;
@@ -44,45 +51,51 @@ namespace Ryujinx.Audio.Backends.SDL2
private void EnsureAudioStreamSetup(AudioBuffer buffer)
{
uint bufferSampleCount = (uint)GetSampleCount(buffer);
- bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
+ bool needAudioSetup = (_outputStream == null && !_hasSetupError) ||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
if (needAudioSetup)
{
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
- uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
+ SDL_AudioStream* newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
- _hasSetupError = newOutputStream == 0;
+ _hasSetupError = newOutputStream == null;
if (!_hasSetupError)
{
- if (_outputStream != 0)
+ if (_outputStream != null)
{
- SDL_CloseAudioDevice(_outputStream);
+ SDL_DestroyAudioStream(_outputStream);
}
_outputStream = newOutputStream;
- SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1);
+ if (_started) {
+ SDL_ResumeAudioStreamDevice(_outputStream);
+ } else {
+ SDL_PauseAudioStreamDevice(_outputStream);
+ }
Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}");
}
}
}
- private unsafe void Update(nint userdata, nint stream, int streamLength)
+ private unsafe void Update(nint userdata, SDL_AudioStream* streamDevice, int additionalAmount, int totalAmmount)
{
- Span streamSpan = new((void*)stream, streamLength);
+ using SpanOwner stream = SpanOwner.Rent(additionalAmount);
+ Span streamSpan = stream.Span;
- int maxFrameCount = (int)GetSampleCount(streamLength);
+
+ int maxFrameCount = (int)GetSampleCount(additionalAmount);
int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
if (frameCount == 0)
{
- // SDL2 left the responsibility to the user to clear the buffer.
+ // SDL3 left the responsibility to the user to clear the buffer.
streamSpan.Clear();
return;
@@ -94,15 +107,17 @@ namespace Ryujinx.Audio.Backends.SDL2
_ringBuffer.Read(samples, 0, samples.Length);
- fixed (byte* p = samples)
- {
- nint pStreamSrc = (nint)p;
+ // Zero the dest buffer
+ streamSpan.Clear();
- // Zero the dest buffer
- streamSpan.Clear();
+ fixed (byte* pStreamDst = streamSpan) {
+ fixed (byte* pStreamSrc = samples)
+ {
- // Apply volume to written data
- SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
+ // Apply volume to written data
+ SDL_MixAudio(pStreamDst, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, _driver.Volume * _volume);
+ SDL_PutAudioStreamData(streamDevice, (nint)pStreamDst, additionalAmount);
+ }
}
ulong sampleCount = GetSampleCount(samples.Length);
@@ -111,7 +126,7 @@ namespace Ryujinx.Audio.Backends.SDL2
bool needUpdate = false;
- while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
+ while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{
ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
@@ -152,9 +167,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{
EnsureAudioStreamSetup(buffer);
- if (_outputStream != 0)
+ if (_outputStream != null)
{
- SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
+ SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
@@ -177,9 +192,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (!_started)
{
- if (_outputStream != 0)
+ if (_outputStream != null)
{
- SDL_PauseAudioDevice(_outputStream, 0);
+ SDL_ResumeAudioStreamDevice(_outputStream);
}
_started = true;
@@ -190,9 +205,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (_started)
{
- if (_outputStream != 0)
+ if (_outputStream != null)
{
- SDL_PauseAudioDevice(_outputStream, 1);
+ SDL_PauseAudioStreamDevice(_outputStream);
}
_started = false;
@@ -203,7 +218,7 @@ namespace Ryujinx.Audio.Backends.SDL2
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
- if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
+ if (!_queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{
return true;
}
@@ -218,9 +233,9 @@ namespace Ryujinx.Audio.Backends.SDL2
PrepareToClose();
Stop();
- if (_outputStream != 0)
+ if (_outputStream != null)
{
- SDL_CloseAudioDevice(_outputStream);
+ SDL_DestroyAudioStream(_outputStream);
}
}
}
diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs
index a9acabec9..0410314c4 100644
--- a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs
@@ -58,16 +58,16 @@ namespace Ryujinx.Audio.Backends.CompatLayer
switch (realSampleFormat)
{
case SampleFormat.PcmInt8:
- PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast(convertedSamples), samples);
+ PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast(new Span(convertedSamples)), samples);
break;
case SampleFormat.PcmInt24:
PcmHelper.ConvertSampleToPcm24(convertedSamples, samples);
break;
case SampleFormat.PcmInt32:
- PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast(convertedSamples), samples);
+ PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast(new Span(convertedSamples)), samples);
break;
case SampleFormat.PcmFloat:
- PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast(convertedSamples), samples);
+ PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast(new Span(convertedSamples)), samples);
break;
default:
throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented.");
diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
index 1369f953a..32457d09a 100644
--- a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
+++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
@@ -27,7 +27,7 @@ namespace Ryujinx.Audio.Integration
public void AppendBuffer(ReadOnlySpan data, uint channelCount)
{
- data.CopyTo(MemoryMarshal.Cast(_buffer));
+ data.CopyTo(MemoryMarshal.Cast(new Span(_buffer)));
_session.QueueBuffer(new AudioBuffer
{
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
index aeb187b41..c70d186a0 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
@@ -1,6 +1,7 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Server.Sink;
using System;
+using System.Buffers;
using System.Runtime.CompilerServices;
using System.Text;
@@ -35,7 +36,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Enabled = true;
NodeId = nodeId;
- DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
+ // Unused and wasting time and memory, re-add if needed
+ // DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
+
SessionId = sessionId;
InputCount = sink.Parameter.InputCount;
InputBufferIndices = new ushort[InputCount];
@@ -88,7 +91,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
inputCount = bufferCount;
}
- short[] outputBuffer = new short[inputCount * SampleCount];
+ short[] outputBuffer = ArrayPool.Shared.Rent((int)inputCount * SampleCount);
+ Array.Fill(outputBuffer, (short)0, 0, (int)inputCount * SampleCount);
for (int i = 0; i < bufferCount; i++)
{
@@ -100,7 +104,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
}
- device.AppendBuffer(outputBuffer, inputCount);
+ device.AppendBuffer(outputBuffer.AsSpan(..((int)inputCount * SampleCount)), inputCount);
+
+ ArrayPool.Shared.Return(outputBuffer);
}
else
{
diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs
index 558a66baa..666e2a9e6 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs
@@ -188,6 +188,8 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
///
public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization);
+ private static List _waveBufferUpdaterErrorInfosList;
+
///
/// Initialize the .
///
@@ -216,6 +218,8 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
DataSourceStateAddressInfo.Setup(0, 0);
InitializeWaveBuffers();
+
+ _waveBufferUpdaterErrorInfosList ??= [];
}
///
@@ -587,14 +591,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
Span waveBuffersSpan = WaveBuffers.AsSpan();
Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan();
- List errorInfosList = [];
+ _waveBufferUpdaterErrorInfosList.Clear();
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{
- UpdateWaveBuffer(errorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo);
+ UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo);
}
- errorInfos = errorInfosList.ToArray();
+ errorInfos = _waveBufferUpdaterErrorInfosList.ToArray();
}
///
@@ -628,14 +632,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
Span waveBuffersSpan = WaveBuffers.AsSpan();
Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan();
- List errorInfosList = [];
+ _waveBufferUpdaterErrorInfosList.Clear();
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{
- UpdateWaveBuffer(errorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo);
+ UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo);
}
- errorInfos = errorInfosList.ToArray();
+ errorInfos = _waveBufferUpdaterErrorInfosList.ToArray();
}
///
diff --git a/src/Ryujinx.Common/Collections/IntervalTree.cs b/src/Ryujinx.Common/Collections/IntervalTree.cs
index 27503c5b7..cc2062d48 100644
--- a/src/Ryujinx.Common/Collections/IntervalTree.cs
+++ b/src/Ryujinx.Common/Collections/IntervalTree.cs
@@ -24,7 +24,10 @@ namespace Ryujinx.Common.Collections
/// is null
public int Get(TKey key, ref TValue[] overlaps)
{
- ArgumentNullException.ThrowIfNull(key);
+ if (!typeof(TKey).IsValueType)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+ }
IntervalTreeNode node = GetNode(key);
@@ -91,7 +94,10 @@ namespace Ryujinx.Common.Collections
/// Number of deleted values
public int Remove(TKey key, TValue value)
{
- ArgumentNullException.ThrowIfNull(key);
+ if (!typeof(TKey).IsValueType)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+ }
int removed = Delete(key, value);
@@ -144,7 +150,10 @@ namespace Ryujinx.Common.Collections
/// is null
private IntervalTreeNode GetNode(TKey key)
{
- ArgumentNullException.ThrowIfNull(key);
+ if (!typeof(TKey).IsValueType)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+ }
IntervalTreeNode node = Root;
while (node != null)
@@ -377,10 +386,7 @@ namespace Ryujinx.Common.Collections
IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
- if (tmp != null)
- {
- tmp.Parent = ParentOf(replacementNode);
- }
+ tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{
diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs
index d951a6024..55bfe0019 100644
--- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs
+++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs
@@ -235,10 +235,7 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(element);
color = ColorOf(element);
- if (child != null)
- {
- child.Parent = parent;
- }
+ child?.Parent = parent;
if (parent == null)
{
@@ -258,8 +255,7 @@ namespace Ryujinx.Common.Collections
element.Right = old.Right;
element.Parent = old.Parent;
element.Predecessor = old.Predecessor;
- if (element.Predecessor != null)
- element.Predecessor.Successor = element;
+ element.Predecessor?.Successor = element;
if (ParentOf(old) == null)
{
@@ -292,10 +288,7 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(nodeToDelete);
color = ColorOf(nodeToDelete);
- if (child != null)
- {
- child.Parent = parent;
- }
+ child?.Parent = parent;
if (parent == null)
{
@@ -314,11 +307,9 @@ namespace Ryujinx.Common.Collections
{
RestoreBalanceAfterRemoval(child);
}
-
- if (old.Successor != null)
- old.Successor.Predecessor = old.Predecessor;
- if (old.Predecessor != null)
- old.Predecessor.Successor = old.Successor;
+
+ old.Successor?.Predecessor = old.Predecessor;
+ old.Predecessor?.Successor = old.Successor;
return old;
}
diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs
index abd3723fe..58bcc4fe5 100644
--- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs
+++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs
@@ -250,10 +250,7 @@ namespace Ryujinx.Common.Collections
{
T right = RightOf(node);
node.Right = LeftOf(right);
- if (node.Right != null)
- {
- node.Right.Parent = node;
- }
+ node.Right?.Parent = node;
T nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -281,10 +278,7 @@ namespace Ryujinx.Common.Collections
{
T left = LeftOf(node);
node.Left = RightOf(left);
- if (node.Left != null)
- {
- node.Left.Parent = node;
- }
+ node.Left?.Parent = node;
T nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -329,10 +323,7 @@ namespace Ryujinx.Common.Collections
/// Color (Boolean)
protected static void SetColor(T node, bool color)
{
- if (node != null)
- {
- node.Color = color;
- }
+ node?.Color = color;
}
///
diff --git a/src/Ryujinx.Common/Collections/TreeDictionary.cs b/src/Ryujinx.Common/Collections/TreeDictionary.cs
index 453f128d3..ef818167d 100644
--- a/src/Ryujinx.Common/Collections/TreeDictionary.cs
+++ b/src/Ryujinx.Common/Collections/TreeDictionary.cs
@@ -328,10 +328,7 @@ namespace Ryujinx.Common.Collections
Node tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
- if (tmp != null)
- {
- tmp.Parent = ParentOf(replacementNode);
- }
+ tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{
diff --git a/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
index c3e4402b2..b8cfcae2d 100644
--- a/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
+++ b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
Invalid,
WindowKeyboard,
- GamepadSDL2,
+ GamepadSDL2, //backcompat
+ GamepadSDL3,
}
}
diff --git a/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs b/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs
index 6c2a69b88..eadaab492 100644
--- a/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs
+++ b/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs
@@ -58,7 +58,7 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch
{
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig),
- InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
+ InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
};
}
@@ -70,7 +70,7 @@ namespace Ryujinx.Common.Configuration.Hid
case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig);
break;
- case InputBackendType.GamepadSDL2:
+ case InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig);
break;
default:
diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index 89f0336dc..eb0821db6 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -35,6 +35,7 @@ namespace Ryujinx.Common.Logging
ServiceBsd,
ServiceBtm,
ServiceCaps,
+ ServiceEctx,
ServiceFatal,
ServiceFriend,
ServiceFs,
diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
index a29def8e8..9b2bf7015 100644
--- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Cpu.Jit
_functionTable = AddressTable.CreateForArm(for64Bit, memory.Type);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
- if (memory.Type.IsHostMappedOrTracked())
+ if (memory.Type.IsHostMappedOrTracked)
{
NativeSignalHandler.InitializeSignalHandler();
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs
index 18b1416ea..e4ebf1309 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs
@@ -26,7 +26,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
return;
}
- if (_operand.Type.IsInteger())
+ if (_operand.Type.IsInteger)
{
_registerAllocator.FreeTempGprRegister(_operand.AsInt32());
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
index 643d1e20d..ff11cbd41 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
@@ -381,7 +381,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
if (currentCond != ArmCondition.Al)
{
instructionPointer = context.CodeWriter.InstructionPointer;
- context.Arm64Assembler.B(currentCond.Invert(), 0);
+ context.Arm64Assembler.B(currentCond.Inverse, 0);
}
}
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs
index 8190bd7ea..943bc6897 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs
@@ -104,7 +104,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
if (invert)
{
- conditions[i++] = ((ArmCondition)firstCond).Invert();
+ conditions[i++] = ((ArmCondition)firstCond).Inverse;
}
else
{
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs
index d8caee6e7..761335c47 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs
@@ -1129,7 +1129,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
// We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit
// and can never reach out of the guest address space.
- if (mmType.IsHostTracked())
+ if (mmType.IsHostTracked)
{
int tempRegister = regAlloc.AllocateTempGprRegister();
@@ -1141,7 +1141,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
regAlloc.FreeTempGprRegister(tempRegister);
}
- else if (mmType.IsHostMapped())
+ else if (mmType.IsHostMapped)
{
asm.Add(destination64, basePointer, guestAddress);
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs
index 405126357..43ad1b3b6 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs
@@ -132,7 +132,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
InstName lastInstructionName = Instructions[^1].Name;
- return lastInstructionName.IsCall() || lastInstructionName.IsException();
+ return lastInstructionName.IsCall || lastInstructionName.IsException;
}
}
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs
index af3b872a5..8a937134f 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs
@@ -1042,126 +1042,39 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
static class InstNameExtensions
{
- public static bool IsCall(this InstName name)
+ extension(InstName name)
{
- return name is InstName.Bl or InstName.Blr;
- }
+ public bool IsCall => name is InstName.Bl or InstName.Blr;
- public static bool IsControlFlowOrException(this InstName name)
- {
- switch (name)
+ public bool IsControlFlowOrException => name is
+ InstName.BUncond or InstName.BCond or InstName.Bl or InstName.Blr or InstName.Br or InstName.Brk
+ or InstName.Cbnz or InstName.Cbz or InstName.Ret or InstName.Tbnz or InstName.Tbz or InstName.Svc
+ or InstName.UdfPermUndef;
+
+ public bool IsException => name is InstName.Brk or InstName.Svc or InstName.UdfPermUndef;
+
+ public bool IsSystem => name switch
{
- case InstName.BUncond:
- case InstName.BCond:
- case InstName.Bl:
- case InstName.Blr:
- case InstName.Br:
- case InstName.Brk:
- case InstName.Cbnz:
- case InstName.Cbz:
- case InstName.Ret:
- case InstName.Tbnz:
- case InstName.Tbz:
- case InstName.Svc:
- case InstName.UdfPermUndef:
- return true;
- }
+ InstName.Mrs or InstName.MsrImm or InstName.MsrReg => true,
+ _ => name.IsException
+ };
- return false;
- }
+ public bool IsSystemOrCall => name.IsCall || name is
+ InstName.Svc or InstName.Mrs or InstName.MsrImm or InstName.MsrReg
+ or InstName.Sysl;
- public static bool IsException(this InstName name)
- {
- switch (name)
- {
- case InstName.Brk:
- case InstName.Svc:
- case InstName.UdfPermUndef:
- return true;
- }
+ public bool IsPrivileged => name is
+ InstName.Dcps1 or InstName.Dcps2 or InstName.Dcps3 or InstName.Drps or InstName.Eret or InstName.Ereta
+ or InstName.Hvc or InstName.MsrImm or InstName.Smc;
- return false;
- }
+ public bool IsPartialRegisterUpdateMemory => name is
+ InstName.Ld1AdvsimdSnglAsNoPostIndex or InstName.Ld1AdvsimdSnglAsPostIndex
+ or InstName.Ld2AdvsimdSnglAsNoPostIndex or InstName.Ld2AdvsimdSnglAsPostIndex
+ or InstName.Ld3AdvsimdSnglAsNoPostIndex or InstName.Ld3AdvsimdSnglAsPostIndex
+ or InstName.Ld4AdvsimdSnglAsNoPostIndex or InstName.Ld4AdvsimdSnglAsPostIndex;
- public static bool IsSystem(this InstName name)
- {
- switch (name)
- {
- case InstName.Mrs:
- case InstName.MsrImm:
- case InstName.MsrReg:
- return true;
- }
-
- return name.IsException();
- }
-
- public static bool IsSystemOrCall(this InstName name)
- {
- switch (name)
- {
- case InstName.Bl:
- case InstName.Blr:
- case InstName.Svc:
- case InstName.Mrs:
- case InstName.MsrImm:
- case InstName.MsrReg:
- case InstName.Sysl:
- return true;
- }
-
- return false;
- }
-
- public static bool IsPrivileged(this InstName name)
- {
- switch (name)
- {
- case InstName.Dcps1:
- case InstName.Dcps2:
- case InstName.Dcps3:
- case InstName.Drps:
- case InstName.Eret:
- case InstName.Ereta:
- case InstName.Hvc:
- case InstName.MsrImm:
- case InstName.Smc:
- return true;
- }
-
- return false;
- }
-
- public static bool IsPartialRegisterUpdateMemory(this InstName name)
- {
- switch (name)
- {
- case InstName.Ld1AdvsimdSnglAsNoPostIndex:
- case InstName.Ld1AdvsimdSnglAsPostIndex:
- case InstName.Ld2AdvsimdSnglAsNoPostIndex:
- case InstName.Ld2AdvsimdSnglAsPostIndex:
- case InstName.Ld3AdvsimdSnglAsNoPostIndex:
- case InstName.Ld3AdvsimdSnglAsPostIndex:
- case InstName.Ld4AdvsimdSnglAsNoPostIndex:
- case InstName.Ld4AdvsimdSnglAsPostIndex:
- return true;
- }
-
- return false;
- }
-
- public static bool IsPrefetchMemory(this InstName name)
- {
- switch (name)
- {
- case InstName.PrfmImm:
- case InstName.PrfmLit:
- case InstName.PrfmReg:
- case InstName.Prfum:
- return true;
- }
-
- return false;
+ public bool IsPrefetchMemory => name is
+ InstName.PrfmImm or InstName.PrfmLit or InstName.PrfmReg or InstName.Prfum;
}
}
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs
index 1c6eab0de..425575df6 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs
@@ -150,7 +150,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
public static int CalculateMaxTemps(MemoryManagerType mmType)
{
- return mmType.IsHostMapped() ? 1 : 2;
+ return mmType.IsHostMapped ? 1 : 2;
}
public static int CalculateMaxTempsInclFixed(MemoryManagerType mmType)
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs
index 191e03e7b..c0ee93518 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs
@@ -247,7 +247,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
}
}
- if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory())
+ if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory)
{
if (flags.HasFlag(InstFlags.Rt))
{
@@ -281,7 +281,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
gprMask |= MaskFromIndex(ExtractRd(flags, encoding));
}
- if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory())
+ if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory)
{
if (flags.HasFlag(InstFlags.Rt))
{
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs
index e45d74f9b..ac389f4ce 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs
@@ -364,7 +364,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{
InstEmitMemory.RewriteSysInstruction(memoryManager.AddressSpaceBits, memoryManager.Type, writer, regAlloc, encoding);
}
- else if (instInfo.Name.IsSystem())
+ else if (instInfo.Name.IsSystem)
{
bool needsContextStoreLoad = InstEmitSystem.NeedsContextStoreLoad(instInfo.Name);
@@ -405,7 +405,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
lastInstructionEncoding = RegisterUtils.RemapRegisters(regAlloc, lastInstructionFlags, lastInstructionEncoding);
- if (lastInstructionName.IsCall())
+ if (lastInstructionName.IsCall)
{
context.StoreToContextBeforeCall(blockIndex, pc + 4UL);
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs
index ad221c7aa..931c7381e 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs
@@ -257,7 +257,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
(name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features);
- if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding)))
+ if (name.IsPrivileged || (name == InstName.Sys && IsPrivilegedSys(encoding)))
{
name = InstName.UdfPermUndef;
flags = InstFlags.None;
@@ -267,7 +267,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
(uint instGprReadMask, uint instFpSimdReadMask) = RegisterUtils.PopulateReadMasks(name, flags, encoding);
(uint instGprWriteMask, uint instFpSimdWriteMask) = RegisterUtils.PopulateWriteMasks(name, flags, encoding);
- if (name.IsCall())
+ if (name.IsCall)
{
instGprWriteMask |= 1u << RegisterUtils.LrIndex;
}
@@ -310,12 +310,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
fpSimdUseMask |= instFpSimdReadMask | instFpSimdWriteMask;
pStateUseMask |= instPStateReadMask | instPStateWriteMask;
- if (name.IsSystemOrCall() && !hasHostCall)
+ if (name.IsSystemOrCall && !hasHostCall)
{
- hasHostCall = name.IsCall() || InstEmitSystem.NeedsCall(encoding);
+ hasHostCall = name.IsCall || InstEmitSystem.NeedsCall(encoding);
}
- isControlFlow = name.IsControlFlowOrException();
+ isControlFlow = name.IsControlFlowOrException;
RegisterUse registerUse = new(
instGprReadMask,
@@ -339,7 +339,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
useMask = new(gprUseMask, fpSimdUseMask, pStateUseMask);
- return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd);
+ return new(startAddress, address, insts, !isTruncated && !name.IsException, isTruncated, isLoopEnd);
}
private static bool IsPrivilegedSys(uint encoding)
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs
index 790a7de95..e9d6c5c86 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs
@@ -55,7 +55,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
ulong pc,
uint encoding)
{
- if (name.IsPrefetchMemory() && mmType == MemoryManagerType.HostTrackedUnsafe)
+ if (name.IsPrefetchMemory && mmType == MemoryManagerType.HostTrackedUnsafe)
{
// Prefetch to invalid addresses do not cause faults, so for memory manager
// types where we need to access the page table before doing the prefetch,
@@ -544,7 +544,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{
Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64);
- if (mmType.IsHostTracked())
+ if (mmType.IsHostTracked)
{
int tempRegister = regAlloc.AllocateTempGprRegister();
@@ -562,7 +562,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
regAlloc.FreeTempGprRegister(tempRegister);
}
- else if (mmType.IsHostMapped())
+ else if (mmType.IsHostMapped)
{
if (mmType == MemoryManagerType.HostMapped)
{
diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs
index caa2e593b..9293b497a 100644
--- a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs
+++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs
@@ -22,9 +22,9 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
static class ArmConditionExtensions
{
- public static ArmCondition Invert(this ArmCondition condition)
+ extension(ArmCondition condition)
{
- return (ArmCondition)((int)condition ^ 1);
+ public ArmCondition Inverse => (ArmCondition)((int)condition ^ 1);
}
}
}
diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs
index 3eeda20bf..f6eb226ec 100644
--- a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs
+++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs
@@ -673,7 +673,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
public readonly void Mov(Operand rd, Operand rn)
{
- Debug.Assert(rd.Type.IsInteger());
+ Debug.Assert(rd.Type.IsInteger);
Orr(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), rn);
}
@@ -4544,7 +4544,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
uint instruction;
int scale;
- if (type.IsInteger())
+ if (type.IsInteger)
{
instruction = intInst;
@@ -4580,7 +4580,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
{
uint instruction;
- if (type.IsInteger())
+ if (type.IsInteger)
{
instruction = intInst;
@@ -4610,7 +4610,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
{
uint instruction;
- if (type.IsInteger())
+ if (type.IsInteger)
{
instruction = intInst;
diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs
index b4b8bb524..527448e1c 100644
--- a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs
+++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs
@@ -34,7 +34,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
int gprCalleeSavedRegsCount = BitOperations.PopCount(_gprMask);
int fpSimdCalleeSavedRegsCount = BitOperations.PopCount(_fpSimdMask);
- return (_hasCall ? 16 : 0) + Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes());
+ return (_hasCall ? 16 : 0) + Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.ByteSize);
}
public void WritePrologue(ref Assembler asm)
@@ -46,7 +46,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
int fpSimdCalleeSavedRegsCount = BitOperations.PopCount(fpSimdMask);
int reservedStackSize = Align16(_reservedStackSize);
- int calleeSaveRegionSize = Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes()) + reservedStackSize;
+ int calleeSaveRegionSize = Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.ByteSize) + reservedStackSize;
int offset = 0;
WritePrologueCalleeSavesPreIndexed(ref asm, ref gprMask, ref offset, calleeSaveRegionSize, OperandType.I64);
@@ -103,7 +103,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
asm.StrRiUn(Register(reg, type), Register(Assembler.SpRegister), 0);
}
- offset += type.GetSizeInBytes();
+ offset += type.ByteSize;
}
while (mask != 0)
@@ -130,7 +130,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
asm.StpRiUn(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), 0);
}
- offset += type.GetSizeInBytes() * 2;
+ offset += type.ByteSize * 2;
}
}
@@ -144,7 +144,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
bool misalignedVector = _fpSimdType == OperandType.V128 && (gprCalleeSavedRegsCount & 1) != 0;
- int offset = gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes();
+ int offset = gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.ByteSize;
if (misalignedVector)
{
@@ -197,7 +197,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
mask &= ~(1u << reg2);
- offset -= type.GetSizeInBytes() * 2;
+ offset -= type.ByteSize * 2;
if (offset != 0)
{
@@ -215,7 +215,7 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64
}
else
{
- offset -= type.GetSizeInBytes();
+ offset -= type.ByteSize;
if (offset != 0)
{
diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs
index cd36c6781..ddb33167e 100644
--- a/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs
+++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs
@@ -14,14 +14,11 @@ namespace Ryujinx.Cpu.LightningJit.CodeGen
static class OperandTypeExtensions
{
- public static bool IsInteger(this OperandType type)
+ extension(OperandType type)
{
- return type is OperandType.I32 or OperandType.I64;
- }
+ public bool IsInteger => type is OperandType.I32 or OperandType.I64;
- public static int GetSizeInBytes(this OperandType type)
- {
- return type switch
+ public int ByteSize => type switch
{
OperandType.FP32 => 4,
OperandType.FP64 => 8,
diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs
index f3ae0a9c5..1ee6993d2 100644
--- a/src/Ryujinx.Cpu/LightningJit/Translator.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs
@@ -48,7 +48,7 @@ namespace Ryujinx.Cpu.LightningJit
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
- if (memory.Type.IsHostMappedOrTracked())
+ if (memory.Type.IsHostMappedOrTracked)
{
NativeSignalHandler.InitializeSignalHandler();
}
diff --git a/src/Ryujinx.Graphics.GAL/BlendFactor.cs b/src/Ryujinx.Graphics.GAL/BlendFactor.cs
index 1dba229d6..99f946c3c 100644
--- a/src/Ryujinx.Graphics.GAL/BlendFactor.cs
+++ b/src/Ryujinx.Graphics.GAL/BlendFactor.cs
@@ -41,22 +41,12 @@ namespace Ryujinx.Graphics.GAL
public static class BlendFactorExtensions
{
- public static bool IsDualSource(this BlendFactor factor)
+ extension(BlendFactor factor)
{
- switch (factor)
- {
- case BlendFactor.Src1Color:
- case BlendFactor.Src1ColorGl:
- case BlendFactor.Src1Alpha:
- case BlendFactor.Src1AlphaGl:
- case BlendFactor.OneMinusSrc1Color:
- case BlendFactor.OneMinusSrc1ColorGl:
- case BlendFactor.OneMinusSrc1Alpha:
- case BlendFactor.OneMinusSrc1AlphaGl:
- return true;
- default:
- return false;
- }
+ public bool IsDualSource => factor is
+ BlendFactor.Src1Color or BlendFactor.Src1ColorGl or BlendFactor.Src1Alpha or BlendFactor.Src1AlphaGl
+ or BlendFactor.OneMinusSrc1Color or BlendFactor.OneMinusSrc1ColorGl or BlendFactor.OneMinusSrc1Alpha
+ or BlendFactor.OneMinusSrc1AlphaGl;
}
}
}
diff --git a/src/Ryujinx.Graphics.GAL/BufferHandle.cs b/src/Ryujinx.Graphics.GAL/BufferHandle.cs
index 7994e4eea..b8550a848 100644
--- a/src/Ryujinx.Graphics.GAL/BufferHandle.cs
+++ b/src/Ryujinx.Graphics.GAL/BufferHandle.cs
@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.GAL
@@ -10,5 +11,7 @@ namespace Ryujinx.Graphics.GAL
public static BufferHandle Null => new(0);
private BufferHandle(ulong value) => _value = value;
+
+ public static implicit operator int(BufferHandle handle) => (int)Unsafe.As(ref handle);
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs
index 25446f978..277276fe3 100644
--- a/src/Ryujinx.Graphics.GAL/Format.cs
+++ b/src/Ryujinx.Graphics.GAL/Format.cs
@@ -159,589 +159,191 @@ namespace Ryujinx.Graphics.GAL
///
public const int MaxBufferFormatScalarSize = 4;
- ///
- /// Gets the byte size for a single component of this format, or its packed size.
- ///
- /// Texture format
- /// Byte size for a single component, or packed size
- public static int GetScalarSize(this Format format)
+ extension(Format fmt)
{
- switch (format)
+ ///
+ /// Gets the byte size for a single component of this format, or its packed size.
+ ///
+ public int ScalarSize => fmt switch
{
- case Format.R8Unorm:
- case Format.R8Snorm:
- case Format.R8Uint:
- case Format.R8Sint:
- case Format.R8G8Unorm:
- case Format.R8G8Snorm:
- case Format.R8G8Uint:
- case Format.R8G8Sint:
- case Format.R8G8B8Unorm:
- case Format.R8G8B8Snorm:
- case Format.R8G8B8Uint:
- case Format.R8G8B8Sint:
- case Format.R8G8B8A8Unorm:
- case Format.R8G8B8A8Snorm:
- case Format.R8G8B8A8Uint:
- case Format.R8G8B8A8Sint:
- case Format.R8G8B8A8Srgb:
- case Format.R4G4Unorm:
- case Format.R8Uscaled:
- case Format.R8Sscaled:
- case Format.R8G8Uscaled:
- case Format.R8G8Sscaled:
- case Format.R8G8B8Uscaled:
- case Format.R8G8B8Sscaled:
- case Format.R8G8B8A8Uscaled:
- case Format.R8G8B8A8Sscaled:
- case Format.B8G8R8A8Unorm:
- case Format.B8G8R8A8Srgb:
- return 1;
+ Format.R8Unorm or Format.R8Snorm or Format.R8Uint or Format.R8Sint or Format.R8G8Unorm
+ or Format.R8G8Snorm or Format.R8G8Uint or Format.R8G8Sint or Format.R8G8B8Unorm
+ or Format.R8G8B8Snorm or Format.R8G8B8Uint or Format.R8G8B8Sint or Format.R8G8B8A8Unorm
+ or Format.R8G8B8A8Snorm or Format.R8G8B8A8Uint or Format.R8G8B8A8Sint or Format.R8G8B8A8Srgb
+ or Format.R4G4Unorm or Format.R8Uscaled or Format.R8Sscaled or Format.R8G8Uscaled
+ or Format.R8G8Sscaled or Format.R8G8B8Uscaled or Format.R8G8B8Sscaled or Format.R8G8B8A8Uscaled
+ or Format.R8G8B8A8Sscaled or Format.B8G8R8A8Unorm or Format.B8G8R8A8Srgb => 1,
+ Format.R16Float or Format.R16Unorm or Format.R16Snorm or Format.R16Uint or Format.R16Sint
+ or Format.R16G16Float or Format.R16G16Unorm or Format.R16G16Snorm or Format.R16G16Uint
+ or Format.R16G16Sint or Format.R16G16B16Float or Format.R16G16B16Unorm or Format.R16G16B16Snorm
+ or Format.R16G16B16Uint or Format.R16G16B16Sint or Format.R16G16B16A16Float
+ or Format.R16G16B16A16Unorm or Format.R16G16B16A16Snorm or Format.R16G16B16A16Uint
+ or Format.R16G16B16A16Sint or Format.R4G4B4A4Unorm or Format.R5G5B5X1Unorm or Format.R5G5B5A1Unorm
+ or Format.R5G6B5Unorm or Format.R16Uscaled or Format.R16Sscaled or Format.R16G16Uscaled
+ or Format.R16G16Sscaled or Format.R16G16B16Uscaled or Format.R16G16B16Sscaled
+ or Format.R16G16B16A16Uscaled or Format.R16G16B16A16Sscaled or Format.B5G6R5Unorm
+ or Format.B5G5R5A1Unorm or Format.A1B5G5R5Unorm => 2,
+ Format.R32Float or Format.R32Uint or Format.R32Sint or Format.R32G32Float or Format.R32G32Uint
+ or Format.R32G32Sint or Format.R32G32B32Float or Format.R32G32B32Uint or Format.R32G32B32Sint
+ or Format.R32G32B32A32Float or Format.R32G32B32A32Uint or Format.R32G32B32A32Sint
+ or Format.R10G10B10A2Unorm or Format.R10G10B10A2Uint or Format.R11G11B10Float
+ or Format.R9G9B9E5Float or Format.R32Uscaled or Format.R32Sscaled or Format.R32G32Uscaled
+ or Format.R32G32Sscaled or Format.R32G32B32Uscaled or Format.R32G32B32Sscaled
+ or Format.R32G32B32A32Uscaled or Format.R32G32B32A32Sscaled or Format.R10G10B10A2Snorm
+ or Format.R10G10B10A2Sint or Format.R10G10B10A2Uscaled or Format.R10G10B10A2Sscaled
+ or Format.B10G10R10A2Unorm => 4,
+ Format.S8Uint => 1,
+ Format.D16Unorm => 2,
+ Format.S8UintD24Unorm or Format.X8UintD24Unorm or Format.D32Float or Format.D24UnormS8Uint => 4,
+ Format.D32FloatS8Uint => 8,
+ Format.Bc1RgbaUnorm or Format.Bc1RgbaSrgb => 8,
+ Format.Bc2Unorm or Format.Bc3Unorm or Format.Bc2Srgb or Format.Bc3Srgb or Format.Bc4Unorm
+ or Format.Bc4Snorm or Format.Bc5Unorm or Format.Bc5Snorm or Format.Bc7Unorm or Format.Bc7Srgb
+ or Format.Bc6HSfloat or Format.Bc6HUfloat => 16,
+ Format.Etc2RgbUnorm or Format.Etc2RgbPtaUnorm or Format.Etc2RgbSrgb or Format.Etc2RgbPtaSrgb => 8,
+ Format.Etc2RgbaUnorm or Format.Etc2RgbaSrgb => 16,
+ Format.Astc4x4Unorm or Format.Astc5x4Unorm or Format.Astc5x5Unorm or Format.Astc6x5Unorm
+ or Format.Astc6x6Unorm or Format.Astc8x5Unorm or Format.Astc8x6Unorm or Format.Astc8x8Unorm
+ or Format.Astc10x5Unorm or Format.Astc10x6Unorm or Format.Astc10x8Unorm or Format.Astc10x10Unorm
+ or Format.Astc12x10Unorm or Format.Astc12x12Unorm or Format.Astc4x4Srgb or Format.Astc5x4Srgb
+ or Format.Astc5x5Srgb or Format.Astc6x5Srgb or Format.Astc6x6Srgb or Format.Astc8x5Srgb
+ or Format.Astc8x6Srgb or Format.Astc8x8Srgb or Format.Astc10x5Srgb or Format.Astc10x6Srgb
+ or Format.Astc10x8Srgb or Format.Astc10x10Srgb or Format.Astc12x10Srgb
+ or Format.Astc12x12Srgb => 16,
+ _ => 1
+ };
- case Format.R16Float:
- case Format.R16Unorm:
- case Format.R16Snorm:
- case Format.R16Uint:
- case Format.R16Sint:
- case Format.R16G16Float:
- case Format.R16G16Unorm:
- case Format.R16G16Snorm:
- case Format.R16G16Uint:
- case Format.R16G16Sint:
- case Format.R16G16B16Float:
- case Format.R16G16B16Unorm:
- case Format.R16G16B16Snorm:
- case Format.R16G16B16Uint:
- case Format.R16G16B16Sint:
- case Format.R16G16B16A16Float:
- case Format.R16G16B16A16Unorm:
- case Format.R16G16B16A16Snorm:
- case Format.R16G16B16A16Uint:
- case Format.R16G16B16A16Sint:
- case Format.R4G4B4A4Unorm:
- case Format.R5G5B5X1Unorm:
- case Format.R5G5B5A1Unorm:
- case Format.R5G6B5Unorm:
- case Format.R16Uscaled:
- case Format.R16Sscaled:
- case Format.R16G16Uscaled:
- case Format.R16G16Sscaled:
- case Format.R16G16B16Uscaled:
- case Format.R16G16B16Sscaled:
- case Format.R16G16B16A16Uscaled:
- case Format.R16G16B16A16Sscaled:
- case Format.B5G6R5Unorm:
- case Format.B5G5R5A1Unorm:
- case Format.A1B5G5R5Unorm:
- return 2;
+ ///
+ /// Checks if the texture format is a depth or depth-stencil format.
+ ///
+ public bool HasDepth => fmt is
+ Format.D16Unorm or Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm
+ or Format.D32Float or Format.D32FloatS8Uint;
- case Format.R32Float:
- case Format.R32Uint:
- case Format.R32Sint:
- case Format.R32G32Float:
- case Format.R32G32Uint:
- case Format.R32G32Sint:
- case Format.R32G32B32Float:
- case Format.R32G32B32Uint:
- case Format.R32G32B32Sint:
- case Format.R32G32B32A32Float:
- case Format.R32G32B32A32Uint:
- case Format.R32G32B32A32Sint:
- case Format.R10G10B10A2Unorm:
- case Format.R10G10B10A2Uint:
- case Format.R11G11B10Float:
- case Format.R9G9B9E5Float:
- case Format.R32Uscaled:
- case Format.R32Sscaled:
- case Format.R32G32Uscaled:
- case Format.R32G32Sscaled:
- case Format.R32G32B32Uscaled:
- case Format.R32G32B32Sscaled:
- case Format.R32G32B32A32Uscaled:
- case Format.R32G32B32A32Sscaled:
- case Format.R10G10B10A2Snorm:
- case Format.R10G10B10A2Sint:
- case Format.R10G10B10A2Uscaled:
- case Format.R10G10B10A2Sscaled:
- case Format.B10G10R10A2Unorm:
- return 4;
+ ///
+ /// Checks if the texture format is a stencil or depth-stencil format.
+ ///
+ public bool HasStencil => fmt is
+ Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.D32FloatS8Uint or Format.S8Uint;
- case Format.S8Uint:
- return 1;
- case Format.D16Unorm:
- return 2;
- case Format.S8UintD24Unorm:
- case Format.X8UintD24Unorm:
- case Format.D32Float:
- case Format.D24UnormS8Uint:
- return 4;
- case Format.D32FloatS8Uint:
- return 8;
+ ///
+ /// Checks if the texture format is valid to use as image format.
+ ///
+ public bool IsImageCompatible => fmt is
+ Format.R8Unorm or Format.R8Snorm or Format.R8Uint or Format.R8Sint or Format.R16Float or Format.R16Unorm
+ or Format.R16Snorm or Format.R16Uint or Format.R16Sint or Format.R32Float or Format.R32Uint
+ or Format.R32Sint or Format.R8G8Unorm or Format.R8G8Snorm or Format.R8G8Uint or Format.R8G8Sint
+ or Format.R16G16Float or Format.R16G16Unorm or Format.R16G16Snorm or Format.R16G16Uint
+ or Format.R16G16Sint or Format.R32G32Float or Format.R32G32Uint or Format.R32G32Sint
+ or Format.R8G8B8A8Unorm or Format.R8G8B8A8Snorm or Format.R8G8B8A8Uint or Format.R8G8B8A8Sint
+ or Format.R16G16B16A16Float or Format.R16G16B16A16Unorm or Format.R16G16B16A16Snorm
+ or Format.R16G16B16A16Uint or Format.R16G16B16A16Sint or Format.R32G32B32A32Float
+ or Format.R32G32B32A32Uint or Format.R32G32B32A32Sint or Format.R10G10B10A2Unorm
+ or Format.R10G10B10A2Uint or Format.R11G11B10Float or Format.B8G8R8A8Unorm;
- case Format.Bc1RgbaUnorm:
- case Format.Bc1RgbaSrgb:
- return 8;
+ ///
+ /// Checks if the texture format is valid to use as render target color format.
+ ///
+ public bool IsRtColorCompatible => fmt is
+ Format.R32G32B32A32Float or Format.R32G32B32A32Sint or Format.R32G32B32A32Uint
+ or Format.R16G16B16A16Unorm or Format.R16G16B16A16Snorm or Format.R16G16B16A16Sint
+ or Format.R16G16B16A16Uint or Format.R16G16B16A16Float or Format.R32G32Float or Format.R32G32Sint
+ or Format.R32G32Uint or Format.B8G8R8A8Unorm or Format.B8G8R8A8Srgb or Format.B10G10R10A2Unorm
+ or Format.R10G10B10A2Unorm or Format.R10G10B10A2Uint or Format.R8G8B8A8Unorm or Format.R8G8B8A8Srgb
+ or Format.R8G8B8A8Snorm or Format.R8G8B8A8Sint or Format.R8G8B8A8Uint or Format.R16G16Unorm
+ or Format.R16G16Snorm or Format.R16G16Sint or Format.R16G16Uint or Format.R16G16Float
+ or Format.R11G11B10Float or Format.R32Sint or Format.R32Uint or Format.R32Float
+ or Format.B5G6R5Unorm or Format.B5G5R5A1Unorm or Format.R8G8Unorm or Format.R8G8Snorm
+ or Format.R8G8Sint or Format.R8G8Uint or Format.R16Unorm or Format.R16Snorm or Format.R16Sint
+ or Format.R16Uint or Format.R16Float or Format.R8Unorm or Format.R8Snorm or Format.R8Sint
+ or Format.R8Uint;
- case Format.Bc2Unorm:
- case Format.Bc3Unorm:
- case Format.Bc2Srgb:
- case Format.Bc3Srgb:
- case Format.Bc4Unorm:
- case Format.Bc4Snorm:
- case Format.Bc5Unorm:
- case Format.Bc5Snorm:
- case Format.Bc7Unorm:
- case Format.Bc7Srgb:
- case Format.Bc6HSfloat:
- case Format.Bc6HUfloat:
- return 16;
+ ///
+ /// Checks if the texture format is 16 bit packed.
+ ///
+ public bool Is16BitPacked => fmt is
+ Format.B5G6R5Unorm or Format.B5G5R5A1Unorm or Format.R5G5B5X1Unorm or Format.R5G5B5A1Unorm
+ or Format.R5G6B5Unorm or Format.R4G4B4A4Unorm;
- case Format.Etc2RgbUnorm:
- case Format.Etc2RgbPtaUnorm:
- case Format.Etc2RgbSrgb:
- case Format.Etc2RgbPtaSrgb:
- return 8;
+ ///
+ /// Checks if the texture format is an ETC2 format.
+ ///
+ public bool IsEtc2 => fmt is
+ Format.Etc2RgbaSrgb or Format.Etc2RgbaUnorm or Format.Etc2RgbPtaSrgb
+ or Format.Etc2RgbPtaUnorm or Format.Etc2RgbSrgb or Format.Etc2RgbUnorm;
- case Format.Etc2RgbaUnorm:
- case Format.Etc2RgbaSrgb:
- return 16;
+ ///
+ /// Checks if the texture format is a BGR format.
+ ///
+ public bool IsBgr => fmt is
+ Format.B5G6R5Unorm or Format.B5G5R5A1Unorm or Format.B8G8R8A8Unorm or Format.B8G8R8A8Srgb
+ or Format.B10G10R10A2Unorm;
- case Format.Astc4x4Unorm:
- case Format.Astc5x4Unorm:
- case Format.Astc5x5Unorm:
- case Format.Astc6x5Unorm:
- case Format.Astc6x6Unorm:
- case Format.Astc8x5Unorm:
- case Format.Astc8x6Unorm:
- case Format.Astc8x8Unorm:
- case Format.Astc10x5Unorm:
- case Format.Astc10x6Unorm:
- case Format.Astc10x8Unorm:
- case Format.Astc10x10Unorm:
- case Format.Astc12x10Unorm:
- case Format.Astc12x12Unorm:
- case Format.Astc4x4Srgb:
- case Format.Astc5x4Srgb:
- case Format.Astc5x5Srgb:
- case Format.Astc6x5Srgb:
- case Format.Astc6x6Srgb:
- case Format.Astc8x5Srgb:
- case Format.Astc8x6Srgb:
- case Format.Astc8x8Srgb:
- case Format.Astc10x5Srgb:
- case Format.Astc10x6Srgb:
- case Format.Astc10x8Srgb:
- case Format.Astc10x10Srgb:
- case Format.Astc12x10Srgb:
- case Format.Astc12x12Srgb:
- return 16;
- }
+ ///
+ /// Checks if the texture format is a depth, stencil or depth-stencil format.
+ ///
+ public bool IsDepthOrStencil => fmt is
+ Format.D16Unorm or Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm
+ or Format.D32Float or Format.D32FloatS8Uint or Format.S8Uint;
- return 1;
- }
+ ///
+ /// Checks if the texture format is a float or sRGB color format.
+ ///
+ ///
+ /// Does not include normalized, compressed or depth formats.
+ /// Float and sRGB formats do not participate in logical operations.
+ ///
+ public bool IsFloatOrSrgb => fmt is
+ Format.R8G8B8A8Srgb or Format.B8G8R8A8Srgb or Format.R16Float or Format.R16G16Float
+ or Format.R16G16B16Float or Format.R16G16B16A16Float or Format.R32Float or Format.R32G32Float
+ or Format.R32G32B32Float or Format.R32G32B32A32Float or Format.R11G11B10Float
+ or Format.R9G9B9E5Float;
+
+ ///
+ /// Checks if the texture format is an ASTC Unorm format.
+ ///
+ public bool IsAstcUnorm => fmt is
+ Format.Astc4x4Unorm or Format.Astc5x4Unorm or Format.Astc5x5Unorm or Format.Astc6x5Unorm
+ or Format.Astc6x6Unorm or Format.Astc8x5Unorm or Format.Astc8x6Unorm or Format.Astc8x8Unorm
+ or Format.Astc10x5Unorm or Format.Astc10x6Unorm or Format.Astc10x8Unorm or Format.Astc10x10Unorm
+ or Format.Astc12x10Unorm or Format.Astc12x12Unorm;
- ///
- /// Checks if the texture format is a depth or depth-stencil format.
- ///
- /// Texture format
- /// True if the format is a depth or depth-stencil format, false otherwise
- public static bool HasDepth(this Format format)
- {
- switch (format)
- {
- case Format.D16Unorm:
- case Format.D24UnormS8Uint:
- case Format.S8UintD24Unorm:
- case Format.X8UintD24Unorm:
- case Format.D32Float:
- case Format.D32FloatS8Uint:
- return true;
- }
+ ///
+ /// Checks if the texture format is an ASTC SRGB format.
+ ///
+ public bool IsAstcSrgb => fmt is
+ Format.Astc4x4Srgb or Format.Astc5x4Srgb or Format.Astc5x5Srgb or Format.Astc6x5Srgb
+ or Format.Astc6x6Srgb or Format.Astc8x5Srgb or Format.Astc8x6Srgb or Format.Astc8x8Srgb
+ or Format.Astc10x5Srgb or Format.Astc10x6Srgb or Format.Astc10x8Srgb or Format.Astc10x10Srgb
+ or Format.Astc12x10Srgb or Format.Astc12x12Srgb;
- return false;
- }
+ ///
+ /// Checks if the texture format is an ASTC format.
+ ///
+ public bool IsAstc => fmt.IsAstcUnorm || fmt.IsAstcSrgb;
- ///
- /// Checks if the texture format is a stencil or depth-stencil format.
- ///
- /// Texture format
- /// True if the format is a stencil or depth-stencil format, false otherwise
- public static bool HasStencil(this Format format)
- {
- switch (format)
- {
- case Format.D24UnormS8Uint:
- case Format.S8UintD24Unorm:
- case Format.D32FloatS8Uint:
- case Format.S8Uint:
- return true;
- }
+ ///
+ /// Checks if the texture format is an unsigned integer color format.
+ ///
+ public bool IsUnsignedInt => fmt is
+ Format.R8Uint or Format.R16Uint or Format.R32Uint or Format.R8G8Uint or Format.R16G16Uint
+ or Format.R32G32Uint or Format.R8G8B8Uint or Format.R16G16B16Uint or Format.R32G32B32Uint
+ or Format.R8G8B8A8Uint or Format.R16G16B16A16Uint or Format.R32G32B32A32Uint
+ or Format.R10G10B10A2Uint;
- return false;
- }
+ ///
+ /// Checks if the texture format is a signed integer color format.
+ ///
+ public bool IsSignedInt => fmt is
+ Format.R8Sint or Format.R16Sint or Format.R32Sint or Format.R8G8Sint or Format.R16G16Sint
+ or Format.R32G32Sint or Format.R8G8B8Sint or Format.R16G16B16Sint or Format.R32G32B32Sint
+ or Format.R8G8B8A8Sint or Format.R16G16B16A16Sint or Format.R32G32B32A32Sint
+ or Format.R10G10B10A2Sint;
- ///
- /// Checks if the texture format is valid to use as image format.
- ///
- /// Texture format
- /// True if the texture can be used as image, false otherwise
- public static bool IsImageCompatible(this Format format)
- {
- switch (format)
- {
- case Format.R8Unorm:
- case Format.R8Snorm:
- case Format.R8Uint:
- case Format.R8Sint:
- case Format.R16Float:
- case Format.R16Unorm:
- case Format.R16Snorm:
- case Format.R16Uint:
- case Format.R16Sint:
- case Format.R32Float:
- case Format.R32Uint:
- case Format.R32Sint:
- case Format.R8G8Unorm:
- case Format.R8G8Snorm:
- case Format.R8G8Uint:
- case Format.R8G8Sint:
- case Format.R16G16Float:
- case Format.R16G16Unorm:
- case Format.R16G16Snorm:
- case Format.R16G16Uint:
- case Format.R16G16Sint:
- case Format.R32G32Float:
- case Format.R32G32Uint:
- case Format.R32G32Sint:
- case Format.R8G8B8A8Unorm:
- case Format.R8G8B8A8Snorm:
- case Format.R8G8B8A8Uint:
- case Format.R8G8B8A8Sint:
- case Format.R16G16B16A16Float:
- case Format.R16G16B16A16Unorm:
- case Format.R16G16B16A16Snorm:
- case Format.R16G16B16A16Uint:
- case Format.R16G16B16A16Sint:
- case Format.R32G32B32A32Float:
- case Format.R32G32B32A32Uint:
- case Format.R32G32B32A32Sint:
- case Format.R10G10B10A2Unorm:
- case Format.R10G10B10A2Uint:
- case Format.R11G11B10Float:
- case Format.B8G8R8A8Unorm:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is valid to use as render target color format.
- ///
- /// Texture format
- /// True if the texture can be used as render target, false otherwise
- public static bool IsRtColorCompatible(this Format format)
- {
- switch (format)
- {
- case Format.R32G32B32A32Float:
- case Format.R32G32B32A32Sint:
- case Format.R32G32B32A32Uint:
- case Format.R16G16B16A16Unorm:
- case Format.R16G16B16A16Snorm:
- case Format.R16G16B16A16Sint:
- case Format.R16G16B16A16Uint:
- case Format.R16G16B16A16Float:
- case Format.R32G32Float:
- case Format.R32G32Sint:
- case Format.R32G32Uint:
- case Format.B8G8R8A8Unorm:
- case Format.B8G8R8A8Srgb:
- case Format.B10G10R10A2Unorm:
- case Format.R10G10B10A2Unorm:
- case Format.R10G10B10A2Uint:
- case Format.R8G8B8A8Unorm:
- case Format.R8G8B8A8Srgb:
- case Format.R8G8B8A8Snorm:
- case Format.R8G8B8A8Sint:
- case Format.R8G8B8A8Uint:
- case Format.R16G16Unorm:
- case Format.R16G16Snorm:
- case Format.R16G16Sint:
- case Format.R16G16Uint:
- case Format.R16G16Float:
- case Format.R11G11B10Float:
- case Format.R32Sint:
- case Format.R32Uint:
- case Format.R32Float:
- case Format.B5G6R5Unorm:
- case Format.B5G5R5A1Unorm:
- case Format.R8G8Unorm:
- case Format.R8G8Snorm:
- case Format.R8G8Sint:
- case Format.R8G8Uint:
- case Format.R16Unorm:
- case Format.R16Snorm:
- case Format.R16Sint:
- case Format.R16Uint:
- case Format.R16Float:
- case Format.R8Unorm:
- case Format.R8Snorm:
- case Format.R8Sint:
- case Format.R8Uint:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is 16 bit packed.
- ///
- /// Texture format
- /// True if the texture format is 16 bit packed, false otherwise
- public static bool Is16BitPacked(this Format format)
- {
- switch (format)
- {
- case Format.B5G6R5Unorm:
- case Format.B5G5R5A1Unorm:
- case Format.R5G5B5X1Unorm:
- case Format.R5G5B5A1Unorm:
- case Format.R5G6B5Unorm:
- case Format.R4G4B4A4Unorm:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is an ASTC format.
- ///
- /// Texture format
- /// True if the texture format is an ASTC format, false otherwise
- public static bool IsAstc(this Format format)
- {
- return format.IsAstcUnorm() || format.IsAstcSrgb();
- }
-
- ///
- /// Checks if the texture format is an ASTC Unorm format.
- ///
- /// Texture format
- /// True if the texture format is an ASTC Unorm format, false otherwise
- public static bool IsAstcUnorm(this Format format)
- {
- switch (format)
- {
- case Format.Astc4x4Unorm:
- case Format.Astc5x4Unorm:
- case Format.Astc5x5Unorm:
- case Format.Astc6x5Unorm:
- case Format.Astc6x6Unorm:
- case Format.Astc8x5Unorm:
- case Format.Astc8x6Unorm:
- case Format.Astc8x8Unorm:
- case Format.Astc10x5Unorm:
- case Format.Astc10x6Unorm:
- case Format.Astc10x8Unorm:
- case Format.Astc10x10Unorm:
- case Format.Astc12x10Unorm:
- case Format.Astc12x12Unorm:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is an ASTC SRGB format.
- ///
- /// Texture format
- /// True if the texture format is an ASTC SRGB format, false otherwise
- public static bool IsAstcSrgb(this Format format)
- {
- switch (format)
- {
- case Format.Astc4x4Srgb:
- case Format.Astc5x4Srgb:
- case Format.Astc5x5Srgb:
- case Format.Astc6x5Srgb:
- case Format.Astc6x6Srgb:
- case Format.Astc8x5Srgb:
- case Format.Astc8x6Srgb:
- case Format.Astc8x8Srgb:
- case Format.Astc10x5Srgb:
- case Format.Astc10x6Srgb:
- case Format.Astc10x8Srgb:
- case Format.Astc10x10Srgb:
- case Format.Astc12x10Srgb:
- case Format.Astc12x12Srgb:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is an ETC2 format.
- ///
- /// Texture format
- /// True if the texture format is an ETC2 format, false otherwise
- public static bool IsEtc2(this Format format)
- {
- switch (format)
- {
- case Format.Etc2RgbaSrgb:
- case Format.Etc2RgbaUnorm:
- case Format.Etc2RgbPtaSrgb:
- case Format.Etc2RgbPtaUnorm:
- case Format.Etc2RgbSrgb:
- case Format.Etc2RgbUnorm:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is a BGR format.
- ///
- /// Texture format
- /// True if the texture format is a BGR format, false otherwise
- public static bool IsBgr(this Format format)
- {
- switch (format)
- {
- case Format.B5G6R5Unorm:
- case Format.B5G5R5A1Unorm:
- case Format.B8G8R8A8Unorm:
- case Format.B8G8R8A8Srgb:
- case Format.B10G10R10A2Unorm:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is a depth, stencil or depth-stencil format.
- ///
- /// Texture format
- /// True if the format is a depth, stencil or depth-stencil format, false otherwise
- public static bool IsDepthOrStencil(this Format format)
- {
- switch (format)
- {
- case Format.D16Unorm:
- case Format.D24UnormS8Uint:
- case Format.S8UintD24Unorm:
- case Format.X8UintD24Unorm:
- case Format.D32Float:
- case Format.D32FloatS8Uint:
- case Format.S8Uint:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is an unsigned integer color format.
- ///
- /// Texture format
- /// True if the texture format is an unsigned integer color format, false otherwise
- public static bool IsUint(this Format format)
- {
- switch (format)
- {
- case Format.R8Uint:
- case Format.R16Uint:
- case Format.R32Uint:
- case Format.R8G8Uint:
- case Format.R16G16Uint:
- case Format.R32G32Uint:
- case Format.R8G8B8Uint:
- case Format.R16G16B16Uint:
- case Format.R32G32B32Uint:
- case Format.R8G8B8A8Uint:
- case Format.R16G16B16A16Uint:
- case Format.R32G32B32A32Uint:
- case Format.R10G10B10A2Uint:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is a signed integer color format.
- ///
- /// Texture format
- /// True if the texture format is a signed integer color format, false otherwise
- public static bool IsSint(this Format format)
- {
- switch (format)
- {
- case Format.R8Sint:
- case Format.R16Sint:
- case Format.R32Sint:
- case Format.R8G8Sint:
- case Format.R16G16Sint:
- case Format.R32G32Sint:
- case Format.R8G8B8Sint:
- case Format.R16G16B16Sint:
- case Format.R32G32B32Sint:
- case Format.R8G8B8A8Sint:
- case Format.R16G16B16A16Sint:
- case Format.R32G32B32A32Sint:
- case Format.R10G10B10A2Sint:
- return true;
- }
-
- return false;
- }
-
- ///
- /// Checks if the texture format is an integer color format.
- ///
- /// Texture format
- /// True if the texture format is an integer color format, false otherwise
- public static bool IsInteger(this Format format)
- {
- return format.IsUint() || format.IsSint();
- }
-
- ///
- /// Checks if the texture format is a float or sRGB color format.
- ///
- ///
- /// Does not include normalized, compressed or depth formats.
- /// Float and sRGB formats do not participate in logical operations.
- ///
- /// Texture format
- /// True if the format is a float or sRGB color format, false otherwise
- public static bool IsFloatOrSrgb(this Format format)
- {
- switch (format)
- {
- case Format.R8G8B8A8Srgb:
- case Format.B8G8R8A8Srgb:
- case Format.R16Float:
- case Format.R16G16Float:
- case Format.R16G16B16Float:
- case Format.R16G16B16A16Float:
- case Format.R32Float:
- case Format.R32G32Float:
- case Format.R32G32B32Float:
- case Format.R32G32B32A32Float:
- case Format.R11G11B10Float:
- case Format.R9G9B9E5Float:
- return true;
- }
-
- return false;
+ ///
+ /// Checks if the texture format is an integer color format.
+ ///
+ public bool IsInt => fmt.IsUnsignedInt || fmt.IsSignedInt;
}
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
index 063cf1e03..ca7c8c8c2 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
@@ -1,11 +1,12 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
-using System.Linq;
+using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetRenderTargetsCommand : IGALCommand, IGALCommand
{
+ public static readonly ArrayPool ArrayPool = ArrayPool.Create(512, 50);
public readonly CommandType CommandType => CommandType.SetRenderTargets;
private TableRef _colors;
private TableRef _depthStencil;
@@ -18,7 +19,18 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
- renderer.Pipeline.SetRenderTargets(command._colors.Get(threaded).Select(color => ((ThreadedTexture)color)?.Base).ToArray(), command._depthStencil.GetAs(threaded)?.Base);
+ ITexture[] colors = command._colors.Get(threaded);
+ ITexture[] colorsCopy = ArrayPool.Rent(colors.Length);
+
+ for (int i = 0; i < colors.Length; i++)
+ {
+ colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base;
+ }
+
+ renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs(threaded)?.Base);
+
+ ArrayPool.Return(colorsCopy);
+ ArrayPool.Return(colors);
}
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index c0fb6361e..ea3fd1e11 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -269,7 +269,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
{
- _renderer.New()->Set(Ref(colors.ToArray()), Ref(depthStencil));
+ ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
+ colors.CopyTo(colorsCopy, 0);
+
+ _renderer.New()->Set(Ref(colorsCopy), Ref(depthStencil));
_renderer.QueueCommand();
}
diff --git a/src/Ryujinx.Graphics.GAL/Target.cs b/src/Ryujinx.Graphics.GAL/Target.cs
index 1c184981d..531051d02 100644
--- a/src/Ryujinx.Graphics.GAL/Target.cs
+++ b/src/Ryujinx.Graphics.GAL/Target.cs
@@ -16,19 +16,18 @@ namespace Ryujinx.Graphics.GAL
public static class TargetExtensions
{
- public static bool IsMultisample(this Target target)
+ extension(Target target)
{
- return target is Target.Texture2DMultisample or Target.Texture2DMultisampleArray;
- }
+ public bool IsMultisample => target is Target.Texture2DMultisample or Target.Texture2DMultisampleArray;
- public static bool HasDepthOrLayers(this Target target)
- {
- return target is Target.Texture3D or
- Target.Texture1DArray or
- Target.Texture2DArray or
- Target.Texture2DMultisampleArray or
- Target.Cubemap or
- Target.CubemapArray;
+ public bool HasDepthOrLayers =>
+ target is
+ Target.Texture3D or
+ Target.Texture1DArray or
+ Target.Texture2DArray or
+ Target.Texture2DMultisampleArray or
+ Target.Cubemap or
+ Target.CubemapArray;
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
index 37d7457fc..c9fe2470e 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
@@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
{
MemoryManager memoryManager = _channel.MemoryManager;
- Span data = MemoryMarshal.Cast(_buffer)[.._size];
+ Span data = MemoryMarshal.Cast(new Span(_buffer))[.._size];
if (_isLinear && _lineCount == 1)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
index 23a73908d..6d62464d2 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
@@ -176,7 +176,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
- int componentSize = format.GetScalarSize();
+ int componentSize = format.ScalarSize;
address += attributeOffset;
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index c9f3e8bad..d9218e524 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -451,7 +451,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// TODO: Confirm behaviour on hardware.
// When this is active, the origin appears to be on the bottom.
- if (_state.State.YControl.HasFlag(YControl.NegateY))
+ if ((_state.State.YControl & YControl.NegateY) != 0)
{
dstY0 -= dstHeight;
}
@@ -849,8 +849,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FormatInfo dsFormat = _state.State.RtDepthStencilState.Format.Convert();
- bool hasDepth = dsFormat.Format.HasDepth();
- bool hasStencil = dsFormat.Format.HasStencil();
+ bool hasDepth = dsFormat.Format.HasDepth;
+ bool hasStencil = dsFormat.Format.HasStencil;
if (hasStencil && (!clearStencil || (clearAffectedByStencilMask && _state.State.StencilTestState.FrontMask != 0xff)))
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
index 2fb8de920..0c1e33731 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
@@ -297,7 +297,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
Format format = colorState.Format.Convert().Format;
- AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
+ AttributeType type = format.IsInt
+ ? (format.IsSignedInt ? AttributeType.Sint : AttributeType.Uint)
+ : AttributeType.Float;
if (type != fragmentOutputTypesSpan[index])
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 7bcf16ae7..4f2f01606 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -534,7 +534,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (!_context.Capabilities.SupportsBgraFormat)
{
- _context.SupportBufferUpdater.SetRenderTargetIsBgra(index, color.Format.IsBgr());
+ _context.SupportBufferUpdater.SetRenderTargetIsBgra(index, color.Format.IsBgr);
}
}
}
@@ -646,7 +646,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int width = scissor.X2 - x;
int height = scissor.Y2 - y;
- if (_state.State.YControl.HasFlag(YControl.NegateY))
+ if ((_state.State.YControl & YControl.NegateY) != 0)
{
ref ScreenScissorState screenScissor = ref _state.State.ScreenScissorState;
y = screenScissor.Height - height - y;
@@ -730,7 +730,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FaceState face = _state.State.FaceState;
bool disableTransform = _state.State.ViewportTransformEnable == 0;
- bool yNegate = yControl.HasFlag(YControl.NegateY);
+ bool yNegate = (yControl & YControl.NegateY) != 0;
UpdateFrontFace(yControl, face.FrontFace);
UpdateDepthMode();
@@ -1230,7 +1230,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Front face
private void UpdateFrontFace(YControl yControl, FrontFace frontFace)
{
- bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip);
+ bool isUpperLeftOrigin = (yControl & YControl.TriangleRastFlip) == 0;
if (isUpperLeftOrigin)
{
@@ -1317,10 +1317,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaDstFactor, index));
if (enable &&
- (blend.ColorSrcFactor.IsDualSource() ||
- blend.ColorDstFactor.IsDualSource() ||
- blend.AlphaSrcFactor.IsDualSource() ||
- blend.AlphaDstFactor.IsDualSource()))
+ (blend.ColorSrcFactor.IsDualSource ||
+ blend.ColorDstFactor.IsDualSource ||
+ blend.AlphaSrcFactor.IsDualSource ||
+ blend.AlphaDstFactor.IsDualSource))
{
dualSourceBlendEnabled = true;
}
@@ -1345,10 +1345,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaDstFactor, 0));
if (enable &&
- (blend.ColorSrcFactor.IsDualSource() ||
- blend.ColorDstFactor.IsDualSource() ||
- blend.AlphaSrcFactor.IsDualSource() ||
- blend.AlphaDstFactor.IsDualSource()))
+ (blend.ColorSrcFactor.IsDualSource ||
+ blend.ColorDstFactor.IsDualSource ||
+ blend.AlphaSrcFactor.IsDualSource ||
+ blend.AlphaDstFactor.IsDualSource))
{
dualSourceBlendEnabled = true;
}
@@ -1521,7 +1521,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
// Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
- if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY))
+ if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0)
{
UpdateSupportBufferViewportSize();
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs
index 5ab58d7d1..5deddbadf 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs
@@ -333,7 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
// as copies between depth and color formats are not allowed.
// For depth blit, the destination texture format should always match exactly.
- if (srcTexture.Format.IsDepthOrStencil())
+ if (srcTexture.Format.IsDepthOrStencil)
{
dstCopyTextureFormat = srcTexture.Info.FormatInfo;
}
diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
index d0b8277da..8b1277c47 100644
--- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -381,9 +381,9 @@ namespace Ryujinx.Graphics.Gpu
/// Modifiers for how host sync should be created
internal void CreateHostSyncIfNeeded(HostSyncFlags flags)
{
- bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint);
- bool strict = flags.HasFlag(HostSyncFlags.Strict);
- bool force = flags.HasFlag(HostSyncFlags.Force);
+ bool syncPoint = (flags & HostSyncFlags.Syncpoint) == HostSyncFlags.Syncpoint;
+ bool strict = (flags & HostSyncFlags.Strict) == HostSyncFlags.Strict;
+ bool force = (flags & HostSyncFlags.Force) == HostSyncFlags.Force;
if (BufferMigrations.Count > 0)
{
@@ -402,24 +402,37 @@ namespace Ryujinx.Graphics.Gpu
}
}
- if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
+ if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0))
{
foreach (ISyncActionHandler action in SyncActions)
{
- action.SyncPreAction(syncpoint);
+ action.SyncPreAction(syncPoint);
}
foreach (ISyncActionHandler action in SyncpointActions)
{
- action.SyncPreAction(syncpoint);
+ action.SyncPreAction(syncPoint);
}
Renderer.CreateSync(SyncNumber, strict);
SyncNumber++;
- SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
- SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint));
+ for (int i = 0; i < SyncActions.Count; i++)
+ {
+ if (SyncActions[i].SyncAction(syncPoint))
+ {
+ SyncActions.RemoveAt(i--);
+ }
+ }
+
+ for (int i = 0; i < SyncpointActions.Count; i++)
+ {
+ if (SyncpointActions[i].SyncAction(syncPoint))
+ {
+ SyncpointActions.RemoveAt(i--);
+ }
+ }
}
_pendingSync = false;
diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
index 8bfebe8a1..b1e46cfbe 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -662,7 +662,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool found = _textureFormats.TryGetValue((TextureFormat)encoded, out format);
- if (found && isPacked && !format.Format.IsDepthOrStencil())
+ if (found && isPacked && !format.Format.IsDepthOrStencil)
{
// If the packed flag is set, then the components of the pixel are tightly packed into the
// GPU registers on the shader.
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 9540df548..05a316c45 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// The decompression is slow, so we want to avoid it as much as possible.
// This does a byte-by-byte check and skips the update if the data is equal in this case.
// This improves the speed on applications that overwrites ASTC data without changing anything.
- if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression)
+ if (Info.FormatInfo.Format.IsAstc && !_context.Capabilities.SupportsAstcCompression)
{
if (_updateCount < ByteComparisonSwitchThreshold)
{
@@ -792,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures.
- if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
+ if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc)
{
using (result)
{
@@ -823,7 +823,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return decoded;
}
}
- else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2())
+ else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2)
{
switch (Format)
{
@@ -924,7 +924,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
}
- else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
+ else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked)
{
switch (Format)
{
@@ -1251,7 +1251,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps));
- bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
+ bool bothMs = Info.Target.IsMultisample && info.Target.IsMultisample;
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
{
result = TextureViewCompatibility.Incompatible;
@@ -1628,7 +1628,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{
lock (_poolOwners)
{
- int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1);
+ int references = 0;
+ for (int i = 0; i < _poolOwners.Count; i++)
+ {
+ if (_poolOwners[i].Pool == pool && _poolOwners[i].ID == id || id == -1)
+ {
+ _poolOwners.RemoveAt(i--);
+ references++;
+ }
+ }
if (references == 0)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index f3df8b072..390d7c5c7 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int widthAlignment = (info.IsLinear ? Constants.StrideAlignment : Constants.GobAlignment) / info.FormatInfo.BytesPerPixel;
- if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1))
+ if (!(info.FormatInfo.Format.IsDepthOrStencil || info.FormatInfo.Components == 1))
{
// Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas)
// Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height.
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index f2cbca832..af7e9a7af 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -75,13 +75,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!caps.SupportsAstcCompression)
{
- if (info.FormatInfo.Format.IsAstcUnorm())
+ if (info.FormatInfo.Format.IsAstcUnorm)
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
- else if (info.FormatInfo.Format.IsAstcSrgb())
+
+ if (info.FormatInfo.Format.IsAstcSrgb)
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
@@ -151,9 +152,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
- else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
+ else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked)
{
- return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
+ return new FormatInfo(info.FormatInfo.Format.IsBgr ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
return info.FormatInfo;
@@ -388,7 +389,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
}
- else if (lhs.Target.IsMultisample() != rhs.Target.IsMultisample() && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
+ else if (lhs.Target.IsMultisample != rhs.Target.IsMultisample && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
{
// Copy between multisample and non-multisample textures with mismatching size is allowed,
// as long aligned size matches.
@@ -644,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
FormatInfo lhsFormat = lhs.FormatInfo;
FormatInfo rhsFormat = rhs.FormatInfo;
- if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
+ if (lhsFormat.Format.IsDepthOrStencil || rhsFormat.Format.IsDepthOrStencil)
{
bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 7f38df129..e7a1afe1a 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -45,7 +45,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
private const int GranularLayerThreshold = 8;
- private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
+ private delegate bool HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false, bool specialData = false);
+
+ private readonly HandlesCallbackDelegate _signalModifyingCallback;
+ private readonly HandlesCallbackDelegate _discardDataCallback;
+ private readonly HandlesCallbackDelegate _checkDirtyCallback;
///
/// The storage texture associated with this group.
@@ -126,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_incompatibleOverlaps = incompatibleOverlaps;
_flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities);
+
+ _signalModifyingCallback = SignalModifyingCallback;
+ _discardDataCallback = DiscardDataCallback;
+ _checkDirtyCallback = CheckDirtyCallback;
}
///
@@ -139,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_allOffsets = size.AllOffsets;
_sliceSizes = size.SliceSizes;
- if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
+ if (Storage.Target.HasDepthOrLayers && Storage.Info.GetSlices() > GranularLayerThreshold)
{
_hasLayerViews = true;
_hasMipViews = true;
@@ -253,29 +261,33 @@ namespace Ryujinx.Graphics.Gpu.Image
/// True to consume the dirty flags and reprotect, false to leave them as is
/// True if a flag was dirty, false otherwise
public bool CheckDirty(Texture texture, bool consume)
+ {
+ EvaluateRelevantHandles(texture, _checkDirtyCallback, out bool dirty, consume);
+
+ return dirty;
+ }
+
+ bool CheckDirtyCallback(int baseHandle, int regionCount, bool split, bool consume)
{
bool dirty = false;
-
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+
+ for (int i = 0; i < regionCount; i++)
{
- for (int i = 0; i < regionCount; i++)
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ foreach (RegionHandle handle in group.Handles)
{
- TextureGroupHandle group = _handles[baseHandle + i];
-
- foreach (RegionHandle handle in group.Handles)
+ if (handle.Dirty)
{
- if (handle.Dirty)
+ if (consume)
{
- if (consume)
- {
- handle.Reprotect();
- }
-
- dirty = true;
+ handle.Reprotect();
}
+
+ dirty = true;
}
}
- });
+ }
return dirty;
}
@@ -287,15 +299,19 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture being discarded
public void DiscardData(Texture texture)
{
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ EvaluateRelevantHandles(texture, _discardDataCallback, out _);
+ }
+
+ bool DiscardDataCallback(int baseHandle, int regionCount, bool split, bool bound)
+ {
+ for (int i = 0; i < regionCount; i++)
{
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
+ TextureGroupHandle group = _handles[baseHandle + i];
- group.DiscardData();
- }
- });
+ group.DiscardData();
+ }
+
+ return true;
}
///
@@ -307,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
FlushIncompatibleOverlapsIfNeeded();
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
{
bool dirty = false;
bool anyModified = false;
@@ -383,7 +399,9 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.SynchronizeFull();
}
}
- });
+
+ return true;
+ }, out _);
}
///
@@ -460,7 +478,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to synchronize dependents of
public void SynchronizeDependents(Texture texture)
{
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
{
for (int i = 0; i < regionCount; i++)
{
@@ -468,7 +486,9 @@ namespace Ryujinx.Graphics.Gpu.Image
group.SynchronizeDependents();
}
- });
+
+ return true;
+ }, out _);
}
///
@@ -550,7 +570,7 @@ namespace Ryujinx.Graphics.Gpu.Image
tracked = tracked || ShouldFlushTriggerTracking();
bool flushed = false;
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
{
int startSlice = 0;
int endSlice = 0;
@@ -604,7 +624,9 @@ namespace Ryujinx.Graphics.Gpu.Image
flushed = true;
}
- });
+
+ return true;
+ }, out _);
Storage.SignalModifiedDirty();
@@ -693,7 +715,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ClearIncompatibleOverlaps(texture);
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
{
for (int i = 0; i < regionCount; i++)
{
@@ -701,7 +723,9 @@ namespace Ryujinx.Graphics.Gpu.Image
group.SignalModified(_context);
}
- });
+
+ return true;
+ }, out _);
}
///
@@ -714,16 +738,20 @@ namespace Ryujinx.Graphics.Gpu.Image
ModifiedSequence = _context.GetModifiedSequence();
ClearIncompatibleOverlaps(texture);
-
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+
+ EvaluateRelevantHandles(texture, _signalModifyingCallback, out _, bound);
+ }
+
+ bool SignalModifyingCallback(int baseHandle, int regionCount, bool split, bool bound)
+ {
+ for (int i = 0; i < regionCount; i++)
{
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
+ TextureGroupHandle group = _handles[baseHandle + i];
- group.SignalModifying(bound, _context);
- }
- });
+ group.SignalModifying(bound, _context);
+ }
+
+ return true;
}
///
@@ -767,16 +795,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
/// This can be called for multiple disjoint ranges, if required.
///
- private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
+ private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback, out bool result, bool specialData = false)
{
if (texture == Storage || !(_hasMipViews || _hasLayerViews))
{
- callback(0, _handles.Length);
+ result = callback(0, _handles.Length, specialData: specialData);
return;
}
- EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
+ EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback, out result, specialData);
}
///
@@ -791,11 +819,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
/// This can be called for multiple disjoint ranges, if required.
///
- private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
+ private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback, out bool result, bool specialData = false)
{
int targetLayerHandles = _hasLayerViews ? slices : 1;
int targetLevelHandles = _hasMipViews ? levels : 1;
+ result = false;
+
if (_isBuffer)
{
return;
@@ -808,7 +838,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
// When there are no layer views, the mips are at a consistent offset.
- callback(firstLevel, targetLevelHandles);
+ result = callback(firstLevel, targetLevelHandles, specialData: specialData);
}
else
{
@@ -822,7 +852,7 @@ namespace Ryujinx.Graphics.Gpu.Image
while (levels-- > 1)
{
- callback(firstLayer + levelIndex, slices);
+ result = callback(firstLayer + levelIndex, slices, specialData: specialData);
levelIndex += layerCount;
layerCount = Math.Max(layerCount >> 1, 1);
@@ -839,7 +869,7 @@ namespace Ryujinx.Graphics.Gpu.Image
totalSize += layerCount;
}
- callback(firstLayer + levelIndex, totalSize);
+ result = callback(firstLayer + levelIndex, totalSize, specialData: specialData);
}
}
}
@@ -856,12 +886,12 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int i = 0; i < slices; i++)
{
- callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
+ result = callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true, specialData: specialData);
}
}
else
{
- callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
+ result = callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles, specialData: specialData);
}
}
}
@@ -1439,8 +1469,16 @@ namespace Ryujinx.Graphics.Gpu.Image
List<(int BaseHandle, int RegionCount)> targetRange = [];
List<(int BaseHandle, int RegionCount)> otherRange = [];
- EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
- otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
+ EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split, specialData) =>
+ {
+ targetRange.Add((baseHandle, regionCount));
+ return true;
+ }, out _);
+ otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split, specialData) =>
+ {
+ otherRange.Add((baseHandle, regionCount));
+ return true;
+ }, out _);
int targetIndex = 0;
int otherIndex = 0;
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index c2a503840..d9fe02f8d 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -660,7 +660,7 @@ namespace Ryujinx.Graphics.Gpu.Image
swizzleB,
swizzleA);
- if (formatInfo.Format.IsDepthOrStencil())
+ if (formatInfo.Format.IsDepthOrStencil)
{
swizzleR = SwizzleComponent.Red;
swizzleG = SwizzleComponent.Red;
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index c4b848035..277a30689 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -93,6 +93,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private ulong _dirtyStart = ulong.MaxValue;
private ulong _dirtyEnd = ulong.MaxValue;
+ private readonly Action _syncPreRangeAction;
+ private readonly Action _syncRangeAction;
+
///
/// Creates a new instance of the buffer.
///
@@ -135,7 +138,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (item.Value._useGranular)
{
- baseHandles.AddRange((item.Value._memoryTrackingGranular.GetHandles()));
+ baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles);
}
else
{
@@ -177,6 +180,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedDelegate = RegionModified;
_virtualDependenciesLock = new ReaderWriterLockSlim();
+
+ _syncPreRangeAction = SyncPreRangeAction;
+ _syncRangeAction = SyncRangeAction;
}
///
@@ -401,13 +407,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_preFlush.ShouldCopy)
{
- _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) =>
- {
- _preFlush.CopyModified(address, size);
- });
+ _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction);
}
}
}
+
+ void SyncPreRangeAction(ulong address, ulong size)
+ {
+ _preFlush.CopyModified(address, size);
+ }
///
/// Action to be performed when a syncpoint is reached after modification.
@@ -420,11 +428,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_useGranular)
{
- _modifiedRanges?.GetRanges(Address, Size, (address, size) =>
- {
- _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
- SynchronizeMemory(address, size);
- });
+
+
+ _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
}
else
{
@@ -434,6 +440,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
return true;
}
+
+ void SyncRangeAction(ulong address, ulong size)
+ {
+ _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
+ SynchronizeMemory(address, size);
+ }
///
/// Inherit modified and dirty ranges from another buffer.
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
index fee4b11c0..9c50eaf2f 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
@@ -1,5 +1,6 @@
using Ryujinx.Memory.Range;
using System;
+using System.Buffers;
using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory
@@ -276,13 +277,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock();
- RangeItem[] overlaps = FindOverlapsAsArray(address, size);
+ RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int length);
Lock.ExitReadLock();
- for (int i = 0; i < overlaps.Length; i++)
+ if (length != 0)
{
- BufferModifiedRange overlap = overlaps[i].Value;
- rangeAction(overlap.Address, overlap.Size);
+ for (int i = 0; i < length; i++)
+ {
+ BufferModifiedRange overlap = overlaps[i].Value;
+ rangeAction(overlap.Address, overlap.Size);
+ }
+
+ ArrayPool>.Shared.Return(overlaps);
}
}
@@ -392,9 +398,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
- RangeItem[] overlaps = FindOverlapsAsArray(address, size);
-
- int rangeCount = overlaps.Length;
+ RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
if (rangeCount == 0)
{
@@ -410,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++)
{
- BufferModifiedRange overlap = overlaps[i].Value;
+ BufferModifiedRange overlap = overlaps![i].Value;
long diff = (long)(overlap.SyncNumber - currentSync);
@@ -430,7 +434,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Wait for the syncpoint.
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
- RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress);
+ RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
+
+ ArrayPool>.Shared.Return(overlaps!);
Lock.ExitWriteLock();
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs
index 2d67c2c28..33381dd55 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs
@@ -84,10 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < count; i++)
{
ICounterEvent evt = _items[index + i].Event;
- if (evt != null)
- {
- evt.Invalid = true;
- }
+ evt?.Invalid = true;
}
_items.RemoveRange(index, count);
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 8ce937e44..abe5c9840 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// True if queried, false otherwise
public bool IsPrimitiveTopologyQueried()
{
- return _queriedState.HasFlag(QueriedStateFlags.PrimitiveTopology);
+ return (_queriedState & QueriedStateFlags.PrimitiveTopology) == QueriedStateFlags.PrimitiveTopology;
}
///
@@ -904,7 +904,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
specState.PipelineState = pipelineState;
}
- if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ if ((specState._queriedState & QueriedStateFlags.TransformFeedback) == QueriedStateFlags.TransformFeedback)
{
ushort tfCount = 0;
dataReader.Read(ref tfCount);
@@ -930,7 +930,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
specState._textureSpecialization[textureKey] = textureState;
}
- if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
+ if ((specState._queriedState & QueriedStateFlags.TextureArrayFromBuffer) == QueriedStateFlags.TextureArrayFromBuffer)
{
dataReader.Read(ref count);
@@ -946,7 +946,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
- if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
+ if ((specState._queriedState & QueriedStateFlags.TextureArrayFromPool) == QueriedStateFlags.TextureArrayFromPool)
{
dataReader.Read(ref count);
@@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
}
- if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ if ((_queriedState & QueriedStateFlags.TransformFeedback) == QueriedStateFlags.TransformFeedback)
{
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
dataWriter.Write(ref tfCount);
@@ -1029,7 +1029,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
}
- if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
+ if ((_queriedState & QueriedStateFlags.TextureArrayFromBuffer) == QueriedStateFlags.TextureArrayFromBuffer)
{
count = (ushort)_textureArrayFromBufferSpecialization.Count;
dataWriter.Write(ref count);
@@ -1044,7 +1044,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
- if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
+ if ((_queriedState & QueriedStateFlags.TextureArrayFromPool) == QueriedStateFlags.TextureArrayFromPool)
{
count = (ushort)_textureArrayFromPoolSpecialization.Count;
dataWriter.Write(ref count);
diff --git a/src/Ryujinx.Graphics.OpenGL/Buffer.cs b/src/Ryujinx.Graphics.OpenGL/Buffer.cs
index 33ca25174..9df408944 100644
--- a/src/Ryujinx.Graphics.OpenGL/Buffer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Buffer.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.OpenGL
{
public static void Clear(BufferHandle destination, int offset, int size, uint value)
{
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination);
unsafe
{
@@ -58,8 +58,8 @@ namespace Ryujinx.Graphics.OpenGL
public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
{
- GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32());
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyReadBuffer, source);
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination);
GL.CopyBufferSubData(
BufferTarget.CopyReadBuffer,
@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.OpenGL
{
nint target = renderer.PersistentBuffers.Default.GetHostArray(size);
- GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer);
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (nint)offset, size, target);
@@ -96,13 +96,13 @@ namespace Ryujinx.Graphics.OpenGL
public static void Resize(BufferHandle handle, int size)
{
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
GL.BufferData(BufferTarget.CopyWriteBuffer, size, nint.Zero, BufferUsageHint.StreamCopy);
}
public static void SetData(BufferHandle buffer, int offset, ReadOnlySpan data)
{
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, buffer.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, buffer);
unsafe
{
@@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.OpenGL
public static void Delete(BufferHandle buffer)
{
- GL.DeleteBuffer(buffer.ToInt32());
+ GL.DeleteBuffer(buffer);
}
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Handle.cs b/src/Ryujinx.Graphics.OpenGL/Handle.cs
index b63e8f946..3803f0b07 100644
--- a/src/Ryujinx.Graphics.OpenGL/Handle.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Handle.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Graphics.GAL;
using System.Diagnostics;
using System.Runtime.CompilerServices;
@@ -14,10 +13,5 @@ namespace Ryujinx.Graphics.OpenGL
return Unsafe.As(ref handle64);
}
-
- public static int ToInt32(this BufferHandle handle)
- {
- return (int)Unsafe.As(ref handle);
- }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
index 64e4fe36d..1a67c28e7 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
@@ -139,7 +139,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
start = sizeAligned;
}
- Span outSpan = MemoryMarshal.Cast(output);
+ Span outSpan = MemoryMarshal.Cast(new Span(output));
ReadOnlySpan dataSpan = MemoryMarshal.Cast(data);
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
{
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
index 231d9c97b..9ad4eb824 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
@@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
SizedInternalFormat format = (SizedInternalFormat)FormatTable.GetFormatInfo(Info.Format).PixelInternalFormat;
- GL.TexBufferRange(TextureBufferTarget.TextureBuffer, format, _buffer.ToInt32(), (nint)buffer.Offset, buffer.Size);
+ GL.TexBufferRange(TextureBufferTarget.TextureBuffer, format, _buffer, (nint)buffer.Offset, buffer.Size);
}
public void Dispose()
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index 3d1e47339..4b6821934 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
int layers,
int levels)
{
- TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src;
+ TextureView srcConverted = src.Format.IsBgr != dst.Format.IsBgr ? BgraSwap(src) : src;
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
@@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
ClearBufferMask mask = GetMask(src.Format);
- if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
+ if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInt)
{
linearFilter = false;
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index fcd004dd6..12ec23c8b 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
swizzleRgba[2] = temp2;
swizzleRgba[3] = temp;
}
- else if (Info.Format.IsBgr())
+ else if (Info.Format.IsBgr)
{
// Swap B <-> R for BGRA formats, as OpenGL has no support for them
// and we need to manually swap the components on read/write on the GPU.
@@ -116,13 +116,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
- bool srcIsMultisample = Target.IsMultisample();
- bool dstIsMultisample = destinationView.Target.IsMultisample();
+ bool srcIsMultisample = Target.IsMultisample;
+ bool dstIsMultisample = destinationView.Target.IsMultisample;
- if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
+ if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
- CopyWithBlitForDepthMS(destinationView, 0, firstLayer, layers);
+ CopyWithBlitForDepthMultisample(destinationView, 0, firstLayer, layers);
}
else if (!dstIsMultisample && srcIsMultisample)
{
@@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
}
- else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
+ else if (destinationView.Format.IsDepthOrStencil != Format.IsDepthOrStencil)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
@@ -172,12 +172,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
- bool srcIsMultisample = Target.IsMultisample();
- bool dstIsMultisample = destinationView.Target.IsMultisample();
+ bool srcIsMultisample = Target.IsMultisample;
+ bool dstIsMultisample = destinationView.Target.IsMultisample;
- if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
+ if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil)
{
- CopyWithBlitForDepthMS(destinationView, srcLayer, dstLayer, 1);
+ CopyWithBlitForDepthMultisample(destinationView, srcLayer, dstLayer, 1);
}
else if (!dstIsMultisample && srcIsMultisample)
{
@@ -191,7 +191,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
- else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
+ else if (destinationView.Format.IsDepthOrStencil != Format.IsDepthOrStencil)
{
int minWidth = Math.Min(Width, destinationView.Width);
int minHeight = Math.Min(Height, destinationView.Height);
@@ -204,7 +204,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
- private void CopyWithBlitForDepthMS(TextureView destinationView, int srcLayer, int dstLayer, int layers)
+ private void CopyWithBlitForDepthMultisample(TextureView destinationView, int srcLayer, int dstLayer, int layers)
{
// This is currently used for multisample <-> non-multisample copies.
// We can't do that with compute because it's not possible to write depth textures on compute.
@@ -216,9 +216,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
Extents2D srcRegion = new(0, 0, Width, Height);
Extents2D dstRegion = new(0, 0, destinationView.Width, destinationView.Height);
- if (destinationView.Target.IsMultisample())
+ if (destinationView.Target.IsMultisample)
{
- TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
+ TextureView intermediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
Info.Target,
Info.BlockWidth,
Info.BlockHeight,
@@ -230,8 +230,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
1,
1);
- _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, dstRegion, false);
- _renderer.TextureCopy.Copy(intermmediate, destinationView, dstRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
+ _renderer.TextureCopy.Copy(this, intermediate, srcRegion, dstRegion, false);
+ _renderer.TextureCopy.Copy(intermediate, destinationView, dstRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
else
{
@@ -242,7 +242,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
_ => Target,
};
- TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
+ TextureView intermediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
target,
Info.BlockWidth,
Info.BlockHeight,
@@ -254,8 +254,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
1,
1);
- _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, false);
- _renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
+ _renderer.TextureCopy.Copy(this, intermediate, srcRegion, srcRegion, false);
+ _renderer.TextureCopy.Copy(intermediate, destinationView, srcRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
}
@@ -305,14 +305,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
return PinnedSpan.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
}
- else
- {
- nint target = _renderer.PersistentBuffers.Default.GetHostArray(size);
- int offset = WriteTo2D(target, layer, level);
+ nint target = _renderer.PersistentBuffers.Default.GetHostArray(size);
- return new PinnedSpan((byte*)target.ToPointer() + offset, size);
- }
+ int offset = WriteTo2D(target, layer, level);
+
+ return new PinnedSpan((byte*)target.ToPointer() + offset, size);
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
@@ -322,7 +320,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotSupportedException("Stride conversion for texture copy to buffer not supported.");
}
- GL.BindBuffer(BufferTarget.PixelPackBuffer, range.Handle.ToInt32());
+ GL.BindBuffer(BufferTarget.PixelPackBuffer, range.Handle);
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
if (format.PixelFormat == PixelFormat.DepthStencil)
diff --git a/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
index 28ebe88a5..d5c02f4df 100644
--- a/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
+++ b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
@@ -26,7 +26,7 @@ namespace Ryujinx.Graphics.OpenGL
public void Map(BufferHandle handle, int size)
{
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
_maps[handle] = ptr;
@@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.OpenGL
{
if (_maps.ContainsKey(handle))
{
- GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
GL.UnmapBuffer(BufferTarget.CopyWriteBuffer);
_maps.Remove(handle);
@@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.OpenGL
{
EnsureBuffer(size);
- GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32());
+ GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer);
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
GL.CopyBufferSubData(BufferTarget.CopyReadBuffer, BufferTarget.CopyWriteBuffer, (nint)offset, nint.Zero, size);
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 36db655ad..c8ca02140 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -587,7 +587,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetRangeOfIndexBuffer();
- GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32());
+ GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle);
GL.DrawElementsIndirect(_primitiveType, _elementsType, (nint)indirectBuffer.Offset);
@@ -608,8 +608,8 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetRangeOfIndexBuffer();
- GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32());
- GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32());
+ GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle);
+ GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle);
GL.MultiDrawElementsIndirectCount(
_primitiveType,
@@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.OpenGL
PreDrawVbUnbounded();
- GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32());
+ GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle);
GL.DrawArraysIndirect(_primitiveType, (nint)indirectBuffer.Offset);
@@ -651,8 +651,8 @@ namespace Ryujinx.Graphics.OpenGL
PreDrawVbUnbounded();
- GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32());
- GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32());
+ GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle);
+ GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle);
GL.MultiDrawArraysIndirectCount(
_primitiveType,
@@ -812,10 +812,10 @@ namespace Ryujinx.Graphics.OpenGL
EnsureFramebuffer();
_framebuffer.SetDualSourceBlend(
- blend.ColorSrcFactor.IsDualSource() ||
- blend.ColorDstFactor.IsDualSource() ||
- blend.AlphaSrcFactor.IsDualSource() ||
- blend.AlphaDstFactor.IsDualSource());
+ blend.ColorSrcFactor.IsDualSource ||
+ blend.ColorDstFactor.IsDualSource ||
+ blend.AlphaSrcFactor.IsDualSource ||
+ blend.AlphaDstFactor.IsDualSource);
if (_blendConstant != blend.BlendConstant)
{
@@ -1178,7 +1178,7 @@ namespace Ryujinx.Graphics.OpenGL
if (color != null)
{
- int isBgra = color.Format.IsBgr() ? 1 : 0;
+ int isBgra = color.Format.IsBgr ? 1 : 0;
if (_fpIsBgra[index].X != isBgra)
{
@@ -1349,7 +1349,7 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Resize(_tfbs[i], buffer.Size);
Buffer.Copy(buffer.Handle, _tfbs[i], buffer.Offset, 0, buffer.Size);
- GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, _tfbs[i].ToInt32());
+ GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, _tfbs[i]);
}
if (_tfEnabled)
@@ -1454,7 +1454,7 @@ namespace Ryujinx.Graphics.OpenGL
continue;
}
- GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (nint)buffer.Offset, buffer.Size);
+ GL.BindBufferRange(target, assignment.Binding, buffer.Handle, (nint)buffer.Offset, buffer.Size);
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/VertexArray.cs b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs
index 2480b6af2..f56a37c68 100644
--- a/src/Ryujinx.Graphics.OpenGL/VertexArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs
@@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.OpenGL
minVertexCount = vertexCount;
}
- GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle.ToInt32(), (nint)vb.Buffer.Offset, vb.Stride);
+ GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle, (nint)vb.Buffer.Offset, vb.Stride);
GL.VertexBindingDivisor(bindingIndex, vb.Divisor);
_vertexBuffersInUse |= 1u << bindingIndex;
}
@@ -134,19 +134,19 @@ namespace Ryujinx.Graphics.OpenGL
public void SetIndexBuffer(BufferRange range)
{
_indexBuffer = range;
- GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle.ToInt32());
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle);
}
public void SetRangeOfIndexBuffer()
{
Buffer.Resize(_tempIndexBuffer, _indexBuffer.Size);
Buffer.Copy(_indexBuffer.Handle, _tempIndexBuffer, _indexBuffer.Offset, 0, _indexBuffer.Size);
- GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer.ToInt32());
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer);
}
public void RestoreIndexBuffer()
{
- GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle.ToInt32());
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle);
}
public void PreDraw(int vertexCount)
@@ -188,7 +188,7 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Copy(vb.Buffer.Handle, tempVertexBuffer, vb.Buffer.Offset, currentTempVbOffset, vb.Buffer.Size);
Buffer.Clear(tempVertexBuffer, currentTempVbOffset + vb.Buffer.Size, requiredSize - vb.Buffer.Size, 0);
- GL.BindVertexBuffer(vbIndex, tempVertexBuffer.ToInt32(), (nint)currentTempVbOffset, vb.Stride);
+ GL.BindVertexBuffer(vbIndex, tempVertexBuffer, (nint)currentTempVbOffset, vb.Stride);
currentTempVbOffset += requiredSize;
_vertexBuffersLimited |= 1u << vbIndex;
@@ -234,7 +234,7 @@ namespace Ryujinx.Graphics.OpenGL
ref VertexBufferDescriptor vb = ref _vertexBuffers[vbIndex];
- GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle.ToInt32(), (nint)vb.Buffer.Offset, vb.Stride);
+ GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle, (nint)vb.Buffer.Offset, vb.Stride);
buffersLimited &= ~(1u << vbIndex);
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs
index bb4df0c06..fb3208f00 100644
--- a/src/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Window.cs
@@ -70,7 +70,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
- TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
+ TextureView viewConverted = view.Format.IsBgr ? _renderer.TextureCopy.BgraSwap(view) : view;
UpdateEffect();
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.OpenGL
viewConverted = _antiAliasing.Run(viewConverted, _width, _height);
- if (viewConverted.Format.IsBgr())
+ if (viewConverted.Format.IsBgr)
{
TextureView swappedView = _renderer.TextureCopy.BgraSwap(viewConverted);
@@ -152,14 +152,14 @@ namespace Ryujinx.Graphics.OpenGL
if (ScreenCaptureRequested)
{
- CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgr(), crop.FlipX, crop.FlipY);
+ CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgr, crop.FlipX, crop.FlipY);
ScreenCaptureRequested = false;
}
if (_scalingFilter != null)
{
- if (viewConverted.Format.IsBgr() && !_isBgra)
+ if (viewConverted.Format.IsBgr && !_isBgra)
{
RecreateUpscalingTexture(true);
}
diff --git a/src/Ryujinx.Graphics.Shader/AttributeType.cs b/src/Ryujinx.Graphics.Shader/AttributeType.cs
index 4c2913416..524453287 100644
--- a/src/Ryujinx.Graphics.Shader/AttributeType.cs
+++ b/src/Ryujinx.Graphics.Shader/AttributeType.cs
@@ -20,20 +20,18 @@ namespace Ryujinx.Graphics.Shader
static class AttributeTypeExtensions
{
- public static AggregateType ToAggregateType(this AttributeType type)
+ extension(AttributeType type)
{
- return (type & ~AttributeType.AnyPacked) switch
+ public AggregateType Aggregate =>
+ (type & ~AttributeType.AnyPacked) switch
{
AttributeType.Float => AggregateType.FP32,
AttributeType.Sint => AggregateType.S32,
AttributeType.Uint => AggregateType.U32,
_ => throw new ArgumentException($"Invalid attribute type \"{type}\"."),
};
- }
-
- public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats)
- {
- return (type & ~AttributeType.AnyPacked) switch
+
+ public AggregateType AsAggregate(bool supportsScaledFormats) => (type & ~AttributeType.AnyPacked) switch
{
AttributeType.Float => AggregateType.FP32,
AttributeType.Sint => AggregateType.S32,
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index e0d7cdc4b..a7b82e742 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
if (context.Definitions.Stage == ShaderStage.Geometry)
{
- string inPrimitive = context.Definitions.InputTopology.ToGlslString();
+ string inPrimitive = context.Definitions.InputTopology.GlslString;
context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else
{
- string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
+ string outPrimitive = context.Definitions.OutputTopology.GlslString;
int maxOutputVertices = context.Definitions.MaxOutputVertices;
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
@@ -123,8 +123,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
tessCw = !tessCw;
}
- string patchType = context.Definitions.TessPatchType.ToGlsl();
- string spacing = context.Definitions.TessSpacing.ToGlsl();
+ string patchType = context.Definitions.TessPatchType.Glsl;
+ string spacing = context.Definitions.TessSpacing.Glsl;
string windingOrder = tessCw ? "cw" : "ccw";
context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;");
@@ -351,7 +351,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
arrayDecl = "[]";
}
- string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
+ string samplerTypeName = definition.Separate ? definition.Type.GlslTextureTypeName : definition.Type.GlslSamplerTypeName;
string layout = string.Empty;
@@ -379,7 +379,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
arrayDecl = "[]";
}
- string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
+ string imageTypeName = definition.Type.GetGlslImageTypeName(definition.Format.GetComponentType());
if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
{
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index 32c930557..881ca0d47 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append('(');
texCallBuilder.Append(imageName);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount + (isArray ? 1 : 0);
@@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
AstTextureOperation texOp = (AstTextureOperation)operation;
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int coordsIndex = 0;
string samplerName = GetSamplerName(context, texOp, ref coordsIndex);
@@ -264,7 +264,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall += "(" + samplerName;
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount;
@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
- name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})";
+ name = $"{texOp.Type.GlslSamplerTypeName}({name}, {samplerName})";
}
return name;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
index c0a597a10..e1571fc78 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -385,12 +385,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void DeclarePerVertexBlock(CodeGenContext context)
{
- if (context.Definitions.Stage.IsVtg())
+ if (context.Definitions.Stage.IsVtg)
{
if (context.Definitions.Stage != ShaderStage.Vertex)
{
SpvInstruction perVertexInputStructType = CreatePerVertexStructType(context);
- int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
+ int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.InputVertexCount : 32;
SpvInstruction perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize));
SpvInstruction perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType);
SpvInstruction perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input);
@@ -537,7 +537,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
{
- int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
+ int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.InputVertexCount : 32;
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
index 27b5c21c0..83b037c1c 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -615,7 +615,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.AccessChain(imagePointerType, image, textureIndex);
}
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount + (isArray ? 1 : 0);
@@ -693,7 +693,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.Load(declaration.ImageType, image);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount + (isArray ? 1 : 0);
@@ -750,7 +750,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.Load(declaration.ImageType, image);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount + (isArray ? 1 : 0);
@@ -840,7 +840,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
- int pCount = texOp.Type.GetDimensions();
+ int pCount = texOp.Type.Dimensions;
SpvInstruction pCoords;
@@ -1164,7 +1164,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int pCount = coordsCount;
@@ -1463,7 +1463,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SamplerType type = context.SamplersTypes[texOp.GetTextureSetAndBinding()];
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
- int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
+ int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.Dimensions;
if (type.HasFlag(SamplerType.Array))
{
@@ -1486,7 +1486,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (dimensions != 1)
{
- result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
+ result = context.CompositeExtract(context.TypeS32(), result, texOp.Index);
}
return new OperationResult(AggregateType.S32, result);
diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
index ac1f24218..45250a463 100644
--- a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
+++ b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
@@ -457,7 +457,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
case AttributeConsts.ClipDistance5:
case AttributeConsts.ClipDistance6:
case AttributeConsts.ClipDistance7:
- if (definitions.Stage.IsVtg())
+ if (definitions.Stage.IsVtg)
{
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
}
diff --git a/src/Ryujinx.Graphics.Shader/InputTopology.cs b/src/Ryujinx.Graphics.Shader/InputTopology.cs
index 9438263de..074386b5d 100644
--- a/src/Ryujinx.Graphics.Shader/InputTopology.cs
+++ b/src/Ryujinx.Graphics.Shader/InputTopology.cs
@@ -11,9 +11,9 @@ namespace Ryujinx.Graphics.Shader
static class InputTopologyExtensions
{
- public static string ToGlslString(this InputTopology topology)
+ extension(InputTopology topology)
{
- return topology switch
+ public string GlslString => topology switch
{
InputTopology.Points => "points",
InputTopology.Lines => "lines",
@@ -22,11 +22,8 @@ namespace Ryujinx.Graphics.Shader
InputTopology.TrianglesAdjacency => "triangles_adjacency",
_ => "points",
};
- }
-
- public static int ToInputVertices(this InputTopology topology)
- {
- return topology switch
+
+ public int InputVertexCount => topology switch
{
InputTopology.Points => 1,
InputTopology.Lines => 2,
@@ -35,17 +32,14 @@ namespace Ryujinx.Graphics.Shader
InputTopology.TrianglesAdjacency => 6,
_ => 1,
};
- }
-
- public static int ToInputVerticesNoAdjacency(this InputTopology topology)
- {
- return topology switch
+
+ public int InputVertexCountNoAdjacency => topology switch
{
InputTopology.Points => 1,
InputTopology.Lines or
- InputTopology.LinesAdjacency => 2,
+ InputTopology.LinesAdjacency => 2,
InputTopology.Triangles or
- InputTopology.TrianglesAdjacency => 3,
+ InputTopology.TrianglesAdjacency => 3,
_ => 1,
};
}
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
index df84c38f1..4c7127274 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
@@ -105,7 +105,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
- src = Const(context.TranslatorContext.Definitions.InputTopology.ToInputVertices() << 16);
+ src = Const(context.TranslatorContext.Definitions.InputTopology.InputVertexCount << 16);
}
}
else
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
index e9f930179..2f86f044d 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
@@ -228,7 +228,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
sourcesList.Add(context.Copy(GetSrcReg(context, srcC)));
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -335,7 +335,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
sourcesList.Add(context.Copy(Register(srcC, RegisterType.Gpr)));
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -507,7 +507,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
sourcesList.Add(context.Copy(GetSrcReg(context, srcC)));
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -612,7 +612,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
sourcesList.Add(context.Copy(Register(srcC, RegisterType.Gpr)));
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 19b22e03b..fa0b87317 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -227,7 +227,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -558,7 +558,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if ((flags & TextureFlags.Offset) != 0)
{
- AddTextureOffset(type.GetDimensions(), 4, 4);
+ AddTextureOffset(type.Dimensions, 4, 4);
}
}
else if (texsType == TexsType.Tld4s)
@@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (tld4sOp.Aoffi)
{
- AddTextureOffset(type.GetDimensions(), 8, 6);
+ AddTextureOffset(type.Dimensions, 8, 6);
flags |= TextureFlags.Offset;
}
@@ -714,7 +714,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -847,7 +847,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
SamplerType type = ConvertSamplerType(dimensions);
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
bool isArray =
dimensions is TexDim.Array1d or
@@ -942,26 +942,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
- Operand Ra()
- {
- if (srcA > RegisterConsts.RegisterZeroIndex)
- {
- return Const(0);
- }
-
- return context.Copy(Register(srcA++, RegisterType.Gpr));
- }
-
- Operand Rb()
- {
- if (srcB > RegisterConsts.RegisterZeroIndex)
- {
- return Const(0);
- }
-
- return context.Copy(Register(srcB++, RegisterType.Gpr));
- }
-
TextureFlags flags = TextureFlags.Derivatives;
List sourcesList = [];
@@ -975,7 +955,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
SamplerType type = ConvertSamplerType(dimensions);
- int coordsCount = type.GetDimensions();
+ int coordsCount = type.Dimensions;
for (int index = 0; index < coordsCount; index++)
{
@@ -1051,6 +1031,28 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
EmitTextureSample(context, type, flags, imm, componentMask, dests, sources);
+
+ return;
+
+ Operand Ra()
+ {
+ if (srcA > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(srcA++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (srcB > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(srcB++, RegisterType.Gpr));
+ }
}
private static void EmitTxq(
diff --git a/src/Ryujinx.Graphics.Shader/OutputTopology.cs b/src/Ryujinx.Graphics.Shader/OutputTopology.cs
index dc4b304ad..a517fe861 100644
--- a/src/Ryujinx.Graphics.Shader/OutputTopology.cs
+++ b/src/Ryujinx.Graphics.Shader/OutputTopology.cs
@@ -9,9 +9,10 @@ namespace Ryujinx.Graphics.Shader
static class OutputTopologyExtensions
{
- public static string ToGlslString(this OutputTopology topology)
+
+ extension(OutputTopology topology)
{
- return topology switch
+ public string GlslString => topology switch
{
OutputTopology.LineStrip => "line_strip",
OutputTopology.PointList => "points",
diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs
index a693495fa..88449ef66 100644
--- a/src/Ryujinx.Graphics.Shader/SamplerType.cs
+++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs
@@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.Shader
static class SamplerTypeExtensions
{
- public static int GetDimensions(this SamplerType type)
+ extension(SamplerType type)
{
- return (type & SamplerType.Mask) switch
+ public int Dimensions => (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => 1,
SamplerType.TextureBuffer => 1,
@@ -33,127 +33,136 @@ namespace Ryujinx.Graphics.Shader
SamplerType.TextureCube => 3,
_ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
};
- }
- public static string ToShortSamplerType(this SamplerType type)
- {
- string typeName = (type & SamplerType.Mask) switch
+ public string ShortTypeName
{
- SamplerType.Texture1D => "1d",
- SamplerType.TextureBuffer => "b",
- SamplerType.Texture2D => "2d",
- SamplerType.Texture3D => "3d",
- SamplerType.TextureCube => "cube",
- _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
- };
+ get
+ {
+ string typeName = (type & SamplerType.Mask) switch
+ {
+ SamplerType.Texture1D => "1d",
+ SamplerType.TextureBuffer => "b",
+ SamplerType.Texture2D => "2d",
+ SamplerType.Texture3D => "3d",
+ SamplerType.TextureCube => "cube",
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
+ };
- if ((type & SamplerType.Multisample) != 0)
- {
- typeName += "ms";
+ if ((type & SamplerType.Multisample) != 0)
+ {
+ typeName += "ms";
+ }
+
+ if ((type & SamplerType.Array) != 0)
+ {
+ typeName += "a";
+ }
+
+ if ((type & SamplerType.Shadow) != 0)
+ {
+ typeName += "s";
+ }
+
+ return typeName;
+ }
}
- if ((type & SamplerType.Array) != 0)
+ public string GlslSamplerTypeName
{
- typeName += "a";
+ get
+ {
+ string typeName = (type & SamplerType.Mask) switch
+ {
+ SamplerType.None => "sampler",
+ SamplerType.Texture1D => "sampler1D",
+ SamplerType.TextureBuffer => "samplerBuffer",
+ SamplerType.Texture2D => "sampler2D",
+ SamplerType.Texture3D => "sampler3D",
+ SamplerType.TextureCube => "samplerCube",
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
+ };
+
+ if ((type & SamplerType.Multisample) != 0)
+ {
+ typeName += "MS";
+ }
+
+ if ((type & SamplerType.Array) != 0)
+ {
+ typeName += "Array";
+ }
+
+ if ((type & SamplerType.Shadow) != 0)
+ {
+ typeName += "Shadow";
+ }
+
+ return typeName;
+ }
}
- if ((type & SamplerType.Shadow) != 0)
+ public string GlslTextureTypeName
{
- typeName += "s";
+ get
+ {
+ string typeName = (type & SamplerType.Mask) switch
+ {
+ SamplerType.Texture1D => "texture1D",
+ SamplerType.TextureBuffer => "textureBuffer",
+ SamplerType.Texture2D => "texture2D",
+ SamplerType.Texture3D => "texture3D",
+ SamplerType.TextureCube => "textureCube",
+ _ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
+ };
+
+ if ((type & SamplerType.Multisample) != 0)
+ {
+ typeName += "MS";
+ }
+
+ if ((type & SamplerType.Array) != 0)
+ {
+ typeName += "Array";
+ }
+
+ return typeName;
+ }
}
- return typeName;
- }
-
- public static string ToGlslSamplerType(this SamplerType type)
- {
- string typeName = (type & SamplerType.Mask) switch
+ public string GetGlslImageTypeName(AggregateType componentType)
{
- SamplerType.None => "sampler",
- SamplerType.Texture1D => "sampler1D",
- SamplerType.TextureBuffer => "samplerBuffer",
- SamplerType.Texture2D => "sampler2D",
- SamplerType.Texture3D => "sampler3D",
- SamplerType.TextureCube => "samplerCube",
- _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
- };
+ string typeName = (type & SamplerType.Mask) switch
+ {
+ SamplerType.Texture1D => "image1D",
+ SamplerType.TextureBuffer => "imageBuffer",
+ SamplerType.Texture2D => "image2D",
+ SamplerType.Texture3D => "image3D",
+ SamplerType.TextureCube => "imageCube",
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
+ };
- if ((type & SamplerType.Multisample) != 0)
- {
- typeName += "MS";
+ if ((type & SamplerType.Multisample) != 0)
+ {
+ typeName += "MS";
+ }
+
+ if ((type & SamplerType.Array) != 0)
+ {
+ typeName += "Array";
+ }
+
+ switch (componentType)
+ {
+ case AggregateType.U32:
+ typeName = 'u' + typeName;
+ break;
+ case AggregateType.S32:
+ typeName = 'i' + typeName;
+ break;
+ }
+
+ return typeName;
}
-
- if ((type & SamplerType.Array) != 0)
- {
- typeName += "Array";
- }
-
- if ((type & SamplerType.Shadow) != 0)
- {
- typeName += "Shadow";
- }
-
- return typeName;
- }
-
- public static string ToGlslTextureType(this SamplerType type)
- {
- string typeName = (type & SamplerType.Mask) switch
- {
- SamplerType.Texture1D => "texture1D",
- SamplerType.TextureBuffer => "textureBuffer",
- SamplerType.Texture2D => "texture2D",
- SamplerType.Texture3D => "texture3D",
- SamplerType.TextureCube => "textureCube",
- _ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
- };
-
- if ((type & SamplerType.Multisample) != 0)
- {
- typeName += "MS";
- }
-
- if ((type & SamplerType.Array) != 0)
- {
- typeName += "Array";
- }
-
- return typeName;
- }
-
- public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
- {
- string typeName = (type & SamplerType.Mask) switch
- {
- SamplerType.Texture1D => "image1D",
- SamplerType.TextureBuffer => "imageBuffer",
- SamplerType.Texture2D => "image2D",
- SamplerType.Texture3D => "image3D",
- SamplerType.TextureCube => "imageCube",
- _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
- };
-
- if ((type & SamplerType.Multisample) != 0)
- {
- typeName += "MS";
- }
-
- if ((type & SamplerType.Array) != 0)
- {
- typeName += "Array";
- }
-
- switch (componentType)
- {
- case AggregateType.U32:
- typeName = 'u' + typeName;
- break;
- case AggregateType.S32:
- typeName = 'i' + typeName;
- break;
- }
-
- return typeName;
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/ShaderStage.cs b/src/Ryujinx.Graphics.Shader/ShaderStage.cs
index faea5c357..cbfd1aa0c 100644
--- a/src/Ryujinx.Graphics.Shader/ShaderStage.cs
+++ b/src/Ryujinx.Graphics.Shader/ShaderStage.cs
@@ -14,27 +14,23 @@ namespace Ryujinx.Graphics.Shader
public static class ShaderStageExtensions
{
- ///
- /// Checks if the shader stage supports render scale.
- ///
- /// Shader stage
- /// True if the shader stage supports render scale, false otherwise
- public static bool SupportsRenderScale(this ShaderStage stage)
+ extension(ShaderStage shaderStage)
{
- return stage is ShaderStage.Vertex or ShaderStage.Fragment or ShaderStage.Compute;
- }
-
- ///
- /// Checks if the shader stage is vertex, tessellation or geometry.
- ///
- /// Shader stage
- /// True if the shader stage is vertex, tessellation or geometry, false otherwise
- public static bool IsVtg(this ShaderStage stage)
- {
- return stage is ShaderStage.Vertex or
- ShaderStage.TessellationControl or
- ShaderStage.TessellationEvaluation or
- ShaderStage.Geometry;
+ ///
+ /// Checks if the shader stage supports render scale.
+ ///
+ public bool SupportsRenderScale =>
+ shaderStage is ShaderStage.Vertex or ShaderStage.Fragment or ShaderStage.Compute;
+
+ ///
+ /// Checks if the shader stage is vertex, tessellation or geometry.
+ ///
+ public bool IsVtg =>
+ shaderStage is ShaderStage.Vertex or
+ ShaderStage.TessellationControl or
+ ShaderStage.TessellationEvaluation or
+ ShaderStage.Geometry;
}
+
}
}
diff --git a/src/Ryujinx.Graphics.Shader/TessPatchType.cs b/src/Ryujinx.Graphics.Shader/TessPatchType.cs
index 76be22fd4..6c4fc1a30 100644
--- a/src/Ryujinx.Graphics.Shader/TessPatchType.cs
+++ b/src/Ryujinx.Graphics.Shader/TessPatchType.cs
@@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Shader
static class TessPatchTypeExtensions
{
- public static string ToGlsl(this TessPatchType type)
+ extension(TessPatchType patchType)
{
- return type switch
+ public string Glsl => patchType switch
{
TessPatchType.Isolines => "isolines",
TessPatchType.Quads => "quads",
diff --git a/src/Ryujinx.Graphics.Shader/TessSpacing.cs b/src/Ryujinx.Graphics.Shader/TessSpacing.cs
index 6035366c1..8d42ccbf1 100644
--- a/src/Ryujinx.Graphics.Shader/TessSpacing.cs
+++ b/src/Ryujinx.Graphics.Shader/TessSpacing.cs
@@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Shader
static class TessSpacingExtensions
{
- public static string ToGlsl(this TessSpacing spacing)
+ extension(TessSpacing spacing)
{
- return spacing switch
+ public string Glsl => spacing switch
{
TessSpacing.FractionalEventSpacing => "fractional_even_spacing",
TessSpacing.FractionalOddSpacing => "fractional_odd_spacing",
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index 94448626f..62dd9e2e7 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -135,7 +135,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (TranslatorContext.Stage == ShaderStage.Geometry)
{
- int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
+ int inputVertices = TranslatorContext.Definitions.InputTopology.InputVertexCount;
Operand baseVertex = this.IMultiply(outputVertexOffset, Const(inputVertices));
@@ -404,7 +404,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else
{
inputStart = 0;
- inputEnd = topology.ToInputVerticesNoAdjacency();
+ inputEnd = topology.InputVertexCountNoAdjacency;
inputStep = 1;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 1f2f79a2d..c40568a61 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -39,8 +39,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Set any destination variables to zero.
string typeName = texOp.Inst.IsImage()
- ? texOp.Type.ToGlslImageType(texOp.Format.GetComponentType())
- : texOp.Type.ToGlslTextureType();
+ ? texOp.Type.GetGlslImageTypeName(texOp.Format.GetComponentType())
+ : texOp.Type.GlslTextureTypeName;
gpuAccessor.Log($"Failed to find handle source for bindless access of type \"{typeName}\".");
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs
index bd2eceda5..ab988f70e 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs
@@ -26,12 +26,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - Both branches are jumping to the same location.
// In this case, the branch on the current block can be removed,
// as the next block is going to jump to the same place anyway.
- if (nextBlock == null)
- {
- return false;
- }
- if (nextBlock.Operations.First?.Value is not Operation next)
+ if (nextBlock?.Operations.First?.Value is not Operation next)
{
return false;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
index 2d366be71..2fc15344e 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
@@ -133,7 +133,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (stage == ShaderStage.Geometry)
{
- LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.ToInputVertices());
+ LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.InputVertexCount);
LocalGeometryOutputVertexCountMemoryId = AddMemoryDefinition("local_geometry_output_vertex", AggregateType.U32);
LocalGeometryOutputIndexCountMemoryId = AddMemoryDefinition("local_geometry_output_index", AggregateType.U32);
@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool coherent,
bool separate)
{
- int dimensions = type == SamplerType.None ? 0 : type.GetDimensions();
+ int dimensions = type == SamplerType.None ? 0 : type.Dimensions;
Dictionary dict = isImage ? _usedImages : _usedTextures;
TextureUsageFlags usageFlags = TextureUsageFlags.None;
@@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
usageFlags |= TextureUsageFlags.NeedsScaleValue;
- bool canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2;
+ bool canScale = _stage.SupportsRenderScale && arrayLength == 1 && !write && dimensions == 2;
if (!canScale)
{
@@ -355,7 +355,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (arrayLength != 1 && type != SamplerType.None)
{
- prefix += type.ToShortSamplerType();
+ prefix += type.ShortTypeName;
}
if (isImage)
@@ -432,9 +432,9 @@ namespace Ryujinx.Graphics.Shader.Translation
if (found)
{
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
-
- int dimensions = type.GetDimensions();
- bool canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2;
+
+ int dimensions = type.Dimensions;
+ bool canScale = _stage.SupportsRenderScale && selectedInfo.ArrayLength == 1 && dimensions == 2;
if (!canScale)
{
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
index 656ad6c5e..c11e65812 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
@@ -157,7 +157,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpPassthrough = gpPassthrough;
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
OutputTopology = outputTopology;
- MaxOutputVertices = gpPassthrough ? graphicsState.Topology.ToInputVerticesNoAdjacency() : maxOutputVertices;
+ MaxOutputVertices = gpPassthrough ? graphicsState.Topology.InputVertexCountNoAdjacency : maxOutputVertices;
ImapTypes = imapTypes;
OmapTargets = omapTargets;
OmapSampleMask = omapSampleMask;
@@ -293,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public AggregateType GetFragmentOutputColorType(int location)
{
- return AggregateType.Vector4 | _graphicsState.FragmentOutputTypes[location].ToAggregateType();
+ return AggregateType.Vector4 | _graphicsState.FragmentOutputTypes[location].Aggregate;
}
public AggregateType GetUserDefinedType(int location, bool isOutput)
@@ -307,7 +307,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex && !isOutput)
{
- type |= _graphicsState.AttributeTypes[location].ToAggregateType(SupportsScaledVertexFormats);
+ type |= _graphicsState.AttributeTypes[location].AsAggregate(SupportsScaledVertexFormats);
}
else
{
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
index 808692559..2ec00ce2d 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
(intCoords || isImage) &&
!isBindless &&
!isIndexed &&
- stage.SupportsRenderScale() &&
+ stage.SupportsRenderScale &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
@@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int coordsIndex = isBindless ? 1 : 0;
for (int index = 0; index < coordsCount; index++)
@@ -103,7 +103,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Index < 2 &&
!isBindless &&
!isIndexed &&
- stage.SupportsRenderScale() &&
+ stage.SupportsRenderScale &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
@@ -168,7 +168,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node;
}
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int coordsIndex = isBindless || isIndexed ? 1 : 0;
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@@ -315,7 +315,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
- int coordsCount = texOp.Type.GetDimensions();
+ int coordsCount = texOp.Type.Dimensions;
int offsetsCount;
diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
index 1e54e8ece..ca0e79b8b 100644
--- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -58,6 +58,8 @@ namespace Ryujinx.Graphics.Vulkan
private Dictionary _mirrors;
private bool _useMirrors;
+ private Action _decrementReferenceCount;
+
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
{
_gd = gd;
@@ -75,6 +77,8 @@ namespace Ryujinx.Graphics.Vulkan
_flushLock = new ReaderWriterLockSlim();
_useMirrors = gd.IsTBDR;
+
+ _decrementReferenceCount = _buffer.DecrementReferenceCount;
}
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
@@ -364,10 +368,13 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- public BufferHandle GetHandle()
+ public BufferHandle Handle
{
- ulong handle = _bufferHandle;
- return Unsafe.As(ref handle);
+ get
+ {
+ ulong handle = _bufferHandle;
+ return Unsafe.As(ref handle);
+ }
}
public nint Map(int offset, int mappingSize)
@@ -444,7 +451,7 @@ namespace Ryujinx.Graphics.Vulkan
_flushLock.ExitReadLock();
- return PinnedSpan.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
+ return PinnedSpan.UnsafeFromSpan(result, _decrementReferenceCount);
}
BackgroundResource resource = _gd.BackgroundResources.Get();
diff --git a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
index aee55fef4..605105ea8 100644
--- a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
@@ -158,16 +158,16 @@ namespace Ryujinx.Graphics.Vulkan
FormatFeatureFlags.TransferSrcBit |
FormatFeatureFlags.TransferDstBit;
- if (srcFormat.IsDepthOrStencil())
+ if (srcFormat.IsDepthOrStencil)
{
requiredFeatures |= FormatFeatureFlags.DepthStencilAttachmentBit;
}
- else if (srcFormat.IsRtColorCompatible())
+ else if (srcFormat.IsRtColorCompatible)
{
requiredFeatures |= FormatFeatureFlags.ColorAttachmentBit;
}
- if (srcFormat.IsImageCompatible() && storageFeatureFlagRequired)
+ if (srcFormat.IsImageCompatible && storageFeatureFlagRequired)
{
requiredFeatures |= FormatFeatureFlags.StorageImageBit;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index a2384c08e..919c45b9d 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -1,6 +1,7 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
+using System.Collections.Generic;
using System.Linq;
using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -10,33 +11,34 @@ namespace Ryujinx.Graphics.Vulkan
class FramebufferParams
{
private readonly Device _device;
- private readonly Auto[] _attachments;
- private readonly TextureView[] _colors;
- private readonly TextureView _depthStencil;
- private readonly TextureView[] _colorsCanonical;
- private readonly TextureView _baseAttachment;
- private readonly uint _validColorAttachments;
+ private Auto[] _attachments;
+ private TextureView[] _colors;
+ private TextureView _depthStencil;
+ private TextureView[] _colorsCanonical;
+ private TextureView _baseAttachment;
+ private uint _validColorAttachments;
+ private int _totalCount;
- public uint Width { get; }
- public uint Height { get; }
- public uint Layers { get; }
+ public uint Width { get; private set; }
+ public uint Height { get; private set; }
+ public uint Layers { get; private set; }
- public uint[] AttachmentSamples { get; }
- public VkFormat[] AttachmentFormats { get; }
- public int[] AttachmentIndices { get; }
- public uint AttachmentIntegerFormatMask { get; }
- public bool LogicOpsAllowed { get; }
+ public uint[] AttachmentSamples { get; private set; }
+ public VkFormat[] AttachmentFormats { get; private set; }
+ public int[] AttachmentIndices { get; private set; }
+ public uint AttachmentIntegerFormatMask { get; private set; }
+ public bool LogicOpsAllowed { get; private set; }
- public int AttachmentsCount { get; }
- public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1;
- public bool HasDepthStencil { get; }
+ public int AttachmentsCount { get; private set; }
+ public int MaxColorAttachmentIndex => ColorAttachmentsCount > 0 ? AttachmentIndices[ColorAttachmentsCount - 1] : -1;
+ public bool HasDepthStencil { get; private set; }
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
public FramebufferParams(Device device, TextureView view, uint width, uint height)
{
Format format = view.Info.Format;
- bool isDepthStencil = format.IsDepthOrStencil();
+ bool isDepthStencil = format.IsDepthOrStencil;
_device = device;
_attachments = [view.GetImageViewForAttachment()];
@@ -50,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan
else
{
_colors = [view];
- _colorsCanonical = _colors;
+ _colorsCanonical = [view];
}
Width = width;
@@ -60,10 +62,11 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentSamples = [(uint)view.Info.Samples];
AttachmentFormats = [view.VkFormat];
AttachmentIndices = isDepthStencil ? [] : [0];
- AttachmentIntegerFormatMask = format.IsInteger() ? 1u : 0u;
- LogicOpsAllowed = !format.IsFloatOrSrgb();
+ AttachmentIntegerFormatMask = format.IsInt ? 1u : 0u;
+ LogicOpsAllowed = !format.IsFloatOrSrgb;
AttachmentsCount = 1;
+ _totalCount = 1;
HasDepthStencil = isDepthStencil;
}
@@ -110,12 +113,12 @@ namespace Ryujinx.Graphics.Vulkan
Format format = texture.Info.Format;
- if (format.IsInteger())
+ if (format.IsInt)
{
attachmentIntegerFormatMask |= 1u << bindIndex;
}
- allFormatsFloatOrSrgb &= format.IsFloatOrSrgb();
+ allFormatsFloatOrSrgb &= format.IsFloatOrSrgb;
width = Math.Min(width, (uint)texture.Width);
height = Math.Min(height, (uint)texture.Height);
@@ -133,7 +136,7 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
LogicOpsAllowed = !allFormatsFloatOrSrgb;
- if (depthStencil is TextureView dsTexture && dsTexture.Valid)
+ if (depthStencil is TextureView { Valid: true } dsTexture)
{
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
_depthStencil = dsTexture;
@@ -159,11 +162,151 @@ namespace Ryujinx.Graphics.Vulkan
Layers = layers;
AttachmentsCount = count;
+ _totalCount = colors.Length;
+ }
+
+ public FramebufferParams Update(ITexture[] colors, ITexture depthStencil)
+ {
+ int colorsCount = colors.Count(IsValidTextureView);
+
+ int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
+
+ Array.Clear(_attachments);
+ Array.Clear(_colors);
+
+ if (_attachments.Length < count)
+ {
+ Array.Resize(ref _attachments, count);
+ }
+ if (_colors.Length < colorsCount)
+ {
+ Array.Resize(ref _colors, colorsCount);
+ }
+ if (_colorsCanonical.Length < colors.Length)
+ {
+ Array.Resize(ref _colorsCanonical, colors.Length);
+ }
+
+ for (int i = 0; i < colors.Length; i++)
+ {
+ ITexture color = colors[i];
+ if (color is TextureView { Valid: true } view)
+ {
+ _colorsCanonical[i] = view;
+ }
+ else
+ {
+ _colorsCanonical[i] = null;
+ }
+ }
+
+ Array.Clear(AttachmentSamples);
+ Array.Clear(AttachmentFormats);
+ Array.Clear(AttachmentIndices);
+
+ if (AttachmentSamples.Length < count)
+ {
+ uint[] attachmentSamples = AttachmentSamples;
+ Array.Resize(ref attachmentSamples, count);
+ AttachmentSamples = attachmentSamples;
+ }
+ if (AttachmentFormats.Length < count)
+ {
+ VkFormat[] attachmentFormats = AttachmentFormats;
+ Array.Resize(ref attachmentFormats, count);
+ AttachmentFormats = attachmentFormats;
+ }
+ if (AttachmentIndices.Length < colorsCount)
+ {
+ int[] attachmentIndices = AttachmentIndices;
+ Array.Resize(ref attachmentIndices, colorsCount);
+ AttachmentIndices = attachmentIndices;
+ }
+
+ uint width = uint.MaxValue;
+ uint height = uint.MaxValue;
+ uint layers = uint.MaxValue;
+
+ int index = 0;
+ uint attachmentIntegerFormatMask = 0;
+ bool allFormatsFloatOrSrgb = colorsCount != 0;
+
+ _validColorAttachments = 0;
+ _baseAttachment = null;
+
+ for (int bindIndex = 0; bindIndex < colors.Length; bindIndex++)
+ {
+ TextureView texture = _colorsCanonical[bindIndex];
+ if (texture is not null)
+ {
+ _attachments[index] = texture.GetImageViewForAttachment();
+ _colors[index] = texture;
+ _validColorAttachments |= 1u << bindIndex;
+ _baseAttachment = texture;
+
+ AttachmentSamples[index] = (uint)texture.Info.Samples;
+ AttachmentFormats[index] = texture.VkFormat;
+ AttachmentIndices[index] = bindIndex;
+
+ Format format = texture.Info.Format;
+
+ if (format.IsInt)
+ {
+ attachmentIntegerFormatMask |= 1u << bindIndex;
+ }
+
+ allFormatsFloatOrSrgb &= format.IsFloatOrSrgb;
+
+ width = Math.Min(width, (uint)texture.Width);
+ height = Math.Min(height, (uint)texture.Height);
+ layers = Math.Min(layers, (uint)texture.Layers);
+
+ if (++index >= colorsCount)
+ {
+ break;
+ }
+ }
+ }
+
+ AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
+ LogicOpsAllowed = !allFormatsFloatOrSrgb;
+ _depthStencil = null;
+ HasDepthStencil = false;
+
+ if (depthStencil is TextureView { Valid: true } dsTexture)
+ {
+ _attachments[count - 1] = dsTexture.GetImageViewForAttachment();
+ _depthStencil = dsTexture;
+ _baseAttachment ??= dsTexture;
+
+ AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
+ AttachmentFormats[count - 1] = dsTexture.VkFormat;
+
+ width = Math.Min(width, (uint)dsTexture.Width);
+ height = Math.Min(height, (uint)dsTexture.Height);
+ layers = Math.Min(layers, (uint)dsTexture.Layers);
+
+ HasDepthStencil = true;
+ }
+
+ if (count == 0)
+ {
+ width = height = layers = 1;
+ }
+
+ Width = width;
+ Height = height;
+ Layers = layers;
+
+ AttachmentsCount = count;
+ _totalCount = colors.Length;
+
+ return this;
}
public Auto GetAttachment(int index)
{
- if ((uint)index >= _attachments.Length)
+ if ((uint)index >= AttachmentsCount)
{
return null;
}
@@ -183,16 +326,16 @@ namespace Ryujinx.Graphics.Vulkan
public ComponentType GetAttachmentComponentType(int index)
{
- if (_colors != null && (uint)index < _colors.Length)
+ if (_colors != null && (uint)index < ColorAttachmentsCount)
{
Format format = _colors[index].Info.Format;
- if (format.IsSint())
+ if (format.IsSignedInt)
{
return ComponentType.SignedInteger;
}
- if (format.IsUint())
+ if (format.IsUnsignedInt)
{
return ComponentType.UnsignedInteger;
}
@@ -218,7 +361,7 @@ namespace Ryujinx.Graphics.Vulkan
private static bool IsValidTextureView(ITexture texture)
{
- return texture is TextureView view && view.Valid;
+ return texture is TextureView { Valid: true };
}
public ClearRect GetClearRect(Rectangle scissor, int layer, int layerCount)
@@ -233,9 +376,9 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe Auto Create(Vk api, CommandBufferScoped cbs, Auto renderPass)
{
- ImageView* attachments = stackalloc ImageView[_attachments.Length];
+ ImageView* attachments = stackalloc ImageView[AttachmentsCount];
- for (int i = 0; i < _attachments.Length; i++)
+ for (int i = 0; i < AttachmentsCount; i++)
{
attachments[i] = _attachments[i].Get(cbs).Value;
}
@@ -244,7 +387,7 @@ namespace Ryujinx.Graphics.Vulkan
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = renderPass.Get(cbs).Value,
- AttachmentCount = (uint)_attachments.Length,
+ AttachmentCount = (uint)AttachmentsCount,
PAttachments = attachments,
Width = Width,
Height = Height,
@@ -252,14 +395,13 @@ namespace Ryujinx.Graphics.Vulkan
};
api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out Framebuffer framebuffer).ThrowOnError();
- return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
+ return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments[..AttachmentsCount]);
}
public TextureView[] GetAttachmentViews()
{
- TextureView[] result = new TextureView[_attachments.Length];
-
- _colors?.CopyTo(result, 0);
+ TextureView[] result = new TextureView[AttachmentsCount];
+ _colors?.AsSpan(..ColorAttachmentsCount).CopyTo(result.AsSpan());
if (_depthStencil != null)
{
@@ -278,8 +420,11 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_colors != null)
{
- foreach (TextureView color in _colors)
+ int count = ColorAttachmentsCount;
+
+ for (int i = 0; i < count; i++)
{
+ TextureView color = _colors[i];
// If Clear or DontCare were used, this would need to be write bit.
color.Storage?.QueueLoadOpBarrier(cbs, false);
}
@@ -294,8 +439,11 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_colors != null)
{
- foreach (TextureView color in _colors)
+ int count = ColorAttachmentsCount;
+
+ for (int i = 0; i < count; i++)
{
+ TextureView color = _colors[i];
color.Storage?.AddStoreOpUsage(false);
}
}
@@ -307,7 +455,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_depthStencil?.Storage.ClearBindings();
- for (int i = 0; i < _colorsCanonical.Length; i++)
+ for (int i = 0; i < _totalCount; i++)
{
_colorsCanonical[i]?.Storage.ClearBindings();
}
@@ -317,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_depthStencil?.Storage.AddBinding(_depthStencil);
- for (int i = 0; i < _colorsCanonical.Length; i++)
+ for (int i = 0; i < _totalCount; i++)
{
TextureView color = _colorsCanonical[i];
color?.Storage.AddBinding(color);
diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
index a60e060c0..dbb5ee224 100644
--- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -402,14 +402,14 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
- bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
+ bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil;
if (dstIsDepthOrStencil)
{
- _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
+ _pipeline.SetProgram(src.Info.Target.IsMultisample ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
- else if (src.Info.Target.IsMultisample())
+ else if (src.Info.Target.IsMultisample)
{
_pipeline.SetProgram(_programColorBlitMs);
}
@@ -566,12 +566,12 @@ namespace Ryujinx.Graphics.Vulkan
if (isDepth)
{
- _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
+ _pipeline.SetProgram(src.Info.Target.IsMultisample ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
else
{
- _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit);
+ _pipeline.SetProgram(src.Info.Target.IsMultisample ? _programStencilBlitMs : _programStencilBlit);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
}
@@ -1047,7 +1047,7 @@ namespace Ryujinx.Graphics.Vulkan
Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
int samples = src.Info.Samples;
- bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil();
+ bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil;
ImageAspectFlags aspectFlags = src.Info.Format.ConvertAspectFlags();
// X and Y are the expected texture samples.
@@ -1173,7 +1173,7 @@ namespace Ryujinx.Graphics.Vulkan
Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
int samples = dst.Info.Samples;
- bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil();
+ bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil;
ImageAspectFlags aspectFlags = src.Info.Format.ConvertAspectFlags();
// X and Y are the expected texture samples.
diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
index e9ef39cda..40ad7716d 100644
--- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
+using System.Buffers;
namespace Ryujinx.Graphics.Vulkan
{
@@ -11,7 +12,7 @@ namespace Ryujinx.Graphics.Vulkan
{
private const int BufferUsageTrackingGranularity = 4096;
- private readonly FenceHolder[] _fences;
+ public FenceHolder[] Fences { get; }
private readonly BufferUsageBitmap _bufferUsageBitmap;
///
@@ -19,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
///
public MultiFenceHolder()
{
- _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
+ Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
}
///
@@ -28,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
/// Size of the buffer
public MultiFenceHolder(int size)
{
- _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
+ Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
@@ -90,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
/// True if the command buffer's previous fence value was null
public bool AddFence(int cbIndex, FenceHolder fence)
{
- ref FenceHolder fenceRef = ref _fences[cbIndex];
+ ref FenceHolder fenceRef = ref Fences[cbIndex];
if (fenceRef == null)
{
@@ -107,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan
/// Command buffer index of the command buffer that owns the fence
public void RemoveFence(int cbIndex)
{
- _fences[cbIndex] = null;
+ Fences[cbIndex] = null;
}
///
@@ -117,7 +118,7 @@ namespace Ryujinx.Graphics.Vulkan
/// True if referenced, false otherwise
public bool HasFence(int cbIndex)
{
- return _fences[cbIndex] != null;
+ return Fences[cbIndex] != null;
}
///
@@ -227,9 +228,9 @@ namespace Ryujinx.Graphics.Vulkan
{
int count = 0;
- for (int i = 0; i < _fences.Length; i++)
+ for (int i = 0; i < Fences.Length; i++)
{
- FenceHolder fence = _fences[i];
+ FenceHolder fence = Fences[i];
if (fence != null)
{
@@ -251,9 +252,9 @@ namespace Ryujinx.Graphics.Vulkan
{
int count = 0;
- for (int i = 0; i < _fences.Length; i++)
+ for (int i = 0; i < Fences.Length; i++)
{
- FenceHolder fence = _fences[i];
+ FenceHolder fence = Fences[i];
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index fb244d307..f2f68378f 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -1181,7 +1181,7 @@ namespace Ryujinx.Graphics.Vulkan
if (!attribute.IsZero)
{
- newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.GetScalarSize());
+ newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.ScalarSize);
dirtyVbSizes |= 1u << rawIndex;
}
@@ -1453,7 +1453,7 @@ namespace Ryujinx.Graphics.Vulkan
FramebufferParams?.ClearBindings();
}
- FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
+ FramebufferParams = FramebufferParams?.Update(colors, depthStencil) ?? new FramebufferParams(Device, colors, depthStencil);
if (IsMainPipeline)
{
@@ -1471,18 +1471,18 @@ namespace Ryujinx.Graphics.Vulkan
protected void UpdatePipelineAttachmentFormats()
{
Span dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
- FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
+ FramebufferParams.AttachmentFormats.AsSpan(..FramebufferParams.AttachmentsCount).CopyTo(dstAttachmentFormats);
_newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
_newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed;
- for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
+ for (int i = FramebufferParams.AttachmentsCount; i < dstAttachmentFormats.Length; i++)
{
dstAttachmentFormats[i] = 0;
}
_newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1);
_newState.HasDepthStencil = FramebufferParams.HasDepthStencil;
- _newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1;
+ _newState.SamplesCount = FramebufferParams.AttachmentsCount != 0 ? FramebufferParams.AttachmentSamples[0] : 1;
}
protected unsafe void CreateRenderPass()
@@ -1575,7 +1575,7 @@ namespace Ryujinx.Graphics.Vulkan
// May need to enforce feedback loop layout here in the future.
// Though technically, it should always work with the general layout.
- if (view.Info.Format.IsDepthOrStencil())
+ if (view.Info.Format.IsDepthOrStencil)
{
if (_passWritesDepthStencil)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index c259d91a9..e0bff2c15 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
int maxColorAttachmentIndex = -1;
bool isNotMsOrSupportsStorage = gd.Capabilities.SupportsShaderStorageImageMultisample ||
- !state.DepthStencilFormat.IsImageCompatible();
+ !state.DepthStencilFormat.IsImageCompatible;
Span attachmentEnableSpan = state.AttachmentEnable.AsSpan();
Span attachmentFormatsSpan = state.AttachmentFormats.AsSpan();
@@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
if (attachmentEnableSpan[i])
{
bool isNotMsOrSupportsStorageAttachments = gd.Capabilities.SupportsShaderStorageImageMultisample ||
- !attachmentFormatsSpan[i].IsImageCompatible();
+ !attachmentFormatsSpan[i].IsImageCompatible;
attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(attachmentFormatsSpan[i], isNotMsOrSupportsStorageAttachments);
@@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < attachmentCount; i++)
{
- int bindIndex = attachmentIndices[i];
+ //int bindIndex = attachmentIndices[i];
attachmentDescs[i] = new AttachmentDescription(
0,
@@ -242,7 +242,7 @@ namespace Ryujinx.Graphics.Vulkan
if (!attribute.IsZero && bufferIndex < vbCount)
{
- vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.GetScalarSize(), vbScalarSizes[bufferIndex - 1]);
+ vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.ScalarSize, vbScalarSizes[bufferIndex - 1]);
}
}
@@ -320,23 +320,23 @@ namespace Ryujinx.Graphics.Vulkan
if (attachmentEnableSpan[i])
{
bool isNotMsOrSupportsStorage = gd.Capabilities.SupportsShaderStorageImageMultisample ||
- !attachmentFormatsSpan[i].IsImageCompatible();
+ !attachmentFormatsSpan[i].IsImageCompatible;
pAttachmentFormatsSpan[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(attachmentFormatsSpan[i], isNotMsOrSupportsStorage);
maxColorAttachmentIndex = i;
- if (attachmentFormatsSpan[i].IsInteger())
+ if (attachmentFormatsSpan[i].IsInt)
{
attachmentIntegerFormatMask |= 1u << i;
}
- allFormatsFloatOrSrgb &= attachmentFormatsSpan[i].IsFloatOrSrgb();
+ allFormatsFloatOrSrgb &= attachmentFormatsSpan[i].IsFloatOrSrgb;
}
}
if (state.DepthStencilEnable)
{
- bool isNotMsOrSupportsStorage = !state.DepthStencilFormat.IsImageCompatible() ||
+ bool isNotMsOrSupportsStorage = !state.DepthStencilFormat.IsImageCompatible ||
gd.Capabilities.SupportsShaderStorageImageMultisample;
pAttachmentFormatsSpan[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat, isNotMsOrSupportsStorage);
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
index 36f5804bb..75fff2034 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -647,7 +647,7 @@ namespace Ryujinx.Graphics.Vulkan
{
result.ThrowOnError();
}
- else if (result.IsError())
+ else if (result.IsError)
{
program.AddGraphicsPipeline(ref Internal, null);
diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
index 2c2d381a9..7b477366b 100644
--- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
@@ -178,17 +178,16 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_forcedFences.Count > 0)
{
- _forcedFences.RemoveAll((entry) =>
+ for (int i = 0; i < _forcedFences.Count; i++)
{
- if (entry.Texture.Disposed)
+ if (_forcedFences[i].Texture.Disposed)
{
- return true;
+ _forcedFences.RemoveAt(i--);
+ continue;
}
-
- entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
-
- return false;
- });
+
+ _forcedFences[i].Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, _forcedFences[i].StageFlags);
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
index 5f6214a4f..149759906 100644
--- a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
+++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Silk.NET.Vulkan;
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -192,6 +193,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_firstHandle = first.ID + 1;
_handles.RemoveAt(0);
+ ArrayPool.Shared.Return(first.Waitable.Fences);
first.Waitable = null;
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
index e0de5692c..aae3b0afb 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageBlit.SrcOffsetsBuffer srcOffsets = new();
ImageBlit.DstOffsetsBuffer dstOffsets = new();
- Filter filter = linearFilter && !dstInfo.Format.IsDepthOrStencil() ? Filter.Linear : Filter.Nearest;
+ Filter filter = linearFilter && !dstInfo.Format.IsDepthOrStencil ? Filter.Linear : Filter.Nearest;
TextureView.InsertImageBarrier(
api,
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index 3dc605891..46cd5b4be 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -77,7 +77,7 @@ namespace Ryujinx.Graphics.Vulkan
_device = device;
_info = info;
- bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample();
+ bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
uint levels = (uint)info.Levels;
@@ -311,16 +311,16 @@ namespace Ryujinx.Graphics.Vulkan
{
ImageUsageFlags usage = DefaultUsageFlags;
- if (format.IsDepthOrStencil())
+ if (format.IsDepthOrStencil)
{
usage |= ImageUsageFlags.DepthStencilAttachmentBit;
}
- else if (format.IsRtColorCompatible())
+ else if (format.IsRtColorCompatible)
{
usage |= ImageUsageFlags.ColorAttachmentBit;
}
- if ((format.IsImageCompatible() && isMsImageStorageSupported) || extendedUsage)
+ if ((format.IsImageCompatible && isMsImageStorageSupported) || extendedUsage)
{
usage |= ImageUsageFlags.StorageBit;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index 1cbb7c6e1..4513c804f 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Textures.Add(this);
- bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample();
+ bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported, false);
@@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
- if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
+ if (info.Format.IsImageCompatible && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample))
{
shaderUsage |= ImageUsageFlags.StorageBit;
}
@@ -150,7 +150,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
{
- if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil()))
+ if (levels == 1 && (info.Format.IsRtColorCompatible || info.Format.IsDepthOrStencil))
{
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1);
@@ -225,12 +225,12 @@ namespace Ryujinx.Graphics.Vulkan
Image srcImage = src.GetImage().Get(cbs).Value;
Image dstImage = dst.GetImage().Get(cbs).Value;
- if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
+ if (!dst.Info.Target.IsMultisample && Info.Target.IsMultisample)
{
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
_gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, 0, firstLayer, layers);
}
- else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
+ else if (dst.Info.Target.IsMultisample && !Info.Target.IsMultisample)
{
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
_gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, 0, firstLayer, layers);
@@ -241,7 +241,7 @@ namespace Ryujinx.Graphics.Vulkan
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
}
- else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
+ else if (src.Info.Format.IsDepthOrStencil != dst.Info.Format.IsDepthOrStencil)
{
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
@@ -285,11 +285,11 @@ namespace Ryujinx.Graphics.Vulkan
Image srcImage = src.GetImage().Get(cbs).Value;
Image dstImage = dst.GetImage().Get(cbs).Value;
- if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
+ if (!dst.Info.Target.IsMultisample && Info.Target.IsMultisample)
{
_gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
}
- else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
+ else if (dst.Info.Target.IsMultisample && !Info.Target.IsMultisample)
{
_gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
}
@@ -297,7 +297,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
- else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
+ else if (src.Info.Format.IsDepthOrStencil != dst.Info.Format.IsDepthOrStencil)
{
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
@@ -370,7 +370,7 @@ namespace Ryujinx.Graphics.Vulkan
src.Height == dst.Height &&
src.VkFormat == dst.VkFormat)
{
- if (src.Info.Samples > 1 && src.Info.Samples != dst.Info.Samples && src.Info.Format.IsDepthOrStencil())
+ if (src.Info.Samples > 1 && src.Info.Samples != dst.Info.Samples && src.Info.Format.IsDepthOrStencil)
{
// CmdResolveImage does not support depth-stencil resolve, so we need to use an alternative path
// for those textures.
@@ -424,7 +424,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
+ bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil;
if (!VulkanConfiguration.UseUnsafeBlit || (_gd.Vendor != Vendor.Nvidia && _gd.Vendor != Vendor.Intel))
{
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
index 5d67ab838..1ccf0363a 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
@@ -5,18 +5,17 @@ namespace Ryujinx.Graphics.Vulkan
{
static class ResultExtensions
{
- public static bool IsError(this Result result)
+ extension(Result result)
{
- // Only negative result codes are errors.
- return result < Result.Success;
- }
+ public bool IsError => result < Result.Success;
- public static void ThrowOnError(this Result result)
- {
- // Only negative result codes are errors.
- if (result.IsError())
+ public void ThrowOnError()
{
- throw new VulkanException(result);
+ // Only negative result codes are errors.
+ if (result.IsError)
+ {
+ throw new VulkanException(result);
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index b67b0dbfa..0a0d970c1 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -401,7 +401,7 @@ namespace Ryujinx.Graphics.Vulkan
cbs = _gd.CommandBufferPool.Rent();
}
- CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
+ CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr, crop.FlipX, crop.FlipY);
ScreenCaptureRequested = false;
}
diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
index 9c6bfced6..9f38de42b 100644
--- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs
+++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
@@ -194,11 +194,11 @@ namespace Ryujinx.HLE.FileSystem
// TODO: Check Aoc version.
if (!AocData.TryAdd(titleId, new AocItem(containerPath, ncaPath)))
{
- Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}");
+ Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16} @ '{containerPath}'");
}
else
{
- Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}");
+ Logger.Notice.Print(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16} @ '{containerPath}'");
if (!mergedToContainer)
{
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs
index 5424342fb..98f122e37 100644
--- a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs
@@ -891,10 +891,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
result = new NestedName(name, prev);
}
- if (context != null)
- {
- context.FinishWithTemplateArguments = false;
- }
+ context?.FinishWithTemplateArguments = false;
return result;
}
@@ -1074,10 +1071,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
- if (context != null)
- {
- context.CtorDtorConversion = true;
- }
+ context?.CtorDtorConversion = true;
return new ConversionOperatorType(type);
default:
@@ -1349,10 +1343,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
- if (context != null)
- {
- context.CtorDtorConversion = true;
- }
+ context?.CtorDtorConversion = true;
if (isInherited && ParseName(context) == null)
{
@@ -1372,10 +1363,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
- if (context != null)
- {
- context.CtorDtorConversion = true;
- }
+ context?.CtorDtorConversion = true;
return new CtorDtorNameType(prev, true);
}
@@ -3005,16 +2993,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
BaseNode result = null;
CvType cv = new(ParseCvQualifiers(), null);
- if (context != null)
- {
- context.Cv = cv;
- }
+ context?.Cv = cv;
SimpleReferenceType Ref = ParseRefQualifiers();
- if (context != null)
- {
- context.Ref = Ref;
- }
+ context?.Ref = Ref;
if (ConsumeIf("St"))
{
@@ -3060,10 +3042,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
}
result = new NameTypeWithTemplateArguments(result, templateArgument);
- if (context != null)
- {
- context.FinishWithTemplateArguments = true;
- }
+ context?.FinishWithTemplateArguments = true;
_substitutionList.Add(result);
continue;
@@ -3256,10 +3235,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
- if (context != null)
- {
- context.FinishWithTemplateArguments = true;
- }
+ context?.FinishWithTemplateArguments = true;
return new NameTypeWithTemplateArguments(substitution, templateArguments);
}
@@ -3279,10 +3255,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
- if (context != null)
- {
- context.FinishWithTemplateArguments = true;
- }
+ context?.FinishWithTemplateArguments = true;
return new NameTypeWithTemplateArguments(result, templateArguments);
}
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 517f8ef16..83aaa1f4d 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -122,8 +122,8 @@ namespace Ryujinx.HLE.HOS
TickSource,
device,
device.Memory,
- device.Configuration.MemoryConfiguration.ToKernelMemorySize(),
- device.Configuration.MemoryConfiguration.ToKernelMemoryArrange());
+ device.Configuration.MemoryConfiguration.KernelMemorySize,
+ device.Configuration.MemoryConfiguration.KernelMemoryArrange);
Device = device;
diff --git a/src/Ryujinx.HLE/HOS/HorizonFsClient.cs b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs
index 56bc3bec3..cffd89413 100644
--- a/src/Ryujinx.HLE/HOS/HorizonFsClient.cs
+++ b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS
public Result GetFileSize(out long size, FileHandle handle)
{
- return _fsClient.GetFileSize(out size, (LibHac.Fs.FileHandle)handle.Value).ToHorizonResult();
+ return _fsClient.GetFileSize(out size, (LibHac.Fs.FileHandle)handle.Value).Horizon;
}
public Result MountSystemData(string mountName, ulong dataId)
@@ -58,7 +58,7 @@ namespace Ryujinx.HLE.HOS
using IFileSystem ncaFileSystem = nca.OpenFileSystem(NcaSectionType.Data, _system.FsIntegrityCheckLevel);
using UniqueRef ncaFsRef = new(ncaFileSystem);
- Result result = _fsClient.Register(mountName.ToU8Span(), ref ncaFsRef.Ref).ToHorizonResult();
+ Result result = _fsClient.Register(mountName.ToU8Span(), ref ncaFsRef.Ref).Horizon;
if (result.IsFailure)
{
ncaStorage.Dispose();
@@ -74,14 +74,14 @@ namespace Ryujinx.HLE.HOS
{
ncaStorage?.Dispose();
- return ex.ResultValue.ToHorizonResult();
+ return ex.ResultValue.Horizon;
}
}
}
// TODO: Return correct result here, this is likely wrong.
- return LibHac.Fs.ResultFs.TargetNotFound.Handle().ToHorizonResult();
+ return LibHac.Fs.ResultFs.TargetNotFound.Handle().Horizon;
}
public Result OpenFile(out FileHandle handle, string path, OpenMode openMode)
@@ -89,7 +89,7 @@ namespace Ryujinx.HLE.HOS
LibHac.Result result = _fsClient.OpenFile(out LibHac.Fs.FileHandle libhacHandle, path.ToU8Span(), (LibHac.Fs.OpenMode)openMode);
handle = new(libhacHandle);
- return result.ToHorizonResult();
+ return result.Horizon;
}
public Result QueryMountSystemDataCacheSize(out long size, ulong dataId)
@@ -103,7 +103,7 @@ namespace Ryujinx.HLE.HOS
public Result ReadFile(FileHandle handle, long offset, Span destination)
{
- return _fsClient.ReadFile((LibHac.Fs.FileHandle)handle.Value, offset, destination).ToHorizonResult();
+ return _fsClient.ReadFile((LibHac.Fs.FileHandle)handle.Value, offset, destination).Horizon;
}
public void Unmount(string mountName)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
index 50fe01069..fba8d0590 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System.Collections.Generic;
@@ -5,6 +6,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{
class KSynchronizationObject : KAutoObject
{
+ private static readonly ObjectPool> _nodePool = new(() => new LinkedListNode(null));
+
public LinkedList WaitingThreads { get; }
public KSynchronizationObject(KernelContext context) : base(context)
@@ -14,12 +17,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public LinkedListNode AddWaitingThread(KThread thread)
{
- return WaitingThreads.AddLast(thread);
+ LinkedListNode node = _nodePool.Allocate();
+ node.Value = thread;
+ WaitingThreads.AddLast(node);
+ return node;
}
public void RemoveWaitingThread(LinkedListNode node)
{
WaitingThreads.Remove(node);
+ _nodePool.Release(node);
}
public virtual void Signal()
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
index d2c4aadf3..25be81b10 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
@@ -110,7 +110,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong size = PagesCount * KPageTableBase.PageSize;
- return new KMemoryInfo(
+ return KMemoryInfo.Pool.Allocate().Set(
+ BaseAddress,
+ size,
+ State,
+ Permission,
+ Attribute,
+ SourcePermission,
+ IpcRefCount,
+ DeviceRefCount);
+ }
+
+ public KMemoryInfo GetInfo(KMemoryInfo oldInfo)
+ {
+ ulong size = PagesCount * KPageTableBase.PageSize;
+
+ return oldInfo.Set(
BaseAddress,
size,
State,
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
index 4db484d04..087eaea1d 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
@@ -1,19 +1,23 @@
+using Ryujinx.Common;
+
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KMemoryInfo
{
- public ulong Address { get; }
- public ulong Size { get; }
+ public static readonly ObjectPool Pool = new(() => new KMemoryInfo());
+
+ public ulong Address { get; private set; }
+ public ulong Size { get; private set; }
- public MemoryState State { get; }
- public KMemoryPermission Permission { get; }
- public MemoryAttribute Attribute { get; }
- public KMemoryPermission SourcePermission { get; }
+ public MemoryState State { get; private set; }
+ public KMemoryPermission Permission { get; private set; }
+ public MemoryAttribute Attribute { get;private set; }
+ public KMemoryPermission SourcePermission { get; private set; }
- public int IpcRefCount { get; }
- public int DeviceRefCount { get; }
+ public int IpcRefCount { get; private set; }
+ public int DeviceRefCount { get; private set; }
- public KMemoryInfo(
+ public KMemoryInfo Set(
ulong address,
ulong size,
MemoryState state,
@@ -31,6 +35,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
SourcePermission = sourcePermission;
IpcRefCount = ipcRefCount;
DeviceRefCount = deviceRefCount;
+
+ return this;
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
index 32734574e..f5baede08 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
@@ -25,17 +25,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
MemoryPermission output = MemoryPermission.None;
- if (permission.HasFlag(KMemoryPermission.Read))
+ if ((permission & KMemoryPermission.Read) == KMemoryPermission.Read)
{
output = MemoryPermission.Read;
}
- if (permission.HasFlag(KMemoryPermission.Write))
+ if ((permission & KMemoryPermission.Write) == KMemoryPermission.Write)
{
output |= MemoryPermission.Write;
}
- if (permission.HasFlag(KMemoryPermission.Execute))
+ if ((permission & KMemoryPermission.Execute) == KMemoryPermission.Execute)
{
output |= MemoryPermission.Execute;
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
index bc59b0b4d..8f56879ed 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
@@ -998,7 +998,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
else
{
- return new KMemoryInfo(
+ return KMemoryInfo.Pool.Allocate().Set(
AddrSpaceEnd,
~AddrSpaceEnd + 1,
MemoryState.Reserved,
@@ -2544,10 +2544,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryPermission firstPermission = info.Permission;
MemoryAttribute firstAttribute = info.Attribute;
- do
+ info = currBlock.GetInfo(info);
+
+ while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null)
{
- info = currBlock.GetInfo();
-
// Check if the block state matches what we expect.
if (firstState != info.State ||
firstPermission != info.Permission ||
@@ -2559,11 +2559,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
outState = MemoryState.Unmapped;
outPermission = KMemoryPermission.None;
outAttribute = MemoryAttribute.None;
+
+ KMemoryInfo.Pool.Release(info);
return false;
}
+
+ info = currBlock.GetInfo(info);
}
- while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null);
+
+ KMemoryInfo.Pool.Release(info);
outState = firstState;
outPermission = firstPermission;
@@ -2582,16 +2587,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute attributeMask,
MemoryAttribute attributeExpected)
{
- foreach (KMemoryInfo info in IterateOverRange(address, address + size))
+ KMemoryBlock currBlock = _blockManager.FindBlock(address);
+
+ KMemoryInfo info = currBlock.GetInfo();
+
+ while (info.Address + info.Size - 1 < address + size - 1 && (currBlock = currBlock.Successor) != null)
{
// Check if the block state matches what we expect.
if ((info.State & stateMask) != stateExpected ||
(info.Permission & permissionMask) != permissionExpected ||
(info.Attribute & attributeMask) != attributeExpected)
{
+ KMemoryInfo.Pool.Release(info);
+
return false;
}
+
+ info = currBlock.GetInfo(info);
}
+
+ KMemoryInfo.Pool.Release(info);
return true;
}
@@ -2641,6 +2656,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
ulong currBaseAddr = info.Address + reservedPagesCount * PageSize;
ulong currEndAddr = info.Address + info.Size;
+
+ KMemoryInfo.Pool.Release(info);
if (aslrAddress >= regionStart &&
aslrAddress >= currBaseAddr &&
@@ -2721,6 +2738,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
allocationEndAddr <= regionEndAddr &&
allocationEndAddr <= currEndAddr)
{
+ KMemoryInfo.Pool.Release(info);
return address;
}
}
@@ -2731,9 +2749,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
break;
}
-
- info = currBlock.GetInfo();
+
+ info = currBlock.GetInfo(info);
}
+
+ KMemoryInfo.Pool.Release(info);
return 0;
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
index dd133ee15..845ac850c 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
@@ -4,19 +4,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
static class CapabilityExtensions
{
+ extension(CapabilityType type)
+ {
+ public uint Flag => (uint)type + 1;
+
+ public uint Id => (uint)BitOperations.TrailingZeroCount(type.Flag);
+ }
+
public static CapabilityType GetCapabilityType(this uint cap)
{
return (CapabilityType)(((cap + 1) & ~cap) - 1);
}
-
- public static uint GetFlag(this CapabilityType type)
- {
- return (uint)type + 1;
- }
-
- public static uint GetId(this CapabilityType type)
- {
- return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
- }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
index 71ee7a086..8146538f9 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -386,6 +386,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
ulong rwdataStart = roInfo.Address + roInfo.Size;
+
+ KMemoryInfo.Pool.Release(roInfo);
try
{
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
index 745d3edd8..2d9d0ef47 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
@@ -133,7 +133,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success;
}
- int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
+ int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.Flag + 1));
// Check if the property was already set.
if (((mask0 & codeMask) & 0x1e008) != 0)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
index 62b57da04..278a9b2ff 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common;
@@ -11,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter
{
private const int HasListenersMask = 0x40000000;
+ private static readonly ObjectPool _threadArrayPool = new(() => []);
private readonly KernelContext _context;
@@ -198,9 +200,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
_context.CriticalSection.Enter();
- WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address);
+ static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
+ {
+ return thread.CondVarAddress == address;
+ }
- if (!_condVarThreads.Any(x => x.CondVarAddress == address))
+ int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
+
+ if (validThreads == 0)
{
KernelTransfer.KernelToUser(address, 0);
}
@@ -480,9 +487,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// or negative. It is incremented if there are no threads waiting.
int waitingCount = 0;
- foreach (KThread thread in _arbiterThreads.Where(x => x.MutexAddress == address))
+ foreach (KThread thread in _arbiterThreads)
{
- if (++waitingCount >= count)
+ if (thread.MutexAddress == address &&
+ ++waitingCount >= count)
{
break;
}
@@ -553,23 +561,55 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false;
}
- WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address);
+ static bool ArbiterThreadPredecate(KThread thread, ulong address)
+ {
+ return thread.MutexAddress == address;
+ }
+
+ WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
}
- private static void WakeThreads(
+ private static int WakeThreads(
List threads,
int count,
Action removeCallback,
- Func predicate)
+ Func predicate,
+ ulong address = 0)
{
- IOrderedEnumerable candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority);
- KThread[] toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray();
+ KThread[] candidates = _threadArrayPool.Allocate();
+ if (candidates.Length < threads.Count)
+ {
+ Array.Resize(ref candidates, threads.Count);
+ }
+
+ int validCount = 0;
+
+ for (int i = 0; i < threads.Count; i++)
+ {
+ if (predicate(threads[i], address))
+ {
+ candidates[validCount++] = threads[i];
+ }
+ }
+
+ Span candidatesSpan = candidates.AsSpan(..validCount);
+
+ candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
- foreach (KThread thread in toSignal)
+ if (count > 0)
+ {
+ candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
+ }
+
+ foreach (KThread thread in candidatesSpan)
{
removeCallback(thread);
threads.Remove(thread);
}
+
+ _threadArrayPool.Release(candidates);
+
+ return validCount;
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
index b97ba705c..330624b2b 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -174,10 +174,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (previousThread != nextThread)
{
- if (previousThread != null)
- {
- previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
- }
+ previousThread?.LastScheduledTime = PerformanceCounter.ElapsedTicks;
_state.SelectedThread = nextThread;
_state.NeedsScheduling = true;
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 2ebcd80d7..64cd4d595 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -48,8 +48,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KThreadContext ThreadContext { get; private set; }
- public int DynamicPriority { get; set; }
- public ulong AffinityMask { get; set; }
+ public int DynamicPriority { get; private set; }
+ public ulong AffinityMask { get; private set; }
public ulong ThreadUid { get; private set; }
@@ -83,18 +83,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public long LastScheduledTime { get; set; }
- public LinkedListNode[] SiblingsPerCore { get; private set; }
+ public readonly LinkedListNode[] SiblingsPerCore;
public LinkedList Withholder { get; set; }
- public LinkedListNode WithholderNode { get; set; }
+ public readonly LinkedListNode WithholderNode;
- public LinkedListNode ProcessListNode { get; set; }
+ public readonly LinkedListNode ProcessListNode;
private readonly LinkedList _mutexWaiters;
- private LinkedListNode _mutexWaiterNode;
+ private readonly LinkedListNode _mutexWaiterNode;
private readonly LinkedList _pinnedWaiters;
- private LinkedListNode _pinnedWaiterNode;
+ private readonly LinkedListNode _pinnedWaiterNode;
public KThread MutexOwner { get; private set; }
@@ -1070,11 +1070,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (nextPrio != null)
{
- thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread);
+ _mutexWaiters.AddBefore(nextPrio, thread._mutexWaiterNode);
}
else
{
- thread._mutexWaiterNode = _mutexWaiters.AddLast(thread);
+ _mutexWaiters.AddLast(thread._mutexWaiterNode);
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
index e1c0e5e62..cd71103ca 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
@@ -1,4 +1,5 @@
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
@@ -119,12 +120,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
}
[CommandCmif(90)]
- // ILibraryAppletAccessor:90
+ // Unknown90(ulong[4])
public ResultCode Unknown90(ServiceCtx context)
{
// NOTE: This call is performed on SDK 20+ when applet is called.
- // Since we don't support applets for now, it's fine to stub it.
-
+ // Since we don't support most applets for now, it's fine to stub it.
+ // Throw if values are not 0 to learn more about what this function does.
+
+ if (context.RequestData.ReadUInt64() != 0 || context.RequestData.ReadUInt64() != 0 ||
+ context.RequestData.ReadUInt64() != 0 || context.RequestData.ReadUInt64() != 0)
+ {
+ throw new ServiceNotImplementedException(this, context, $"{GetType().FullName}: 90");
+ }
+
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IContextRegistrar.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IContextRegistrar.cs
new file mode 100644
index 000000000..34adfe9be
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IContextRegistrar.cs
@@ -0,0 +1,32 @@
+using System;
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Services.Ectx
+{
+ class IContextRegistrar : DisposableIpcService
+ {
+ public IContextRegistrar(ServiceCtx context) { }
+
+ [CommandCmif(0)] // 11.0.0+
+ // Complete(nn::Result result, buffer raw_context) -> (i32 context_descriptor)
+ public ResultCode Complete(ServiceCtx context)
+ {
+ Result result = new(context.RequestData.ReadInt32());
+ ulong rawContextPosition = context.Request.SendBuff[0].Position;
+ ulong rawContextSize = context.Request.SendBuff[0].Size;
+
+ byte[] rawContext = new byte[rawContextSize];
+
+ context.Memory.Read(rawContextPosition, rawContext);
+
+ context.ResponseData.Write(0); // TODO: return context_descriptor
+
+ Logger.Stub?.PrintStub(LogClass.ServiceEctx, $"Result: {result}, rawContext: {Convert.ToHexString(rawContext)}" );
+
+ return ResultCode.Success;
+ }
+
+ protected override void Dispose(bool isDisposing) { }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs
index cf2cdddc2..c8ef155e3 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs
@@ -4,5 +4,14 @@ namespace Ryujinx.HLE.HOS.Services.Ectx
class IWriterForApplication : IpcService
{
public IWriterForApplication(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateContextRegistrar() -> object
+ public ResultCode CreateContextRegistrar(ServiceCtx context)
+ {
+ MakeObject(context, new IContextRegistrar(context));
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
index a485ebe63..480899ae1 100644
--- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
@@ -1,8 +1,10 @@
using LibHac;
using LibHac.Common;
+using LibHac.Fs;
using LibHac.Sf;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System.Threading;
@@ -40,7 +42,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
}
using WritableRegion region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true);
- Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size);
+ Result result;
+
+ try
+ {
+ result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size);
+ }
+ catch (HorizonResultException hre) when (hre.IsOfResultType(ResultFs.NonRealDataVerificationFailed))
+ {
+ Logger.Error?.Print(LogClass.ServiceFs,
+ $"Encountered corrupted data in filesystem storage @ offset 0x{offset:X8}, size 0x{size:X8}. " +
+ "Please redump the current game and/or update from your console.");
+ result = ResultFs.NonRealDataVerificationFailed;
+ }
if (context.Device.DirtyHacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix) && IsXc2)
{
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
index b11ecbf77..a92e1656f 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -1349,8 +1349,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
- Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer);
- Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer);
+ Span deviceHandles = MemoryMarshal.Cast(new Span(vibrationDeviceHandleBuffer));
+ Span vibrationValues = MemoryMarshal.Cast(new Span(vibrationValueBuffer));
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{
diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
index 89ef76616..598c7e6e2 100644
--- a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
@@ -14,6 +14,7 @@ using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.Memory;
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Reflection;
@@ -46,6 +47,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv
{ "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) },
{ "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) },
};
+
+ private static readonly ArrayPool _byteArrayPool = ArrayPool.Create();
public static IdDictionary DeviceFileIdRegistry = new();
@@ -471,10 +474,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments);
- if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBuffer))
+ byte[] inlineInBuffer = null;
+
+ if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBufferSpan))
{
- inlineInBuffer = new byte[inlineInBufferSize];
- context.Memory.Read(inlineInBufferPosition, inlineInBuffer);
+ inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
+ inlineInBufferSpan = inlineInBuffer;
+ context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
}
if (errorCode == NvResult.Success)
@@ -483,7 +489,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
- NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer);
+ NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -498,6 +504,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
}
}
}
+
+ if (inlineInBuffer is not null)
+ {
+ _byteArrayPool.Return(inlineInBuffer);
+ }
}
context.ResponseData.Write((uint)errorCode);
@@ -520,10 +531,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments);
- if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBuffer))
+ byte[] inlineOutBuffer = null;
+
+ if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBufferSpan))
{
- inlineOutBuffer = new byte[inlineOutBufferSize];
- context.Memory.Read(inlineOutBufferPosition, inlineOutBuffer);
+ inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
+ inlineOutBufferSpan = inlineOutBuffer;
+ context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
}
if (errorCode == NvResult.Success)
@@ -532,7 +546,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
- NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer);
+ NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -544,10 +558,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
- context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray());
+ context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
}
}
}
+
+ if (inlineOutBuffer is not null)
+ {
+ _byteArrayPool.Return(inlineOutBuffer);
+ }
}
context.ResponseData.Write((uint)errorCode);
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs
index d3a88998f..b7bd3e6da 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs
@@ -7,7 +7,7 @@ using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
- class BsdContext
+ class BsdContext : IDisposable
{
private static readonly ConcurrentDictionary _registry = new();
@@ -158,6 +158,20 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.SUCCESS;
}
+ public void Dispose()
+ {
+ int count;
+
+ lock (_lock)
+ {
+ count = _fds.Count;
+ }
+
+ for (int fd = 0; fd < count; fd++) {
+ CloseFileDescriptor(fd);
+ }
+ }
+
public static BsdContext GetOrRegister(ulong processId)
{
BsdContext context = GetContext(processId);
@@ -174,12 +188,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public static BsdContext GetContext(ulong processId)
{
- if (!_registry.TryGetValue(processId, out BsdContext processContext))
- {
- return null;
- }
+ return _registry.GetValueOrDefault(processId);
+ }
- return processContext;
+ public static void DeleteContext(ulong processId)
+ {
+ if (_registry.Remove(processId, out BsdContext context))
+ {
+ context.Dispose();
+ }
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
index fe37ca4fa..ef3b68b27 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
@@ -1165,5 +1165,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return WriteBsdResult(context, newSockFd, errno);
}
+
+
+ public override void DestroyAtExit()
+ {
+ _context?.Dispose();
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs
index 301d415a0..6a9b4d442 100644
--- a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs
@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Spl
#pragma warning disable IDE0059 // Remove unnecessary value assignment
SystemVersion version = context.Device.System.ContentManager.GetCurrentFirmwareVersion();
#pragma warning restore IDE0059
- MemorySize memorySize = context.Device.Configuration.MemoryConfiguration.ToKernelMemorySize();
+ MemorySize memorySize = context.Device.Configuration.MemoryConfiguration.KernelMemorySize;
switch (configItem)
{
diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
index 3e7408194..8d87a1fd0 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
@@ -102,11 +102,13 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (false, ProcessResult.Failed);
}
- (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
+ (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string updatePath);
if (updatePatchNca != null)
{
patchNca = updatePatchNca;
+ if (updatePath != null)
+ Logger.Notice.PrintMsg(LogClass.Application, $"Loading update NCA from '{updatePath}'.");
}
if (updateControlNca != null)
diff --git a/src/Ryujinx.HLE/MemoryConfiguration.cs b/src/Ryujinx.HLE/MemoryConfiguration.cs
index 21ecd737f..9a397098a 100644
--- a/src/Ryujinx.HLE/MemoryConfiguration.cs
+++ b/src/Ryujinx.HLE/MemoryConfiguration.cs
@@ -18,11 +18,11 @@ namespace Ryujinx.HLE
{
private const ulong GiB = 1024 * 1024 * 1024;
-#pragma warning disable IDE0055 // Disable formatting
- public static MemoryArrange ToKernelMemoryArrange(this MemoryConfiguration configuration)
+ extension(MemoryConfiguration configuration)
{
- return configuration switch
+ public MemoryArrange KernelMemoryArrange => configuration switch
{
+#pragma warning disable IDE0055 // Disable formatting
MemoryConfiguration.MemoryConfiguration4GiB => MemoryArrange.MemoryArrange4GiB,
MemoryConfiguration.MemoryConfiguration4GiBAppletDev => MemoryArrange.MemoryArrange4GiBAppletDev,
MemoryConfiguration.MemoryConfiguration4GiBSystemDev => MemoryArrange.MemoryArrange4GiBSystemDev,
@@ -31,38 +31,36 @@ namespace Ryujinx.HLE
MemoryConfiguration.MemoryConfiguration8GiB => MemoryArrange.MemoryArrange8GiB,
MemoryConfiguration.MemoryConfiguration12GiB => MemoryArrange.MemoryArrange12GiB,
_ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."),
+#pragma warning restore IDE0055
};
- }
-
- public static MemorySize ToKernelMemorySize(this MemoryConfiguration configuration)
- {
- return configuration switch
+
+ public MemorySize KernelMemorySize => configuration switch
{
+#pragma warning disable IDE0055 // Disable formatting
MemoryConfiguration.MemoryConfiguration4GiB or
- MemoryConfiguration.MemoryConfiguration4GiBAppletDev or
- MemoryConfiguration.MemoryConfiguration4GiBSystemDev => MemorySize.MemorySize4GiB,
+ MemoryConfiguration.MemoryConfiguration4GiBAppletDev or
+ MemoryConfiguration.MemoryConfiguration4GiBSystemDev => MemorySize.MemorySize4GiB,
MemoryConfiguration.MemoryConfiguration6GiB or
- MemoryConfiguration.MemoryConfiguration6GiBAppletDev => MemorySize.MemorySize6GiB,
- MemoryConfiguration.MemoryConfiguration8GiB => MemorySize.MemorySize8GiB,
- MemoryConfiguration.MemoryConfiguration12GiB => MemorySize.MemorySize12GiB,
+ MemoryConfiguration.MemoryConfiguration6GiBAppletDev => MemorySize.MemorySize6GiB,
+ MemoryConfiguration.MemoryConfiguration8GiB => MemorySize.MemorySize8GiB,
+ MemoryConfiguration.MemoryConfiguration12GiB => MemorySize.MemorySize12GiB,
_ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."),
+#pragma warning restore IDE0055
};
- }
-
- public static ulong ToDramSize(this MemoryConfiguration configuration)
- {
- return configuration switch
+
+ public ulong DramSize => configuration switch
{
+#pragma warning disable IDE0055 // Disable formatting
MemoryConfiguration.MemoryConfiguration4GiB or
- MemoryConfiguration.MemoryConfiguration4GiBAppletDev or
- MemoryConfiguration.MemoryConfiguration4GiBSystemDev => 4 * GiB,
+ MemoryConfiguration.MemoryConfiguration4GiBAppletDev or
+ MemoryConfiguration.MemoryConfiguration4GiBSystemDev => 4 * GiB,
MemoryConfiguration.MemoryConfiguration6GiB or
- MemoryConfiguration.MemoryConfiguration6GiBAppletDev => 6 * GiB,
+ MemoryConfiguration.MemoryConfiguration6GiBAppletDev => 6 * GiB,
MemoryConfiguration.MemoryConfiguration8GiB => 8 * GiB,
MemoryConfiguration.MemoryConfiguration12GiB => 12 * GiB,
_ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."),
+#pragma warning restore IDE0055
};
}
-#pragma warning restore IDE0055
}
}
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index 2ce9d9959..850c8b5fa 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -83,7 +83,7 @@ namespace Ryujinx.HLE
#pragma warning disable IDE0055 // Disable formatting
DirtyHacks = new DirtyHacks(Configuration.Hacks);
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
- Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
+ Memory = new MemoryBlock(Configuration.MemoryConfiguration.DramSize, memoryAllocationFlags);
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
System = new HOS.Horizon(this);
diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs
index 35967d274..0ccc35fc8 100644
--- a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs
+++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs
@@ -52,7 +52,7 @@ namespace Ryujinx.Horizon.Bcat.Ipc
service = null;
}
- return resultCode.ToHorizonResult();
+ return resultCode.Horizon;
}
[CmifCommand(2)]
@@ -71,7 +71,7 @@ namespace Ryujinx.Horizon.Bcat.Ipc
service = null;
}
- return resultCode.ToHorizonResult();
+ return resultCode.Horizon;
}
public void Dispose()
diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs
index 1559c833c..132453f05 100644
--- a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs
+++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs
@@ -22,19 +22,19 @@ namespace Ryujinx.Horizon.Bcat.Ipc
[CmifCommand(0)]
public Result Open(DirectoryName directoryName)
{
- return _libHacService.Get.Open(ref directoryName).ToHorizonResult();
+ return _libHacService.Get.Open(ref directoryName).Horizon;
}
[CmifCommand(1)]
public Result Read(out int entriesRead, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span entriesBuffer)
{
- return _libHacService.Get.Read(out entriesRead, entriesBuffer).ToHorizonResult();
+ return _libHacService.Get.Read(out entriesRead, entriesBuffer).Horizon;
}
[CmifCommand(2)]
public Result GetCount(out int count)
{
- return _libHacService.Get.GetCount(out count).ToHorizonResult();
+ return _libHacService.Get.GetCount(out count).Horizon;
}
public void Dispose()
diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs
index bd5c418d9..cb6cc3159 100644
--- a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs
+++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs
@@ -22,25 +22,25 @@ namespace Ryujinx.Horizon.Bcat.Ipc
[CmifCommand(0)]
public Result Open(DirectoryName directoryName, FileName fileName)
{
- return _libHacService.Get.Open(ref directoryName, ref fileName).ToHorizonResult();
+ return _libHacService.Get.Open(ref directoryName, ref fileName).Horizon;
}
[CmifCommand(1)]
public Result Read(long offset, out long bytesRead, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span data)
{
- return _libHacService.Get.Read(out bytesRead, offset, data).ToHorizonResult();
+ return _libHacService.Get.Read(out bytesRead, offset, data).Horizon;
}
[CmifCommand(2)]
public Result GetSize(out long size)
{
- return _libHacService.Get.GetSize(out size).ToHorizonResult();
+ return _libHacService.Get.GetSize(out size).Horizon;
}
[CmifCommand(3)]
public Result GetDigest(out Digest digest)
{
- return _libHacService.Get.GetDigest(out digest).ToHorizonResult();
+ return _libHacService.Get.GetDigest(out digest).Horizon;
}
public void Dispose()
diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs
index 356156fc1..b70d074d0 100644
--- a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs
+++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs
@@ -35,7 +35,7 @@ namespace Ryujinx.Horizon.Bcat.Ipc
service = null;
}
- return resultCode.ToHorizonResult();
+ return resultCode.Horizon;
}
[CmifCommand(1)]
@@ -54,13 +54,13 @@ namespace Ryujinx.Horizon.Bcat.Ipc
service = null;
}
- return resultCode.ToHorizonResult();
+ return resultCode.Horizon;
}
[CmifCommand(10)]
public Result EnumerateDeliveryCacheDirectory(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span directoryNames)
{
- return _libHacService.Get.EnumerateDeliveryCacheDirectory(out count, directoryNames).ToHorizonResult();
+ return _libHacService.Get.EnumerateDeliveryCacheDirectory(out count, directoryNames).Horizon;
}
public void Dispose()
diff --git a/src/Ryujinx.Horizon/LibHacResultExtensions.cs b/src/Ryujinx.Horizon/LibHacResultExtensions.cs
index 2abed197d..92c384141 100644
--- a/src/Ryujinx.Horizon/LibHacResultExtensions.cs
+++ b/src/Ryujinx.Horizon/LibHacResultExtensions.cs
@@ -4,9 +4,9 @@ namespace Ryujinx.Horizon
{
public static class LibHacResultExtensions
{
- public static Result ToHorizonResult(this LibHac.Result result)
+ extension(LibHac.Result libHacResult)
{
- return new Result((int)result.Module, (int)result.Description);
+ public Result Horizon => new((int)libHacResult.Module, (int)libHacResult.Description);
}
}
}
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
index ec0acbdea..9844d8d3d 100644
--- a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
+++ b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
@@ -48,36 +48,36 @@ namespace Ryujinx.Horizon.Sdk.Sf
case CommandArgType.Buffer:
HipcBufferFlags flags = argInfo.BufferFlags;
- if (flags.HasFlag(HipcBufferFlags.In))
+ if ((flags & HipcBufferFlags.In) != 0)
{
- if (flags.HasFlag(HipcBufferFlags.AutoSelect))
+ if ((flags & HipcBufferFlags.AutoSelect) != 0)
{
_inMapAliasBuffersCount++;
_inPointerBuffersCount++;
}
- else if (flags.HasFlag(HipcBufferFlags.MapAlias))
+ else if ((flags & HipcBufferFlags.MapAlias) != 0)
{
_inMapAliasBuffersCount++;
}
- else if (flags.HasFlag(HipcBufferFlags.Pointer))
+ else if ((flags & HipcBufferFlags.Pointer) != 0)
{
_inPointerBuffersCount++;
}
}
else
{
- bool autoSelect = flags.HasFlag(HipcBufferFlags.AutoSelect);
- if (autoSelect || flags.HasFlag(HipcBufferFlags.Pointer))
+ bool autoSelect = (flags & HipcBufferFlags.AutoSelect) != 0;
+ if (autoSelect || (flags & HipcBufferFlags.Pointer) != 0)
{
_outPointerBuffersCount++;
- if (flags.HasFlag(HipcBufferFlags.FixedSize))
+ if ((flags & HipcBufferFlags.FixedSize) != 0)
{
_outFixedSizePointerBuffersCount++;
}
}
- if (autoSelect || flags.HasFlag(HipcBufferFlags.MapAlias))
+ if (autoSelect || (flags & HipcBufferFlags.MapAlias) != 0)
{
_outMapAliasBuffersCount++;
}
@@ -150,17 +150,17 @@ namespace Ryujinx.Horizon.Sdk.Sf
HipcBufferFlags flags = _args[i].BufferFlags;
bool isMapAlias;
- if (flags.HasFlag(HipcBufferFlags.MapAlias))
+ if ((flags & HipcBufferFlags.MapAlias) != 0)
{
isMapAlias = true;
}
- else if (flags.HasFlag(HipcBufferFlags.Pointer))
+ else if ((flags & HipcBufferFlags.Pointer) != 0)
{
isMapAlias = false;
}
- else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */
+ else /* if (flags & HipcBufferFlags.HipcAutoSelect)) */
{
- HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In)
+ HipcBufferDescriptor descriptor = (flags & HipcBufferFlags.In) != 0
? context.Request.Data.SendBuffers[sendMapAliasIndex]
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex];
@@ -171,7 +171,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
if (isMapAlias)
{
- HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In)
+ HipcBufferDescriptor descriptor = (flags & HipcBufferFlags.In) != 0
? context.Request.Data.SendBuffers[sendMapAliasIndex++]
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex++];
@@ -184,7 +184,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
}
else
{
- if (flags.HasFlag(HipcBufferFlags.In))
+ if ((flags & HipcBufferFlags.In) != 0)
{
HipcStaticDescriptor descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
ulong address = descriptor.Address;
@@ -197,11 +197,11 @@ namespace Ryujinx.Horizon.Sdk.Sf
pointerBufferTail = Math.Max(pointerBufferTail, address + size);
}
}
- else /* if (flags.HasFlag(HipcBufferFlags.Out)) */
+ else /* if (flags & HipcBufferFlags.Out)) */
{
ulong size;
- if (flags.HasFlag(HipcBufferFlags.FixedSize))
+ if ((flags & HipcBufferFlags.FixedSize) != 0)
{
size = _args[i].BufferFixedSize;
}
@@ -234,12 +234,12 @@ namespace Ryujinx.Horizon.Sdk.Sf
private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode mode)
{
- if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure))
+ if ((flags & HipcBufferFlags.MapTransferAllowsNonSecure) != 0)
{
return mode == HipcBufferMode.NonSecure;
}
- if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice))
+ if ((flags & HipcBufferFlags.MapTransferAllowsNonDevice) != 0)
{
return mode == HipcBufferMode.NonDevice;
}
@@ -259,18 +259,18 @@ namespace Ryujinx.Horizon.Sdk.Sf
}
HipcBufferFlags flags = _args[i].BufferFlags;
- if (!flags.HasFlag(HipcBufferFlags.Out))
+ if ((flags & HipcBufferFlags.Out) == 0)
{
continue;
}
PointerAndSize buffer = _bufferRanges[i];
- if (flags.HasFlag(HipcBufferFlags.Pointer))
+ if ((flags & HipcBufferFlags.Pointer) != 0)
{
response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex);
}
- else if (flags.HasFlag(HipcBufferFlags.AutoSelect))
+ else if ((flags & HipcBufferFlags.AutoSelect) != 0)
{
if (!isBufferMapAlias[i])
{
diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
deleted file mode 100644
index 5b3756fb6..000000000
--- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
+++ /dev/null
@@ -1,236 +0,0 @@
-using Ryujinx.Common.Logging;
-using Ryujinx.SDL2.Common;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using static SDL2.SDL;
-
-namespace Ryujinx.Input.SDL2
-{
- public class SDL2GamepadDriver : IGamepadDriver
- {
- private readonly Dictionary _gamepadsInstanceIdsMapping;
- private readonly List _gamepadsIds;
- private readonly Lock _lock = new();
-
- public ReadOnlySpan GamepadsIds
- {
- get
- {
- lock (_lock)
- {
- return _gamepadsIds.ToArray();
- }
- }
- }
-
- public string DriverName => "SDL2";
-
- public event Action OnGamepadConnected;
- public event Action OnGamepadDisconnected;
-
- public SDL2GamepadDriver()
- {
- _gamepadsInstanceIdsMapping = new Dictionary();
- _gamepadsIds = [];
-
- SDL2Driver.Instance.Initialize();
- SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
- SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
- SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
-
- // Add already connected gamepads
- int numJoysticks = SDL_NumJoysticks();
-
- for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++)
- {
- HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex));
- }
- }
-
- private string GenerateGamepadId(int joystickIndex)
- {
- Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
-
- // Add a unique identifier to the start of the GUID in case of duplicates.
-
- if (guid == Guid.Empty)
- {
- return null;
- }
-
- // Remove the first 4 char of the guid (CRC part) to make it stable
- string guidString = $"0000{guid.ToString()[4..]}";
-
- string id;
-
- lock (_lock)
- {
- int guidIndex = 0;
- id = guidIndex + "-" + guidString;
-
- while (_gamepadsIds.Contains(id))
- {
- id = (++guidIndex) + "-" + guidString;
- }
- }
-
- return id;
- }
-
- private int GetJoystickIndexByGamepadId(string id)
- {
- lock (_lock)
- {
- return _gamepadsIds.IndexOf(id);
- }
- }
-
- private void HandleJoyStickDisconnected(int joystickInstanceId)
- {
- bool joyConPairDisconnected = false;
-
- if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
- return;
-
- lock (_lock)
- {
- _gamepadsIds.Remove(id);
- if (!SDL2JoyConPair.IsCombinable(_gamepadsIds))
- {
- _gamepadsIds.Remove(SDL2JoyConPair.Id);
- joyConPairDisconnected = true;
- }
- }
-
- OnGamepadDisconnected?.Invoke(id);
- if (joyConPairDisconnected)
- {
- OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id);
- }
- }
-
- private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
- {
- bool joyConPairConnected = false;
-
- if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
- {
- if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
- {
- // Sometimes a JoyStick connected event fires after the app starts even though it was connected before
- // so it is rejected to avoid doubling the entries.
- return;
- }
-
- string id = GenerateGamepadId(joystickDeviceId);
-
- if (id == null)
- {
- return;
- }
-
- if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
- {
- lock (_lock)
- {
- if (joystickDeviceId <= _gamepadsIds.FindLastIndex(_ => true))
- _gamepadsIds.Insert(joystickDeviceId, id);
- else
- _gamepadsIds.Add(id);
-
- if (SDL2JoyConPair.IsCombinable(_gamepadsIds))
- {
- _gamepadsIds.Remove(SDL2JoyConPair.Id);
- _gamepadsIds.Add(SDL2JoyConPair.Id);
- joyConPairConnected = true;
- }
- }
-
- OnGamepadConnected?.Invoke(id);
- if (joyConPairConnected)
- {
- OnGamepadConnected?.Invoke(SDL2JoyConPair.Id);
- }
- }
- }
- }
-
- private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel)
- {
- Logger.Info?.Print(LogClass.Hid,
- $"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}");
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
- SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
-
- // Simulate a full disconnect when disposing
- foreach (string id in _gamepadsIds)
- {
- OnGamepadDisconnected?.Invoke(id);
- }
-
- lock (_lock)
- {
- _gamepadsIds.Clear();
- }
-
- SDL2Driver.Instance.Dispose();
- }
- }
-
- public void Dispose()
- {
- GC.SuppressFinalize(this);
- Dispose(true);
- }
-
- public IGamepad GetGamepad(string id)
- {
- if (id == SDL2JoyConPair.Id)
- {
- lock (_lock)
- {
- return SDL2JoyConPair.GetGamepad(_gamepadsIds);
- }
- }
-
- int joystickIndex = GetJoystickIndexByGamepadId(id);
-
- if (joystickIndex == -1)
- {
- return null;
- }
-
- nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
-
- if (gamepadHandle == nint.Zero)
- {
- return null;
- }
-
- if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix))
- {
- return new SDL2JoyCon(gamepadHandle, id);
- }
-
- return new SDL2Gamepad(gamepadHandle, id);
- }
-
- public IEnumerable GetGamepads()
- {
- lock (_gamepadsIds)
- {
- foreach (string gamepadId in _gamepadsIds)
- {
- yield return GetGamepad(gamepadId);
- }
- }
- }
- }
-}
diff --git a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/src/Ryujinx.Input.SDL3/Ryujinx.Input.SDL3.csproj
similarity index 69%
rename from src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
rename to src/Ryujinx.Input.SDL3/Ryujinx.Input.SDL3.csproj
index 89f5adda1..06f25ace7 100644
--- a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
+++ b/src/Ryujinx.Input.SDL3/Ryujinx.Input.SDL3.csproj
@@ -1,4 +1,4 @@
-
+
true
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs
similarity index 71%
rename from src/Ryujinx.Input.SDL2/SDL2Gamepad.cs
rename to src/Ryujinx.Input.SDL3/SDL3Gamepad.cs
index 4b868efeb..2b006147d 100644
--- a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs
+++ b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs
@@ -6,11 +6,12 @@ using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
-using static SDL2.SDL;
+using SDL;
+using static SDL.SDL3;
-namespace Ryujinx.Input.SDL2
+namespace Ryujinx.Input.SDL3
{
- public class SDL2Gamepad : IGamepad
+ public unsafe class SDL3Gamepad : IGamepad
{
private bool HasConfiguration => _configuration != null;
@@ -21,43 +22,43 @@ namespace Ryujinx.Input.SDL2
private StandardControllerInputConfig _configuration;
- private static readonly SDL_GameControllerButton[] _buttonsDriverMapping =
+ private readonly SDL_GamepadButton[] _buttonsDriverMapping =
[
// Unbound, ignored.
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_STICK,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
// NOTE: The left and right trigger are axis, we handle those differently
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_UP,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_DOWN,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_LEFT,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_BACK,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_GUIDE,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_MISC1,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_TOUCHPAD,
// Virtual buttons are invalid, ignored.
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
- SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
+ SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
];
private readonly Lock _userMappingLock = new();
@@ -73,29 +74,43 @@ namespace Ryujinx.Input.SDL2
public GamepadFeaturesFlag Features { get; }
- private nint _gamepadHandle;
+ private SDL_Gamepad* _gamepadHandle;
private float _triggerThreshold;
- public SDL2Gamepad(nint gamepadHandle, string driverId)
+ public SDL3Gamepad(SDL_Gamepad* gamepadHandle, string driverId)
{
_gamepadHandle = gamepadHandle;
_buttonsUserMapping = new List(20);
- Name = SDL_GameControllerName(_gamepadHandle);
+ Name = SDL_GetGamepadName(_gamepadHandle);
Id = driverId;
Features = GetFeaturesFlag();
_triggerThreshold = 0.0f;
+ // Face button mapping
+ SDL_GamepadButton[] faceButtons = _buttonsDriverMapping[1..5];
+ foreach (SDL_GamepadButton btn in faceButtons) {
+ int mapId = SDL_GetGamepadButtonLabel(_gamepadHandle, btn) switch {
+ SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_A or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CROSS => 1,
+ SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_B or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CIRCLE => 2,
+ SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_X or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_SQUARE => 3,
+ SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_Y or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE => 4,
+ _ => -1
+ };
+ if (mapId == -1) { continue; }
+ _buttonsDriverMapping[mapId] = btn;
+ }
+
// Enable motion tracking
- if (Features.HasFlag(GamepadFeaturesFlag.Motion))
+ if ((Features & GamepadFeaturesFlag.Motion) != 0)
{
- if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0)
+ if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, true))
{
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
}
- if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE) != 0)
+ if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, true))
{
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
}
@@ -104,14 +119,14 @@ namespace Ryujinx.Input.SDL2
public void SetLed(uint packedRgb)
{
- if (!Features.HasFlag(GamepadFeaturesFlag.Led))
+ if ((Features & GamepadFeaturesFlag.Led) == 0)
return;
byte red = packedRgb > 0 ? (byte)(packedRgb >> 16) : (byte)0;
byte green = packedRgb > 0 ? (byte)(packedRgb >> 8) : (byte)0;
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
- if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
+ if (!SDL_SetGamepadLED(_gamepadHandle, red, green, blue))
Logger.Debug?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
}
@@ -119,21 +134,24 @@ namespace Ryujinx.Input.SDL2
{
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
- if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
- SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
+ if (SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) &&
+ SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO))
{
result |= GamepadFeaturesFlag.Motion;
}
-
- if (SDL_GameControllerHasRumble(_gamepadHandle) == SDL_bool.SDL_TRUE)
+ SDL_PropertiesID propID = SDL_GetGamepadProperties(_gamepadHandle);
+ SDL_LockProperties(propID);
+ if (SDL_GetBooleanProperty(propID, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false))
{
result |= GamepadFeaturesFlag.Rumble;
}
- if (SDL_GameControllerHasLED(_gamepadHandle) == SDL_bool.SDL_TRUE)
+ if (SDL_GetBooleanProperty(propID, SDL_PROP_GAMEPAD_CAP_MONO_LED_BOOLEAN, false))
{
result |= GamepadFeaturesFlag.Led;
}
+ SDL_UnlockProperties(propID);
+ SDL_DestroyProperties(propID);
return result;
}
@@ -141,15 +159,15 @@ namespace Ryujinx.Input.SDL2
public string Id { get; }
public string Name { get; }
- public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
+ public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
protected virtual void Dispose(bool disposing)
{
- if (disposing && _gamepadHandle != nint.Zero)
+ if (disposing && _gamepadHandle != null)
{
- SDL_GameControllerClose(_gamepadHandle);
+ SDL_CloseGamepad(_gamepadHandle);
- _gamepadHandle = nint.Zero;
+ _gamepadHandle = null;
}
}
@@ -166,7 +184,7 @@ namespace Ryujinx.Input.SDL2
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
- if (!Features.HasFlag(GamepadFeaturesFlag.Rumble))
+ if ((Features & GamepadFeaturesFlag.Rumble) == 0)
return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
@@ -174,7 +192,7 @@ namespace Ryujinx.Input.SDL2
if (durationMs == uint.MaxValue)
{
- if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
+ if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
else if (durationMs > SDL_HAPTIC_INFINITY)
@@ -183,7 +201,7 @@ namespace Ryujinx.Input.SDL2
}
else
{
- if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
+ if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
@@ -202,13 +220,11 @@ namespace Ryujinx.Input.SDL2
const int ElementCount = 3;
- unsafe
- {
- float* values = stackalloc float[ElementCount];
+ float[] values = new float[3];
- int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount);
+ fixed (float* pValues = &values[0]) {
- if (result != 0)
+ if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, pValues, ElementCount))
return Vector3.Zero;
Vector3 value = new(values[0], values[1], values[2]);
@@ -232,7 +248,7 @@ namespace Ryujinx.Input.SDL2
{
_configuration = (StandardControllerInputConfig)configuration;
- if (Features.HasFlag(GamepadFeaturesFlag.Led) && _configuration.Led.EnableLed)
+ if ((Features & GamepadFeaturesFlag.Led) != 0 && _configuration.Led.EnableLed)
{
if (_configuration.Led.TurnOffLed)
(this as IGamepad).ClearLed();
@@ -380,11 +396,11 @@ namespace Ryujinx.Input.SDL2
inputId switch
{
StickInputId.Left => (
- SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX),
- SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY)),
+ SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX),
+ SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY)),
StickInputId.Right => (
- SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX),
- SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY)),
+ SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTX),
+ SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTY)),
_ => throw new NotSupportedException($"Unsupported stick {inputId}")
};
@@ -393,17 +409,17 @@ namespace Ryujinx.Input.SDL2
switch (inputId)
{
case GamepadButtonInputId.LeftTrigger:
- return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
+ return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) > _triggerThreshold;
case GamepadButtonInputId.RightTrigger:
- return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
+ return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) > _triggerThreshold;
}
- if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID)
+ if (_buttonsDriverMapping[(int)inputId] == SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID)
{
return false;
}
- return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
+ return SDL_GetGamepadButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]);
}
}
}
diff --git a/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs b/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs
new file mode 100644
index 000000000..b1100384f
--- /dev/null
+++ b/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs
@@ -0,0 +1,254 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.SDL3.Common;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using SDL;
+using System.Linq;
+using static SDL.SDL3;
+
+namespace Ryujinx.Input.SDL3
+{
+ public unsafe class SDL3GamepadDriver : IGamepadDriver
+ {
+ private readonly Dictionary _gamepadsInstanceIdsMapping;
+ private readonly Dictionary _gamepadsIds;
+ private readonly Lock _lock = new();
+
+ public ReadOnlySpan GamepadsIds
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _gamepadsIds.Values.ToArray();
+ }
+ }
+ }
+
+ public string DriverName => "SDL3";
+
+ public event Action