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 OnGamepadConnected; + public event Action OnGamepadDisconnected; + + public SDL3GamepadDriver() + { + _gamepadsInstanceIdsMapping = new Dictionary(); + _gamepadsIds = []; + + SDL3Driver.Instance.Initialize(); + SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; + SDL3Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; + SDL3Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated; + + // Add already connected gamepads + int joystickCount = 0; + + SDL_JoystickID* pJoystickInstanceIds = SDL_GetJoysticks(&joystickCount); + + for (int i = 0; i < joystickCount; i++) + { + HandleJoyStickConnected(pJoystickInstanceIds[i]); + } + } + + private unsafe static string SDLGuidToString(SDL_GUID guid) + { + string map = "0123456789abcdef"; + char[] guidBytes = new char[33]; + + for (int i = 0; i < 16; i++) { + byte c = guid.data[i]; + guidBytes[i * 2] = map[c >> 4]; + guidBytes[(i * 2) + 1] = map[c & 0x0f]; + } + + string strGuid = new(guidBytes); + + return $"{strGuid[4..6]}{strGuid[6..8]}{strGuid[2..4]}{strGuid[0..2]}-{strGuid[10..12]}{strGuid[8..10]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}"; + + } + + private unsafe string GenerateGamepadId(SDL_JoystickID joystickInstanceId) + { + SDL_GUID sdlGuid = SDL_GetJoystickGUIDForID(joystickInstanceId); + string guidBytes = SDLGuidToString(sdlGuid); + Guid guid = Guid.Parse(guidBytes); + + // 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.ContainsValue(id)) + { + id = (++guidIndex) + "-" + guidString; + } + } + + return id; + } + + private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId) + { + bool joyConPairDisconnected = false; + + if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id)) + return; + + lock (_lock) + { + _gamepadsIds.Remove(joystickInstanceId); + if (!SDL3JoyConPair.IsCombinable(_gamepadsIds)) + { + _gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id)); + joyConPairDisconnected = true; + } + } + + OnGamepadDisconnected?.Invoke(id); + if (joyConPairDisconnected) + { + OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id); + } + } + + private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId) + { + bool joyConPairConnected = false; + + if (SDL_IsGamepad(joystickInstanceId)) + { + 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(joystickInstanceId); + + if (id == null) + { + return; + } + + if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id)) + { + lock (_lock) + { + + _gamepadsIds.Add(joystickInstanceId, id); + + if (SDL3JoyConPair.IsCombinable(_gamepadsIds)) + { + // TODO - It appears that you can only have one joy con pair connected at a time? + // This was also the behavior before SDL3 + _gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id)); + uint fakeInstanceID = uint.MaxValue; + while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id)) + { + fakeInstanceID--; + } + joyConPairConnected = true; + } + } + + OnGamepadConnected?.Invoke(id); + if (joyConPairConnected) + { + OnGamepadConnected?.Invoke(SDL3JoyConPair.Id); + } + } + } + } + + private void HandleJoyBatteryUpdated(SDL_JoystickID joystickInstanceId, SDL_PowerState powerLevel) + { + Logger.Info?.Print(LogClass.Hid, + $"{SDL_GetGamepadNameForID(joystickInstanceId)} power level: {powerLevel}"); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + SDL3Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected; + SDL3Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected; + + // Simulate a full disconnect when disposing + foreach (var gamepad in _gamepadsIds) + { + OnGamepadDisconnected?.Invoke(gamepad.Value); + } + + lock (_lock) + { + _gamepadsIds.Clear(); + } + + SDL3Driver.Instance.Dispose(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + public SDL_JoystickID GetInstanceIdFromId(string id) { + return _gamepadsInstanceIdsMapping.Where(e => e.Value == id).FirstOrDefault().Key; + } + + public IGamepad GetGamepad(string id) + { + if (id == SDL3JoyConPair.Id) + { + lock (_lock) + { + return SDL3JoyConPair.GetGamepad(_gamepadsIds); + } + } + + SDL_JoystickID instanceId = GetInstanceIdFromId(id); + + SDL_Gamepad* gamepadHandle = SDL_OpenGamepad(instanceId); + + if (gamepadHandle == null) + { + return null; + } + + if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix)) + { + return new SDL3JoyCon(gamepadHandle, id); + } + + return new SDL3Gamepad(gamepadHandle, id); + } + + public IEnumerable GetGamepads() + { + lock (_gamepadsIds) + { + foreach (var gamepad in _gamepadsIds) + { + yield return GetGamepad(gamepad.Value); + } + } + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs similarity index 76% rename from src/Ryujinx.Input.SDL2/SDL2JoyCon.cs rename to src/Ryujinx.Input.SDL3/SDL3JoyCon.cs index ee80881c2..d71b06dda 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs @@ -5,11 +5,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 { - internal class SDL2JoyCon : IGamepad + internal unsafe class SDL3JoyCon : IGamepad { private bool HasConfiguration => _configuration != null; @@ -20,34 +21,34 @@ namespace Ryujinx.Input.SDL2 private StandardControllerInputConfig _configuration; - private readonly Dictionary _leftButtonsDriverMapping = new() + private readonly Dictionary _leftButtonsDriverMapping = new() { - { GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK }, - {GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, - {GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, - {GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, - {GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, - {GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, - {GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2}, - {GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4}, - {GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK}, + {GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH}, + {GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH}, + {GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST}, + {GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST}, + {GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START}, + {GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1}, + {GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, + {GamepadButtonInputId.SingleRightTrigger0, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, + {GamepadButtonInputId.SingleLeftTrigger0, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}, }; - private readonly Dictionary _rightButtonsDriverMapping = new() + private readonly Dictionary _rightButtonsDriverMapping = new() { - {GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, - {GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, - {GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, - {GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, - {GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, - {GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1}, - {GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3}, - {GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER} + {GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK}, + {GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST}, + {GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH}, + {GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH}, + {GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST}, + {GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START}, + {GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, + {GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, + {GamepadButtonInputId.SingleRightTrigger1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, + {GamepadButtonInputId.SingleLeftTrigger1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER} }; - private readonly Dictionary _buttonsDriverMapping; + private readonly Dictionary _buttonsDriverMapping; private readonly Lock _userMappingLock = new(); private readonly List _buttonsUserMapping; @@ -59,7 +60,7 @@ namespace Ryujinx.Input.SDL2 public GamepadFeaturesFlag Features { get; } - private nint _gamepadHandle; + private SDL_Gamepad* _gamepadHandle; private enum JoyConType { @@ -72,27 +73,25 @@ namespace Ryujinx.Input.SDL2 private readonly JoyConType _joyConType; - public SDL2JoyCon(nint gamepadHandle, string driverId) + public SDL3JoyCon(SDL_Gamepad* gamepadHandle, string driverId) { _gamepadHandle = gamepadHandle; _buttonsUserMapping = new List(10); - Name = SDL_GameControllerName(_gamepadHandle); + Name = SDL_GetGamepadName(_gamepadHandle); Id = driverId; Features = GetFeaturesFlag(); // 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}."); @@ -120,15 +119,13 @@ 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; } - int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); - - if (error == 0) + if (SDL_RumbleGamepad(_gamepadHandle, 0, 0, 100)) { result |= GamepadFeaturesFlag.Rumble; } @@ -138,15 +135,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; } } @@ -162,7 +159,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); @@ -170,8 +167,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) @@ -180,7 +176,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."); } } @@ -194,18 +190,15 @@ namespace Ryujinx.Input.SDL2 _ => SDL_SensorType.SDL_SENSOR_INVALID }; - if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) + if ((Features & GamepadFeaturesFlag.Motion) == 0 || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) return Vector3.Zero; const int ElementCount = 3; - unsafe - { - float* values = stackalloc float[ElementCount]; + float[] values = new float[3]; - int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); - - if (result != 0) + fixed (float* pValues = &values[0]) { + if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, pValues, ElementCount)) return Vector3.Zero; Vector3 value = _joyConType switch @@ -392,18 +385,18 @@ namespace Ryujinx.Input.SDL2 private (short, short) GetStickXY() { return ( - 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)); } public bool IsPressed(GamepadButtonInputId inputId) { - if (!_buttonsDriverMapping.TryGetValue(inputId, out SDL_GameControllerButton button)) + if (!_buttonsDriverMapping.TryGetValue(inputId, out SDL_GamepadButton button)) { return false; } - return SDL_GameControllerGetButton(_gamepadHandle, button) == 1; + return SDL_GetGamepadButton(_gamepadHandle, button); } } } diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs similarity index 70% rename from src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs rename to src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs index dce767523..303875eac 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs @@ -2,11 +2,12 @@ using Ryujinx.Common.Configuration.Hid; using System.Collections.Generic; using System.Linq; using System.Numerics; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.Input.SDL3 { - internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad + internal class SDL3JoyConPair(IGamepad left, IGamepad right) : IGamepad { public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) | (right?.Features ?? GamepadFeaturesFlag.None); @@ -95,40 +96,44 @@ namespace Ryujinx.Input.SDL2 right.SetTriggerThreshold(triggerThreshold); } - public static bool IsCombinable(List gamepadsIds) + public static bool IsCombinable(Dictionary gamepadsIds) { (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); return leftIndex >= 0 && rightIndex >= 0; } - private static (int leftIndex, int rightIndex) DetectJoyConPair(List gamepadsIds) + private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary gamepadsIds) { - List gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) - .Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); - int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName); - int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName); + Dictionary gamepadNames = gamepadsIds + .Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName) + .Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key)) + .ToDictionary(); + SDL_JoystickID idx; + int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1; + int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1; return (leftIndex, rightIndex); } - public static IGamepad GetGamepad(List gamepadsIds) + public unsafe static IGamepad GetGamepad(Dictionary gamepadsIds) { (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); - if (leftIndex == -1 || rightIndex == -1) + + if (leftIndex <= 0 || rightIndex <= 0) { return null; } - nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); - nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); + SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex); + SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex); - if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero) + if (leftGamepadHandle == null || rightGamepadHandle == null) { return null; } - return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]), - new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex])); + return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]), + new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex])); } } } diff --git a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs similarity index 85% rename from src/Ryujinx.Input.SDL2/SDL2Keyboard.cs rename to src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 1cbf4d35c..f5da11a19 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -6,13 +6,14 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.Input.SDL3 { - class SDL2Keyboard : IKeyboard + class SDL3Keyboard : IKeyboard { private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From) { @@ -22,11 +23,12 @@ namespace Ryujinx.Input.SDL2 private readonly Lock _userMappingLock = new(); #pragma warning disable IDE0052 // Remove unread private member - private readonly SDL2KeyboardDriver _driver; + private readonly SDL3KeyboardDriver _driver; #pragma warning restore IDE0052 private StandardKeyboardInputConfig _configuration; private readonly List _buttonsUserMapping; + private static readonly SDL_Keycode[] _keysDriverMapping = [ // INVALID @@ -116,32 +118,32 @@ namespace Ryujinx.Input.SDL2 SDL_Keycode.SDLK_KP_PLUS, SDL_Keycode.SDLK_KP_DECIMAL, SDL_Keycode.SDLK_KP_ENTER, - SDL_Keycode.SDLK_a, - SDL_Keycode.SDLK_b, - SDL_Keycode.SDLK_c, - SDL_Keycode.SDLK_d, - SDL_Keycode.SDLK_e, - SDL_Keycode.SDLK_f, - SDL_Keycode.SDLK_g, - SDL_Keycode.SDLK_h, - SDL_Keycode.SDLK_i, - SDL_Keycode.SDLK_j, - SDL_Keycode.SDLK_k, - SDL_Keycode.SDLK_l, - SDL_Keycode.SDLK_m, - SDL_Keycode.SDLK_n, - SDL_Keycode.SDLK_o, - SDL_Keycode.SDLK_p, - SDL_Keycode.SDLK_q, - SDL_Keycode.SDLK_r, - SDL_Keycode.SDLK_s, - SDL_Keycode.SDLK_t, - SDL_Keycode.SDLK_u, - SDL_Keycode.SDLK_v, - SDL_Keycode.SDLK_w, - SDL_Keycode.SDLK_x, - SDL_Keycode.SDLK_y, - SDL_Keycode.SDLK_z, + SDL_Keycode.SDLK_A, + SDL_Keycode.SDLK_B, + SDL_Keycode.SDLK_C, + SDL_Keycode.SDLK_D, + SDL_Keycode.SDLK_E, + SDL_Keycode.SDLK_F, + SDL_Keycode.SDLK_G, + SDL_Keycode.SDLK_H, + SDL_Keycode.SDLK_I, + SDL_Keycode.SDLK_J, + SDL_Keycode.SDLK_K, + SDL_Keycode.SDLK_L, + SDL_Keycode.SDLK_M, + SDL_Keycode.SDLK_N, + SDL_Keycode.SDLK_O, + SDL_Keycode.SDLK_P, + SDL_Keycode.SDLK_Q, + SDL_Keycode.SDLK_R, + SDL_Keycode.SDLK_S, + SDL_Keycode.SDLK_T, + SDL_Keycode.SDLK_U, + SDL_Keycode.SDLK_V, + SDL_Keycode.SDLK_W, + SDL_Keycode.SDLK_X, + SDL_Keycode.SDLK_Y, + SDL_Keycode.SDLK_Z, SDL_Keycode.SDLK_0, SDL_Keycode.SDLK_1, SDL_Keycode.SDLK_2, @@ -152,14 +154,14 @@ namespace Ryujinx.Input.SDL2 SDL_Keycode.SDLK_7, SDL_Keycode.SDLK_8, SDL_Keycode.SDLK_9, - SDL_Keycode.SDLK_BACKQUOTE, - SDL_Keycode.SDLK_BACKQUOTE, + SDL_Keycode.SDLK_GRAVE, + SDL_Keycode.SDLK_GRAVE, SDL_Keycode.SDLK_MINUS, SDL_Keycode.SDLK_PLUS, SDL_Keycode.SDLK_LEFTBRACKET, SDL_Keycode.SDLK_RIGHTBRACKET, SDL_Keycode.SDLK_SEMICOLON, - SDL_Keycode.SDLK_QUOTE, + SDL_Keycode.SDLK_APOSTROPHE, SDL_Keycode.SDLK_COMMA, SDL_Keycode.SDLK_PERIOD, SDL_Keycode.SDLK_SLASH, @@ -169,7 +171,7 @@ namespace Ryujinx.Input.SDL2 SDL_Keycode.SDLK_0 ]; - public SDL2Keyboard(SDL2KeyboardDriver driver, string id, string name) + public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name) { _driver = driver; Id = id; @@ -193,55 +195,53 @@ namespace Ryujinx.Input.SDL2 } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ToSDL2Scancode(Key key) + private unsafe static int ToSDL3Scancode(Key key) { if (key is >= Key.Unknown and <= Key.Menu) { return -1; } - return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key]); + return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null); } private static SDL_Keymod GetKeyboardModifierMask(Key key) { return key switch { - Key.ShiftLeft => SDL_Keymod.KMOD_LSHIFT, - Key.ShiftRight => SDL_Keymod.KMOD_RSHIFT, - Key.ControlLeft => SDL_Keymod.KMOD_LCTRL, - Key.ControlRight => SDL_Keymod.KMOD_RCTRL, - Key.AltLeft => SDL_Keymod.KMOD_LALT, - Key.AltRight => SDL_Keymod.KMOD_RALT, - Key.WinLeft => SDL_Keymod.KMOD_LGUI, - Key.WinRight => SDL_Keymod.KMOD_RGUI, - // NOTE: Menu key isn't supported by SDL2. - _ => SDL_Keymod.KMOD_NONE, + Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT, + Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT, + Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL, + Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL, + Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT, + Key.AltRight => SDL_Keymod.SDL_KMOD_RALT, + Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI, + Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI, + // NOTE: Menu key isn't supported by SDL3. + _ => SDL_Keymod.SDL_KMOD_NONE }; } - public KeyboardStateSnapshot GetKeyboardStateSnapshot() + public unsafe KeyboardStateSnapshot GetKeyboardStateSnapshot() { - ReadOnlySpan rawKeyboardState; + SDLBool* rawKeyboardState; SDL_Keymod rawKeyboardModifierState = SDL_GetModState(); unsafe { - nint statePtr = SDL_GetKeyboardState(out int numKeys); - - rawKeyboardState = new ReadOnlySpan((byte*)statePtr, numKeys); + rawKeyboardState = SDL_GetKeyboardState(null); } bool[] keysState = new bool[(int)Key.Count]; for (Key key = 0; key < Key.Count; key++) { - int index = ToSDL2Scancode(key); + int index = ToSDL3Scancode(key); if (index == -1) { SDL_Keymod modifierMask = GetKeyboardModifierMask(key); - if (modifierMask == SDL_Keymod.KMOD_NONE) + if (modifierMask == SDL_Keymod.SDL_KMOD_NONE) { continue; } @@ -250,7 +250,7 @@ namespace Ryujinx.Input.SDL2 } else { - keysState[(int)key] = rawKeyboardState[index] == 1; + keysState[(int)key] = rawKeyboardState[index]; } } @@ -388,7 +388,7 @@ namespace Ryujinx.Input.SDL2 public void SetLed(uint packedRgb) { - Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Keyboard"); + Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard"); } public void SetTriggerThreshold(float triggerThreshold) diff --git a/src/Ryujinx.Input.SDL2/SDL2Mouse.cs b/src/Ryujinx.Input.SDL3/SDL3Mouse.cs similarity index 90% rename from src/Ryujinx.Input.SDL2/SDL2Mouse.cs rename to src/Ryujinx.Input.SDL3/SDL3Mouse.cs index eb86fa799..9fdeb36ab 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Mouse.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Mouse.cs @@ -4,17 +4,17 @@ using System; using System.Drawing; using System.Numerics; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.Input.SDL3 { - public class SDL2Mouse : IMouse + public class SDL3Mouse : IMouse { - private SDL2MouseDriver _driver; + private SDL3MouseDriver _driver; public GamepadFeaturesFlag Features => throw new NotImplementedException(); public string Id => "0"; - public string Name => "SDL2Mouse"; + public string Name => "SDL3Mouse"; public bool IsConnected => true; @@ -22,7 +22,7 @@ namespace Ryujinx.Input.SDL2 Size IMouse.ClientSize => _driver.GetClientSize(); - public SDL2Mouse(SDL2MouseDriver driver) + public SDL3Mouse(SDL3MouseDriver driver) { _driver = driver; } @@ -79,7 +79,7 @@ namespace Ryujinx.Input.SDL2 public void SetLed(uint packedRgb) { - Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Mouse"); + Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Mouse"); } public void SetTriggerThreshold(float triggerThreshold) diff --git a/src/Ryujinx.Input.SDL2/SDL2MouseDriver.cs b/src/Ryujinx.Input.SDL3/SDL3MouseDriver.cs similarity index 81% rename from src/Ryujinx.Input.SDL2/SDL2MouseDriver.cs rename to src/Ryujinx.Input.SDL3/SDL3MouseDriver.cs index a1f6c1d7b..e24f20b1c 100644 --- a/src/Ryujinx.Input.SDL2/SDL2MouseDriver.cs +++ b/src/Ryujinx.Input.SDL3/SDL3MouseDriver.cs @@ -6,11 +6,12 @@ using System.Diagnostics; using System.Drawing; using System.Numerics; using System.Runtime.CompilerServices; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.Input.SDL3 { - public class SDL2MouseDriver : IGamepadDriver + public class SDL3MouseDriver : IGamepadDriver { private const int CursorHideIdleTime = 5; // seconds @@ -25,14 +26,14 @@ namespace Ryujinx.Input.SDL2 public Vector2 Scroll { get; private set; } public Size ClientSize; - public SDL2MouseDriver(HideCursorMode hideCursorMode) + public SDL3MouseDriver(HideCursorMode hideCursorMode) { PressedButtons = new bool[(int)MouseButton.Count]; _hideCursorMode = hideCursorMode; if (_hideCursorMode == HideCursorMode.Always) { - if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) + if (!SDL_HideCursor()) { Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); } @@ -49,9 +50,11 @@ namespace Ryujinx.Input.SDL2 return (MouseButton)(rawButton - 1); } - public void UpdatePosition() + public unsafe void UpdatePosition() { - _ = SDL_GetMouseState(out int posX, out int posY); + float posX = 0; + float posY = 0; + _ = SDL_GetMouseState(&posX, &posY); Vector2 position = new(posX, posY); if (CurrentPosition != position) @@ -76,7 +79,7 @@ namespace Ryujinx.Input.SDL2 { if (!_isHidden) { - if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) + if (!SDL_HideCursor()) { Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); } @@ -88,7 +91,7 @@ namespace Ryujinx.Input.SDL2 { if (_isHidden) { - if (SDL_ShowCursor(SDL_ENABLE) != SDL_ENABLE) + if (!SDL_ShowCursor()) { Logger.Error?.PrintMsg(LogClass.Application, "Failed to enable the cursor."); } @@ -100,15 +103,15 @@ namespace Ryujinx.Input.SDL2 public void Update(SDL_Event evnt) { - switch (evnt.type) + switch (evnt.Type) { - case SDL_EventType.SDL_MOUSEBUTTONDOWN: - case SDL_EventType.SDL_MOUSEBUTTONUP: - uint rawButton = evnt.button.button; + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP: + uint rawButton = (uint)evnt.button.Button; if (rawButton is > 0 and <= ((int)MouseButton.Count)) { - PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN; + PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.Type == SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN; CurrentPosition = new Vector2(evnt.button.x, evnt.button.y); } @@ -116,13 +119,13 @@ namespace Ryujinx.Input.SDL2 break; // NOTE: On Linux using Wayland mouse motion events won't be received at all. - case SDL_EventType.SDL_MOUSEMOTION: + case SDL_EventType.SDL_EVENT_MOUSE_MOTION: CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y); _lastCursorMoveTime = Stopwatch.GetTimestamp(); break; - case SDL_EventType.SDL_MOUSEWHEEL: + case SDL_EventType.SDL_EVENT_MOUSE_WHEEL: Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y); break; @@ -144,7 +147,7 @@ namespace Ryujinx.Input.SDL2 return ClientSize; } - public string DriverName => "SDL2"; + public string DriverName => "SDL3"; public event Action OnGamepadConnected { @@ -162,7 +165,7 @@ namespace Ryujinx.Input.SDL2 public IGamepad GetGamepad(string id) { - return new SDL2Mouse(this); + return new SDL3Mouse(this); } public IEnumerable GetGamepads() => [GetGamepad("0")]; diff --git a/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs similarity index 76% rename from src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs rename to src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs index 25c6f4fe6..cd2a067be 100644 --- a/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs +++ b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs @@ -1,17 +1,17 @@ -using Ryujinx.SDL2.Common; +using Ryujinx.SDL3.Common; using System; using System.Collections.Generic; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.Input.SDL3 { - public class SDL2KeyboardDriver : IGamepadDriver + public class SDL3KeyboardDriver : IGamepadDriver { - public SDL2KeyboardDriver() + public SDL3KeyboardDriver() { - SDL2Driver.Instance.Initialize(); + SDL3Driver.Instance.Initialize(); } - public string DriverName => "SDL2"; + public string DriverName => "SDL3"; private static readonly string[] _keyboardIdentifers = ["0"]; @@ -33,7 +33,7 @@ namespace Ryujinx.Input.SDL2 { if (disposing) { - SDL2Driver.Instance.Dispose(); + SDL3Driver.Instance.Dispose(); } } @@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL2 return null; } - return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards"); + return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); } public IEnumerable GetGamepads() diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index b3979003e..dd8907a4b 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; @@ -291,7 +292,7 @@ namespace Ryujinx.Input.HLE { if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) { - if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) + if ((gamepad.Features & GamepadFeaturesFlag.Motion) != 0) { Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer); Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope); @@ -531,6 +532,8 @@ namespace Ryujinx.Input.HLE hidKeyboard.Modifier |= value << entry.Target; } + + ArrayPool.Shared.Return(keyboardState.KeysState); return hidKeyboard; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 21219d91b..866504128 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -1,8 +1,10 @@ +using Ryujinx.Common; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -18,6 +20,7 @@ namespace Ryujinx.Input.HLE { public class NpadManager : IDisposable { + private static readonly ObjectPool> _hleMotionStatesPool = new (() => new List(NpadDevices.MaxControllers)); private readonly CemuHookClient _cemuHookClient; private readonly Lock _lock = new(); @@ -215,7 +218,7 @@ namespace Ryujinx.Input.HLE lock (_lock) { List hleInputStates = []; - List hleMotionStates = new(NpadDevices.MaxControllers); + List hleMotionStates = _hleMotionStatesPool.Allocate(); KeyboardInput? hleKeyboardInput = null; @@ -317,6 +320,8 @@ namespace Ryujinx.Input.HLE Vector2 position = IMouse.GetScreenPosition(mouseInput.Position, mouse.ClientSize, aspectRatio); _device.Hid.Mouse.Update((int)position.X, (int)position.Y, buttons, (int)mouseInput.Scroll.X, (int)mouseInput.Scroll.Y, true); + + ArrayPool.Shared.Return(mouseInput.ButtonState); } else { @@ -324,6 +329,9 @@ namespace Ryujinx.Input.HLE } _device.TamperMachine.UpdateInput(hleInputStates); + + hleMotionStates.Clear(); + _hleMotionStatesPool.Release(hleMotionStates); } } diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs index 2fc660112..7fecaaa5d 100644 --- a/src/Ryujinx.Input/IKeyboard.cs +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Runtime.CompilerServices; namespace Ryujinx.Input @@ -28,7 +29,8 @@ namespace Ryujinx.Input [MethodImpl(MethodImplOptions.AggressiveInlining)] static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) { - bool[] keysState = new bool[(int)Key.Count]; + + bool[] keysState = ArrayPool.Shared.Rent((int)Key.Count); for (Key key = 0; key < Key.Count; key++) { diff --git a/src/Ryujinx.Input/IMouse.cs b/src/Ryujinx.Input/IMouse.cs index e20e7798d..45307ce06 100644 --- a/src/Ryujinx.Input/IMouse.cs +++ b/src/Ryujinx.Input/IMouse.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Drawing; using System.Numerics; @@ -47,7 +48,7 @@ namespace Ryujinx.Input /// A snaphost of the state of the mouse. public static MouseStateSnapshot GetMouseStateSnapshot(IMouse mouse) { - bool[] buttons = new bool[(int)MouseButton.Count]; + bool[] buttons = ArrayPool.Shared.Rent((int)MouseButton.Count); mouse.Buttons.CopyTo(buttons, 0); diff --git a/src/Ryujinx.Input/KeyboardStateSnapshot.cs b/src/Ryujinx.Input/KeyboardStateSnapshot.cs index e0374a861..9b40b46db 100644 --- a/src/Ryujinx.Input/KeyboardStateSnapshot.cs +++ b/src/Ryujinx.Input/KeyboardStateSnapshot.cs @@ -7,7 +7,7 @@ namespace Ryujinx.Input /// public class KeyboardStateSnapshot { - private readonly bool[] _keysState; + public readonly bool[] KeysState; /// /// Create a new . @@ -15,7 +15,7 @@ namespace Ryujinx.Input /// The keys state public KeyboardStateSnapshot(bool[] keysState) { - _keysState = keysState; + KeysState = keysState; } /// @@ -24,6 +24,6 @@ namespace Ryujinx.Input /// The key /// True if the given key is pressed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsPressed(Key key) => _keysState[(int)key]; + public bool IsPressed(Key key) => KeysState[(int)key]; } } diff --git a/src/Ryujinx.Input/MouseStateSnapshot.cs b/src/Ryujinx.Input/MouseStateSnapshot.cs index 9efc9f9c7..519d119c1 100644 --- a/src/Ryujinx.Input/MouseStateSnapshot.cs +++ b/src/Ryujinx.Input/MouseStateSnapshot.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Input /// public class MouseStateSnapshot { - private readonly bool[] _buttonState; + public readonly bool[] ButtonState; /// /// The position of the mouse cursor @@ -28,7 +28,7 @@ namespace Ryujinx.Input /// The scroll delta public MouseStateSnapshot(bool[] buttonState, Vector2 position, Vector2 scroll) { - _buttonState = buttonState; + ButtonState = buttonState; Position = position; Scroll = scroll; @@ -40,6 +40,6 @@ namespace Ryujinx.Input /// The button /// True if the given button is pressed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsPressed(MouseButton button) => _buttonState[(int)button]; + public bool IsPressed(MouseButton button) => ButtonState[(int)button]; } } diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs index b493134c9..7560d2ae7 100644 --- a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs +++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -38,7 +39,7 @@ namespace Ryujinx.Memory.Range index = ~index; } - RangeItem rangeItem = new(item); + RangeItem rangeItem = _rangeItemPool.Allocate().Set(item); Insert(index, rangeItem); } @@ -144,6 +145,8 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { + _rangeItemPool.Release(Items[index]); + if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -433,7 +436,7 @@ namespace Ryujinx.Memory.Range return (Items[index], Items[endIndex - 1]); } - public RangeItem[] FindOverlapsAsArray(ulong address, ulong size) + public RangeItem[] FindOverlapsAsArray(ulong address, ulong size, out int length) { (int index, int endIndex) = BinarySearchEdges(address, address + size); @@ -441,11 +444,13 @@ namespace Ryujinx.Memory.Range if (index < 0) { - result = []; + result = null; + length = 0; } else { - result = new RangeItem[endIndex - index]; + result = ArrayPool>.Shared.Rent(endIndex - index); + length = endIndex - index; Array.Copy(Items, index, result, 0, endIndex - index); } diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs index be0e34653..63025f1e8 100644 --- a/src/Ryujinx.Memory/Range/RangeList.cs +++ b/src/Ryujinx.Memory/Range/RangeList.cs @@ -36,8 +36,6 @@ namespace Ryujinx.Memory.Range public class RangeList : RangeListBase where T : IRange { public readonly ReaderWriterLockSlim Lock = new(); - - private readonly Dictionary> _quickAccess = new(AddressEqualityComparer.Comparer); /// /// Creates a new range list. @@ -93,11 +91,6 @@ namespace Ryujinx.Memory.Range Items[index + 1].Previous = rangeItem; } - foreach (ulong address in Items[index].QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - Items[index] = rangeItem; return true; @@ -142,11 +135,6 @@ namespace Ryujinx.Memory.Range Items[index + 1].Previous = rangeItem; } - foreach (ulong address in item.QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - Items[index] = rangeItem; return true; @@ -210,11 +198,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { - foreach (ulong address in Items[index].QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -253,15 +236,6 @@ namespace Ryujinx.Memory.Range int startIndex = BinarySearch(startItem.Address); int endIndex = BinarySearch(endItem.Address); - for (int i = startIndex; i <= endIndex; i++) - { - _quickAccess.Remove(Items[i].Address); - foreach (ulong addr in Items[i].QuickAccessAddresses) - { - _quickAccess.Remove(addr); - } - } - if (endIndex < Count - 1) { Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; @@ -349,11 +323,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] public override RangeItem FindOverlapFast(ulong address, ulong size) { - if (_quickAccess.TryGetValue(address, out RangeItem quickResult)) - { - return quickResult; - } - int index = BinarySearch(address, address + size); if (index < 0) @@ -361,12 +330,6 @@ namespace Ryujinx.Memory.Range return null; } - if (Items[index].OverlapsWith(address, address + 1)) - { - _quickAccess.Add(address, Items[index]); - Items[index].QuickAccessAddresses.Add(address); - } - return Items[index]; } diff --git a/src/Ryujinx.Memory/Range/RangeListBase.cs b/src/Ryujinx.Memory/Range/RangeListBase.cs index a1a48b087..01fe1b0dc 100644 --- a/src/Ryujinx.Memory/Range/RangeListBase.cs +++ b/src/Ryujinx.Memory/Range/RangeListBase.cs @@ -1,20 +1,42 @@ -using System.Collections; +using Ryujinx.Common; +using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Ryujinx.Memory.Range { - public class RangeItem(TValue value) where TValue : IRange + public class RangeItem where TValue : IRange { public RangeItem Next; public RangeItem Previous; - public readonly ulong Address = value.Address; - public readonly ulong EndAddress = value.Address + value.Size; + public ulong Address; + public ulong EndAddress; - public readonly TValue Value = value; + public TValue Value; + + public RangeItem() + { + + } + + public RangeItem(TValue value) + { + Address = value.Address; + EndAddress = value.Address + value.Size; + Value = value; + } - public readonly List QuickAccessAddresses = []; + public RangeItem Set(TValue value) + { + Next = null; + Previous = null; + Address = value.Address; + EndAddress = value.Address + value.Size; + Value = value; + + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool OverlapsWith(ulong address, ulong endAddress) @@ -23,20 +45,9 @@ namespace Ryujinx.Memory.Range } } - class AddressEqualityComparer : IEqualityComparer - { - public bool Equals(ulong u1, ulong u2) - { - return u1 == u2; - } - - public int GetHashCode(ulong value) => (int)(value << 5); - - public static readonly AddressEqualityComparer Comparer = new(); - } - public unsafe abstract class RangeListBase : IEnumerable where T : IRange { + protected static readonly ObjectPool> _rangeItemPool = new(() => new RangeItem()); private const int BackingInitialSize = 1024; protected RangeItem[] Items; diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index cd97e6fdf..b4b06da8c 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -1,5 +1,6 @@ using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.Memory.Tracking @@ -300,10 +301,10 @@ namespace Ryujinx.Memory.Tracking // We use the non-span method here because keeping the lock will cause a deadlock. regions.Lock.EnterReadLock(); - RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size); + RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); regions.Lock.ExitReadLock(); - if (overlaps.Length == 0 && !precise) + if (length == 0 && !precise) { if (_memoryManager.IsRangeMapped(address, size)) { @@ -323,8 +324,8 @@ namespace Ryujinx.Memory.Tracking // Increase the access size to trigger handles with misaligned accesses. size += (ulong)_pageSize; } - - for (int i = 0; i < overlaps.Length; i++) + + for (int i = 0; i < length; i++) { VirtualRegion region = overlaps[i].Value; @@ -337,6 +338,11 @@ namespace Ryujinx.Memory.Tracking region.Signal(address, size, write, exemptId); } } + + if (length != 0) + { + ArrayPool>.Shared.Return(overlaps); + } } } diff --git a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs index 20d2a07ef..fdaa87956 100644 --- a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs @@ -123,10 +123,7 @@ namespace Ryujinx.Memory.Tracking Dirty = true; } - public IEnumerable GetHandles() - { - return _handles; - } + public IEnumerable Handles => _handles; public void ForceDirty(ulong address, ulong size) { diff --git a/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs index ccd60e8da..7e350712b 100644 --- a/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs @@ -58,10 +58,7 @@ namespace Ryujinx.Memory.Tracking { foreach (RegionHandle handle in _handles) { - if (handle != null) - { - handle?.RegisterAction((address, size) => action(handle.Address, handle.Size)); - } + handle?.RegisterAction((address, size) => action(handle.Address, handle.Size)); } } @@ -69,10 +66,7 @@ namespace Ryujinx.Memory.Tracking { foreach (RegionHandle handle in _handles) { - if (handle != null) - { - handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write)); - } + handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write)); } } diff --git a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/src/Ryujinx.SDL3.Common/Ryujinx.SDL3.Common.csproj similarity index 75% rename from src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj rename to src/Ryujinx.SDL3.Common/Ryujinx.SDL3.Common.csproj index 895d1a9ce..53e856956 100644 --- a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj +++ b/src/Ryujinx.SDL3.Common/Ryujinx.SDL3.Common.csproj @@ -1,15 +1,16 @@ + true $(DefaultItemExcludes);._* - - - - + + + + diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL3.Common/SDL3Driver.cs similarity index 58% rename from src/Ryujinx.SDL2.Common/SDL2Driver.cs rename to src/Ryujinx.SDL3.Common/SDL3Driver.cs index df11966bc..529a846e0 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL3.Common/SDL3Driver.cs @@ -5,19 +5,20 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Threading; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; -namespace Ryujinx.SDL2.Common +namespace Ryujinx.SDL3.Common { - public class SDL2Driver : IDisposable + public class SDL3Driver : IDisposable { - private static SDL2Driver _instance; + private static SDL3Driver _instance; - public static SDL2Driver Instance + public static SDL3Driver Instance { get { - _instance ??= new SDL2Driver(); + _instance ??= new SDL3Driver(); return _instance; } @@ -25,26 +26,22 @@ namespace Ryujinx.SDL2.Common public static Action MainThreadDispatcher { get; set; } - private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; + private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_EVENTS | SDL_InitFlags.SDL_INIT_GAMEPAD | SDL_InitFlags.SDL_INIT_JOYSTICK | SDL_InitFlags.SDL_INIT_AUDIO | SDL_InitFlags.SDL_INIT_VIDEO; private bool _isRunning; private uint _refereceCount; private Thread _worker; - private const uint SDL_JOYBATTERYUPDATED = 1543; + public event Action OnJoyStickConnected; + public event Action OnJoystickDisconnected; - public event Action OnJoyStickConnected; - public event Action OnJoystickDisconnected; + public event Action OnJoyBatteryUpdated; - public event Action OnJoyBatteryUpdated; - - private ConcurrentDictionary> _registeredWindowHandlers; + private ConcurrentDictionary> _registeredWindowHandlers; private readonly Lock _lock = new(); - private SDL2Driver() { } - - private const string SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS"; + private SDL3Driver() { } public void Initialize() { @@ -58,20 +55,19 @@ namespace Ryujinx.SDL2.Common } SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS , "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); - // NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them. + // NOTE: As of SDL3 2.24.0, joycons are combined by default but the motion source only come from one of them. // We disable this behavior for now. SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0"); - if (SDL_Init(SdlInitFlags) != 0) + if (!SDL_Init(SdlInitFlags)) { - string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\""; + string errorMessage = $"SDL3 initialization failed with error \"{SDL_GetError()}\""; Logger.Error?.Print(LogClass.Application, errorMessage); @@ -79,78 +75,77 @@ namespace Ryujinx.SDL2.Common } // First ensure that we only enable joystick events (for connected/disconnected). - if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE) + SDL_SetGamepadEventsEnabled(false); + SDL_SetJoystickEventsEnabled(true); + if (SDL_GamepadEventsEnabled()) { Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events."); } - if (SDL_JoystickEventState(SDL_ENABLE) < 0) + if (!SDL_JoystickEventsEnabled()) { Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}"); } // Disable all joysticks information, we don't need them no need to flood the event queue for that. - SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE); - SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE); - SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE); - SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE); - SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_AXIS_MOTION, false); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BALL_MOTION, false); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_HAT_MOTION, false); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_DOWN, false); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_UP, false); - SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); + SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_GAMEPAD_SENSOR_UPDATE, false); string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt"); if (File.Exists(gamepadDbPath)) { - SDL_GameControllerAddMappingsFromFile(gamepadDbPath); + SDL_AddGamepadMappingsFromFile(gamepadDbPath); } - _registeredWindowHandlers = new ConcurrentDictionary>(); + _registeredWindowHandlers = new ConcurrentDictionary>(); _worker = new Thread(EventWorker); _isRunning = true; _worker.Start(); } } - public bool RegisterWindow(uint windowId, Action windowEventHandler) + public bool RegisterWindow(SDL_WindowID windowId, Action windowEventHandler) { return _registeredWindowHandlers.TryAdd(windowId, windowEventHandler); } - public void UnregisterWindow(uint windowId) + public void UnregisterWindow(SDL_WindowID windowId) { _registeredWindowHandlers.Remove(windowId, out _); } private void HandleSDLEvent(ref SDL_Event evnt) { - if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED) + SDL_EventType type = evnt.Type; + if (type == SDL_EventType.SDL_EVENT_JOYSTICK_ADDED) { - int deviceId = evnt.cbutton.which; - - // SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system. - int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId); - - if (instanceId == -1) - { - return; - } + SDL_JoystickID instanceId = evnt.jbutton.which; + // SDL3 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system. Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}"); - OnJoyStickConnected?.Invoke(deviceId, instanceId); + OnJoyStickConnected?.Invoke(instanceId); } - else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED) + else if (type == SDL_EventType.SDL_EVENT_JOYSTICK_REMOVED) { - Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}"); + Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.jbutton.which}"); - OnJoystickDisconnected?.Invoke(evnt.cbutton.which); + OnJoystickDisconnected?.Invoke(evnt.jbutton.which); } - else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED) + else if (type == SDL_EventType.SDL_EVENT_JOYSTICK_BATTERY_UPDATED) { - OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code); + OnJoyBatteryUpdated?.Invoke(evnt.jbutton.which, evnt.jbattery.state); } - else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP) + else if ( + ((uint)type >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST && (uint)type <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST) || + type is SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN or SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP + ) { if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action handler)) { @@ -159,7 +154,7 @@ namespace Ryujinx.SDL2.Common } } - private void EventWorker() + private unsafe void EventWorker() { const int WaitTimeMs = 10; @@ -169,7 +164,8 @@ namespace Ryujinx.SDL2.Common { MainThreadDispatcher?.Invoke(() => { - while (SDL_PollEvent(out SDL_Event evnt) != 0) + SDL_Event evnt = new(); + while (SDL_PollEvent(&evnt)) { HandleSDLEvent(ref evnt); } diff --git a/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs b/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs index b35de3a82..e878ff653 100644 --- a/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs +++ b/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs @@ -335,7 +335,7 @@ namespace Ryujinx.Tests.Memory IEnumerable[] handleGroups = [ - granular.GetHandles(), + granular.Handles, singlePages, doublePages ]; @@ -389,7 +389,7 @@ namespace Ryujinx.Tests.Memory Assert.IsTrue(throws); } - IEnumerable combinedHandles = combined.GetHandles(); + IEnumerable combinedHandles = combined.Handles; Assert.AreEqual(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3)); Assert.AreEqual(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4)); diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs index e2fa68397..af61b7b63 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs @@ -1,6 +1,6 @@ using DiscordRPC; using LibHac.Tools.FsSystem; -using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Ava; using Ryujinx.Ava.Systems; using Ryujinx.Ava.Systems.Configuration; @@ -157,7 +157,7 @@ namespace Ryujinx.Headless config = new StandardControllerInputConfig { Version = InputConfig.CurrentVersion, - Backend = InputBackendType.GamepadSDL2, + Backend = InputBackendType.GamepadSDL3, Id = null, ControllerType = ControllerType.JoyconPair, DeadzoneLeft = 0.1f, @@ -305,8 +305,8 @@ namespace Ryujinx.Headless return new VulkanRenderer( api, - (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), - vulkanWindow.GetRequiredInstanceExtensions, + (instance, vk) => new SurfaceKHR((ulong)vulkanWindow.CreateWindowSurface(instance.Handle)), + VulkanWindow.GetRequiredInstanceExtensions, preferredGpuId); } @@ -350,7 +350,7 @@ namespace Ryujinx.Headless _accountManager, _userChannelPersistence, renderer.TryMakeThreaded(options.BackendThreading), - new SDL2HardwareDeviceDriver(), + new SDL3HardwareDeviceDriver(), window ) ); diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 9a06482f9..bba505dbb 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -22,13 +22,14 @@ using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Input; using Ryujinx.Input.HLE; -using Ryujinx.Input.SDL2; -using Ryujinx.SDL2.Common; +using Ryujinx.Input.SDL3; +using Ryujinx.SDL3.Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using SDL; namespace Ryujinx.Headless { @@ -61,7 +62,7 @@ namespace Ryujinx.Headless AutoResetEvent invoked = new(false); // MacOS must perform SDL polls from the main thread. - SDL2Driver.MainThreadDispatcher = action => + SDL3Driver.MainThreadDispatcher = action => { invoked.Reset(); @@ -180,7 +181,7 @@ namespace Ryujinx.Headless _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile); _userChannelPersistence = new UserChannelPersistence(); - _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); + _inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver()); GraphicsConfig.EnableShaderCache = !option.DisableShaderCache; @@ -396,7 +397,7 @@ namespace Ryujinx.Headless _window = window; _window.IsFullscreen = options.IsFullscreen; - _window.DisplayId = options.DisplayId; + _window.DisplayId = (SDL_DisplayID)options.DisplayId; _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 88c74eee5..382294cf7 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -59,10 +59,10 @@ namespace Ryujinx.Headless DisableDockedMode = !configurationState.System.EnableDockedMode; if (NeedsOverride(nameof(SystemLanguage))) - SystemLanguage = configurationState.System.Language.Value.ToHLE(); + SystemLanguage = configurationState.System.Language.Value.Horizon; if (NeedsOverride(nameof(SystemRegion))) - SystemRegion = configurationState.System.Region.Value.ToHLE(); + SystemRegion = configurationState.System.Region.Value.Horizon; if (NeedsOverride(nameof(SystemTimeZone))) SystemTimeZone = configurationState.System.TimeZone; diff --git a/src/Ryujinx/Headless/Windows/OpenGLWindow.cs b/src/Ryujinx/Headless/Windows/OpenGLWindow.cs index 02f24f218..0951f68f5 100644 --- a/src/Ryujinx/Headless/Windows/OpenGLWindow.cs +++ b/src/Ryujinx/Headless/Windows/OpenGLWindow.cs @@ -5,15 +5,17 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.OpenGL; using Ryujinx.Input.HLE; using System; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; +using System.Runtime.InteropServices; namespace Ryujinx.Headless { - class OpenGLWindow : WindowBase + unsafe class OpenGLWindow : WindowBase { - private static void CheckResult(int result) + private static void CheckResult(bool result) { - if (result < 0) + if (!result) { throw new InvalidOperationException($"SDL_GL function returned an error: {SDL_GetError()}"); } @@ -21,21 +23,21 @@ namespace Ryujinx.Headless private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel) { - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 4)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 3)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLProfile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLContextFlag.SDL_GL_CONTEXT_DEBUG_FLAG : 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, 1)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 8)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 0)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1)); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STEREO, 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ACCELERATED_VISUAL, 1)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_RED_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_GREEN_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_BLUE_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ALPHA_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DEPTH_SIZE, 16)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STENCIL_SIZE, 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DOUBLEBUFFER, 1)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STEREO, 0)); } private class OpenToolkitBindingsContext : IBindingsContext @@ -46,35 +48,35 @@ namespace Ryujinx.Headless } } - private class SDL2OpenGLContext : IOpenGLContext + private class SDL3OpenGLContext : IOpenGLContext { - private readonly nint _context; - private readonly nint _window; + private readonly SDL_GLContextState* _context; + private readonly SDL_Window* _window; private readonly bool _shouldDisposeWindow; - public SDL2OpenGLContext(nint context, nint window, bool shouldDisposeWindow = true) + public SDL3OpenGLContext(SDL_GLContextState* context, SDL_Window* window, bool shouldDisposeWindow = true) { _context = context; _window = window; _shouldDisposeWindow = shouldDisposeWindow; } - public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext) + public unsafe static SDL3OpenGLContext CreateBackgroundContext(SDL3OpenGLContext sharedContext) { sharedContext.MakeCurrent(); // Ensure we share our contexts. SetupOpenGLAttributes(true, GraphicsDebugLevel.None); - nint windowHandle = SDL_CreateWindow("Ryujinx background context window", 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN); - nint context = SDL_GL_CreateContext(windowHandle); + SDL_Window* windowHandle = SDL_CreateWindow("Ryujinx background context window", 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN); + SDL_GLContextState* context = SDL_GL_CreateContext(windowHandle); GL.LoadBindings(new OpenToolkitBindingsContext()); - CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0)); - CheckResult(SDL_GL_MakeCurrent(windowHandle, nint.Zero)); + CheckResult(SDL_GL_MakeCurrent(windowHandle, null)); - return new SDL2OpenGLContext(context, windowHandle); + return new SDL3OpenGLContext(context, windowHandle); } public void MakeCurrent() @@ -84,9 +86,9 @@ namespace Ryujinx.Headless return; } - int res = SDL_GL_MakeCurrent(_window, _context); + bool res = SDL_GL_MakeCurrent(_window, _context); - if (res != 0) + if (!res) { string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; @@ -96,11 +98,11 @@ namespace Ryujinx.Headless } } - public bool HasContext() => SDL_GL_GetCurrentContext() != nint.Zero; + public bool HasContext() => SDL_GL_GetCurrentContext() != null; public void Dispose() { - SDL_GL_DeleteContext(_context); + SDL_GL_DestroyContext(_context); if (_shouldDisposeWindow) { @@ -109,7 +111,7 @@ namespace Ryujinx.Headless } } - private SDL2OpenGLContext _openGLContext; + private SDL3OpenGLContext _openGLContext; public OpenGLWindow( InputManager inputManager, @@ -128,10 +130,10 @@ namespace Ryujinx.Headless { // Ensure to not share this context with other contexts before this point. SetupOpenGLAttributes(false, GlLogLevel); - nint context = SDL_GL_CreateContext(WindowHandle); + SDL_GLContextState* context = SDL_GL_CreateContext(WindowHandle); CheckResult(SDL_GL_SetSwapInterval(1)); - if (context == nint.Zero) + if (context == null) { string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; @@ -141,10 +143,10 @@ namespace Ryujinx.Headless } // NOTE: The window handle needs to be disposed by the thread that created it and is handled separately. - _openGLContext = new SDL2OpenGLContext(context, WindowHandle, false); + _openGLContext = new SDL3OpenGLContext(context, WindowHandle, false); // First take exclusivity on the OpenGL context. - ((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext)); + ((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL3OpenGLContext.CreateBackgroundContext(_openGLContext)); _openGLContext.MakeCurrent(); @@ -160,7 +162,8 @@ namespace Ryujinx.Headless else if (IsFullscreen) { // NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow. - if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0) + SDL_Rect displayBounds = new(); + if (!SDL_GetDisplayBounds(DisplayId, &displayBounds)) { Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}"); @@ -189,7 +192,7 @@ namespace Ryujinx.Headless Device.DisposeGpu(); // Unbind context and destroy everything - CheckResult(SDL_GL_MakeCurrent(WindowHandle, nint.Zero)); + CheckResult(SDL_GL_MakeCurrent(WindowHandle, null)); _openGLContext.Dispose(); } diff --git a/src/Ryujinx/Headless/Windows/VulkanWindow.cs b/src/Ryujinx/Headless/Windows/VulkanWindow.cs index 2abbbd1e9..b71dbd728 100644 --- a/src/Ryujinx/Headless/Windows/VulkanWindow.cs +++ b/src/Ryujinx/Headless/Windows/VulkanWindow.cs @@ -1,10 +1,11 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Input.HLE; -using Ryujinx.SDL2.Common; +using Ryujinx.SDL3.Common; using System; +using SDL; +using static SDL.SDL3; using System.Runtime.InteropServices; -using static SDL2.SDL; namespace Ryujinx.Headless { @@ -39,18 +40,15 @@ namespace Ryujinx.Headless } } - private static void BasicInvoke(Action action) + public unsafe nint CreateWindowSurface(nint instance) { - action(); - } - - public nint CreateWindowSurface(nint instance) - { - ulong surfaceHandle = 0; + VkSurfaceKHR_T surface = new(); + VkSurfaceKHR_T* surfaceHandle = &surface; + VkSurfaceKHR_T** surfaceHandleHandle = &surfaceHandle; void CreateSurface() { - if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE) + if (!SDL_Vulkan_CreateSurface(WindowHandle, (VkInstance_T*)instance, null, surfaceHandleHandle)) { string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\""; @@ -60,9 +58,9 @@ namespace Ryujinx.Headless } } - if (SDL2Driver.MainThreadDispatcher != null) + if (SDL3Driver.MainThreadDispatcher != null) { - SDL2Driver.MainThreadDispatcher(CreateSurface); + SDL3Driver.MainThreadDispatcher(CreateSurface); } else { @@ -72,32 +70,22 @@ namespace Ryujinx.Headless return (nint)surfaceHandle; } - public unsafe string[] GetRequiredInstanceExtensions() + public unsafe static string[] GetRequiredInstanceExtensions() { - if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, nint.Zero) == SDL_bool.SDL_TRUE) - { - nint[] rawExtensions = new nint[(int)extensionsCount]; - string[] extensions = new string[(int)extensionsCount]; + uint extensionCount = 0; + byte** extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount); + if (extensionCount == 0) { + string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\""; - fixed (nint* rawExtensionsPtr = rawExtensions) - { - if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (nint)rawExtensionsPtr) == SDL_bool.SDL_TRUE) - { - for (int i = 0; i < extensions.Length; i++) - { - extensions[i] = Marshal.PtrToStringUTF8(rawExtensions[i]); - } + Logger.Error?.Print(LogClass.Application, errorMessage); - return extensions; - } - } + throw new Exception(errorMessage); } - - string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\""; - - Logger.Error?.Print(LogClass.Application, errorMessage); - - throw new Exception(errorMessage); + string[] extensionArr = new string[extensionCount]; + for (int i = 0; i < extensionCount; i++) { + extensionArr[i] = Marshal.PtrToStringUTF8((nint)extensions[i]); + } + return extensionArr; } protected override void FinalizeWindowRenderer() diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 14b090264..8e06a3f20 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -15,37 +15,34 @@ using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.UI; using Ryujinx.Input; using Ryujinx.Input.HLE; -using Ryujinx.Input.SDL2; -using Ryujinx.SDL2.Common; +using Ryujinx.Input.SDL3; +using Ryujinx.SDL3.Common; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using System.Threading; -using static SDL2.SDL; +using SDL; +using static SDL.SDL3; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using Switch = Ryujinx.HLE.Switch; using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; +using LibHac.Util; namespace Ryujinx.Headless { - abstract partial class WindowBase : IHostUIHandler, IDisposable + abstract unsafe partial class WindowBase : IHostUIHandler, IDisposable { protected const int DefaultWidth = 1280; protected const int DefaultHeight = 720; private const int TargetFps = 60; - private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN; + private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS; private SDL_WindowFlags FullscreenFlag = 0; private static readonly ConcurrentQueue _mainThreadActions = new(); - [LibraryImport("SDL2")] - // TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly - private static partial nint SDL_LoadBMP_RW(nint src, int freesrc); - public static void QueueMainThreadAction(Action action) { _mainThreadActions.Enqueue(action); @@ -56,12 +53,12 @@ namespace Ryujinx.Headless public Switch Device { get; private set; } public IRenderer Renderer { get; private set; } - protected nint WindowHandle { get; set; } + protected SDL_Window* WindowHandle { get; set; } public IHostUITheme HostUITheme { get; } public int Width { get; private set; } public int Height { get; private set; } - public int DisplayId { get; set; } + public SDL_DisplayID DisplayId { get; set; } public bool IsFullscreen { get; set; } public bool IsExclusiveFullscreen { get; set; } public int ExclusiveFullscreenWidth { get; set; } @@ -70,7 +67,7 @@ namespace Ryujinx.Headless public ScalingFilter ScalingFilter { get; set; } public int ScalingFilterLevel { get; set; } - protected SDL2MouseDriver MouseDriver; + protected SDL3MouseDriver MouseDriver; private readonly InputManager _inputManager; private readonly IKeyboard _keyboardInterface; protected readonly GraphicsDebugLevel GlLogLevel; @@ -83,7 +80,7 @@ namespace Ryujinx.Headless private long _ticks; private bool _isActive; private bool _isStopped; - private uint _windowId; + private SDL_WindowID _windowId; private string _gpuDriverName; @@ -99,7 +96,7 @@ namespace Ryujinx.Headless HideCursorMode hideCursorMode, bool ignoreControllerApplet) { - MouseDriver = new SDL2MouseDriver(hideCursorMode); + MouseDriver = new SDL3MouseDriver(hideCursorMode); _inputManager = inputManager; _inputManager.SetMouseDriver(MouseDriver); NpadManager = _inputManager.CreateNpadManager(); @@ -116,7 +113,7 @@ namespace Ryujinx.Headless _ignoreControllerApplet = ignoreControllerApplet; HostUITheme = new HeadlessHostUiTheme(); - SDL2Driver.Instance.Initialize(); + SDL3Driver.Instance.Initialize(); } public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse) @@ -155,11 +152,11 @@ namespace Ryujinx.Headless { fixed (byte* iconPtr = iconBytes) { - nint rwOpsStruct = SDL_RWFromConstMem((nint)iconPtr, iconBytes.Length); - nint iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1); + SDL_IOStream* rwOpsStruct = SDL_IOFromConstMem((nint)iconPtr, (nuint)iconBytes.Length); + SDL_Surface* iconHandle = SDL_LoadBMP_IO(rwOpsStruct, true); SDL_SetWindowIcon(WindowHandle, iconHandle); - SDL_FreeSurface(iconHandle); + SDL_DestroySurface(iconHandle); } } } @@ -183,18 +180,27 @@ namespace Ryujinx.Headless Width = ExclusiveFullscreenWidth; Height = ExclusiveFullscreenHeight; - DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY; FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; } else if (IsFullscreen) { - DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; - FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY; + FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_BORDERLESS; } - WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | WindowFlags); + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId)); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId)); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, Width); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, Height); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, (long)(DefaultFlags | FullscreenFlag | WindowFlags)); - if (WindowHandle == nint.Zero) + WindowHandle = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + + if (WindowHandle == null) { string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\""; @@ -206,16 +212,16 @@ namespace Ryujinx.Headless SetWindowIcon(); _windowId = SDL_GetWindowID(WindowHandle); - SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); + SDL3Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); } private void HandleWindowEvent(SDL_Event evnt) { - if (evnt.type == SDL_EventType.SDL_WINDOWEVENT) + if ((uint)evnt.Type >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST && (uint)evnt.Type <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST) { - switch (evnt.window.windowEvent) + switch (evnt.Type) { - case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_EventType.SDL_EVENT_WINDOW_RESIZED: // Unlike on Windows, this event fires on macOS when triggering fullscreen mode. // And promptly crashes the process because `Renderer?.window.SetSize` is undefined. // As we don't need this to fire in either case we can test for fullscreen. @@ -229,7 +235,7 @@ namespace Ryujinx.Headless break; - case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: + case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED: Exit(); break; } @@ -409,7 +415,7 @@ namespace Ryujinx.Headless // Get screen touch position if (!_enableMouse) { - hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL2MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat()); + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL3MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat()); } if (!hasTouch) @@ -461,7 +467,7 @@ namespace Ryujinx.Headless public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) { - // SDL2 doesn't support input dialogs + // SDL3 doesn't support input dialogs userText = "Ryujinx"; return true; @@ -476,7 +482,7 @@ namespace Ryujinx.Headless public bool DisplayCabinetDialog(out string userText) { - // SDL2 doesn't support input dialogs + // SDL3 doesn't support input dialogs userText = "Ryujinx"; return true; @@ -515,27 +521,36 @@ namespace Ryujinx.Headless Exit(); } - public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null) + public unsafe bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null) { - SDL_MessageBoxData data = new() - { - title = title, - message = message, - buttons = new SDL_MessageBoxButtonData[buttonsText.Length], - numbuttons = buttonsText.Length, - window = WindowHandle - }; + SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length]; for (int i = 0; i < buttonsText.Length; i++) { - data.buttons[i] = new SDL_MessageBoxButtonData + string buttonText = buttonsText[i]; + fixed (byte* pButtonText = &buttonText.ToBytes()[0]) + buttons[i] = new SDL_MessageBoxButtonData { - buttonid = i, - text = buttonsText[i], + buttonID = i, + text = pButtonText, }; } - SDL_ShowMessageBox(ref data, out int _); + fixed (byte* pTitle = &title.ToBytes()[0]) + fixed (byte* pMessage = &message.ToBytes()[0]) + fixed (SDL_MessageBoxButtonData* p = &buttons[0]) { + SDL_MessageBoxData data = new() + { + title = pTitle, + message = pMessage, + buttons = p, + numbuttons = buttonsText.Length, + window = WindowHandle + }; + + + SDL_ShowMessageBox(&data, null); + } return true; } @@ -553,11 +568,11 @@ namespace Ryujinx.Headless TouchScreenManager?.Dispose(); NpadManager.Dispose(); - SDL2Driver.Instance.UnregisterWindow(_windowId); + SDL3Driver.Instance.UnregisterWindow(_windowId); SDL_DestroyWindow(WindowHandle); - SDL2Driver.Instance.Dispose(); + SDL3Driver.Instance.Dispose(); } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index f0e0c2ec3..4904b8464 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -19,16 +19,17 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Headless; -using Ryujinx.SDL2.Common; +using Ryujinx.SDL3.Common; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Security.Principal; using System.Threading.Tasks; namespace Ryujinx.Ava { - internal partial class Program + internal static class Program { public static double WindowScaleFactor { get; set; } public static double DesktopScaleFactor { get; set; } = 1.0; @@ -40,19 +41,36 @@ namespace Ryujinx.Ava public static bool UseHardwareAcceleration { get; private set; } public static string BackendThreadingArg { get; private set; } - [LibraryImport("user32.dll", SetLastError = true)] - public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - private const uint MbIconwarning = 0x30; public static int Main(string[] args) { Version = ReleaseInformation.Version; - - if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041)) + + if (OperatingSystem.IsWindows()) { - _ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning); - return 0; + if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041)) + { + _ = Win32NativeInterop.MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning); + return 0; + } + + if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") || + Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)")) + { + _ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning); + return 0; + } + + // The names of everything here makes no sense for what this actually checks for. Thanks, Microsoft. + // If you can't tell by the error string, + // this actually checks if the current process was run with "Run as Administrator" + // ...but this reads like it checks if the current is in/has the Windows admin role? lol + if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + { + _ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run as administrator.", $"Ryujinx {Version}", MbIconwarning); + return 0; + } } PreviewerDetached = true; @@ -131,8 +149,8 @@ namespace Ryujinx.Ava // Initialize Discord integration. DiscordIntegrationModule.Initialize(); - // Initialize SDL2 driver - SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); + // Initialize SDL3 driver + SDL3Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); ReloadConfig(); @@ -178,22 +196,26 @@ namespace Ryujinx.Ava return gameDir; } - public static void ReloadConfig() + public static void ReloadConfig(bool isRunGameWithCustomConfig = false) { string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); - // Now load the configuration as the other subsystems are now registered - if (File.Exists(localConfigurationPath)) - { - ConfigurationPath = localConfigurationPath; - } - else if (File.Exists(appDataConfigurationPath)) - { - ConfigurationPath = appDataConfigurationPath; - } + if (!isRunGameWithCustomConfig) // To return settings from the game folder if the user configuration exists + { + // Now load the configuration as the other subsystems are now registered + if (File.Exists(localConfigurationPath)) + { + ConfigurationPath = localConfigurationPath; + } + else if (File.Exists(appDataConfigurationPath)) + { + ConfigurationPath = appDataConfigurationPath; + } + } + if (ConfigurationPath == null) { // No configuration, we load the default values and save it to disk @@ -215,6 +237,8 @@ namespace Ryujinx.Ava { Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}"); + ConfigurationFileFormat.RenameInvalidConfigFile(ConfigurationPath); + ConfigurationState.Instance.LoadDefault(); } } @@ -278,16 +302,16 @@ namespace Ryujinx.Ava // Check if region was overridden. if (CommandLineState.OverrideSystemRegion is not null) - if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out HLE.HOS.SystemState.RegionCode result)) + if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Region result)) { - ConfigurationState.Instance.System.Region.Value = result.ToUI(); + ConfigurationState.Instance.System.Region.Value = result; } //Check if language was overridden. if (CommandLineState.OverrideSystemLanguage is not null) - if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out HLE.HOS.SystemState.SystemLanguage result)) + if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Language result)) { - ConfigurationState.Instance.System.Language.Value = result.ToUI(); + ConfigurationState.Instance.System.Language.Value = result; } // Check if hardware-acceleration was overridden. diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 3d40ae64e..31dc20aac 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -77,10 +77,10 @@ - + - + diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs index 0aca8d3a4..2eba0d26b 100644 --- a/src/Ryujinx/Systems/AppHost.cs +++ b/src/Ryujinx/Systems/AppHost.cs @@ -8,7 +8,7 @@ using LibHac.Common; using LibHac.Ns; using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Integration; using Ryujinx.Ava.Common; @@ -501,18 +501,12 @@ namespace Ryujinx.Ava.Systems private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) { - if (Device != null) - { - Device.Configuration.IgnoreMissingServices = args.NewValue; - } + Device?.Configuration.IgnoreMissingServices = args.NewValue; } private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) { - if (Device != null) - { - Device.Configuration.AspectRatio = args.NewValue; - } + Device?.Configuration.AspectRatio = args.NewValue; } private void UpdateAntiAliasing(object sender, ReactiveEventArgs e) @@ -949,7 +943,7 @@ namespace Ryujinx.Ava.Systems { List availableBackends = [ - AudioBackend.SDL2, + AudioBackend.SDL3, AudioBackend.SoundIo, AudioBackend.OpenAl, AudioBackend.Dummy @@ -957,6 +951,9 @@ namespace Ryujinx.Ava.Systems AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; + if (preferredBackend is AudioBackend.SDL2) + preferredBackend = AudioBackend.SDL3; + for (int i = 0; i < availableBackends.Count; i++) { if (availableBackends[i] == preferredBackend) @@ -988,7 +985,7 @@ namespace Ryujinx.Ava.Systems deviceDriver = currentBackend switch { - AudioBackend.SDL2 => InitializeAudioBackend(AudioBackend.SDL2, nextBackend), + AudioBackend.SDL3 => InitializeAudioBackend(AudioBackend.SDL3, nextBackend), AudioBackend.SoundIo => InitializeAudioBackend(AudioBackend.SoundIo, nextBackend), AudioBackend.OpenAl => InitializeAudioBackend(AudioBackend.OpenAl, nextBackend), _ => new DummyHardwareDeviceDriver(), diff --git a/src/Ryujinx/Systems/Configuration/AudioBackend.cs b/src/Ryujinx/Systems/Configuration/AudioBackend.cs index a0aa30f38..f1a0c2362 100644 --- a/src/Ryujinx/Systems/Configuration/AudioBackend.cs +++ b/src/Ryujinx/Systems/Configuration/AudioBackend.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Ava.Systems.Configuration Dummy, OpenAl, SoundIo, - SDL2, + SDL3, + SDL2 = SDL3 } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs index 26ea73f73..f0fafb4e0 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs @@ -6,7 +6,9 @@ using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.HLE; +using System; using System.Collections.Generic; +using System.IO; namespace Ryujinx.Ava.Systems.Configuration { @@ -15,7 +17,7 @@ namespace Ryujinx.Ava.Systems.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 70; + public const int CurrentVersion = 71; /// /// Version of the configuration file format @@ -510,6 +512,43 @@ namespace Ryujinx.Ava.Systems.Configuration } } + /// + /// Renames the configuration file when it is deemed invalid + /// + /// The path to the invalid JSON configuration file + /// The path of the renamed invalid JSON configuration file, or null if the rename failed + public static string RenameInvalidConfigFile(string path) + { + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + { + return null; + } + + try + { + string directory = Path.GetDirectoryName(path) ?? string.Empty; + string originalFileName = Path.GetFileName(path); + if (string.IsNullOrWhiteSpace(originalFileName)) + { + return null; + } + string renamedFileName = $"{originalFileName}.{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.invalid"; + string renamedPath = string.IsNullOrEmpty(directory) ? renamedFileName : Path.Combine(directory, renamedFileName); + + File.Move(path, renamedPath, overwrite: false); + + Logger.Warning?.PrintMsg(LogClass.Application, $"Invalid configuration renamed to: {renamedPath}"); + + return renamedPath; + } + catch (Exception ex) + { + Logger.Error?.PrintMsg(LogClass.Application, $"Failed to rename invalid configuration file \"{path}\": {ex}"); + + return null; + } + } + /// /// Save a configuration file to disk /// diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs index a91a74711..163b7e98f 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs @@ -11,6 +11,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using RyuLogger = Ryujinx.Common.Logging.Logger; @@ -27,7 +28,10 @@ namespace Ryujinx.Ava.Systems.Configuration { RyuLogger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {cff.Version}, loading default."); + ConfigurationFileFormat.RenameInvalidConfigFile(configurationFilePath); + LoadDefault(); + return; } foreach ((int newVersion, Action migratorFunction) @@ -167,14 +171,53 @@ namespace Ryujinx.Ava.Systems.Configuration DirtyHacks hacks = new(cff.DirtyHacks ?? []); Hacks.Xc2MenuSoftlockFix.Value = hacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix); - } - if (configurationFileUpdated) + List existingDirs = []; + bool didPathUpdate = false; + + { // Game dirs + foreach (var gameDir in UI.GameDirs.Value) + { + if (Directory.Exists(gameDir)) + { + existingDirs.Add(gameDir); + } + else + { + RyuLogger.Warning?.Print(LogClass.Configuration, $"Path '{gameDir}' seems to no longer exist. Removing it from game directory configuration."); + didPathUpdate = true; + } + } + + UI.GameDirs.Value = existingDirs.ToList(); + } + + existingDirs.Clear(); + + { // Autoload dirs + foreach (var autoloadDir in UI.AutoloadDirs.Value) + { + if (Directory.Exists(autoloadDir)) + { + existingDirs.Add(autoloadDir); + } + else + { + RyuLogger.Warning?.Print(LogClass.Configuration, $"Path '{autoloadDir}' seems to no longer exist. Removing it from auto load directory configuration."); + didPathUpdate = true; + } + } + + UI.AutoloadDirs.Value = existingDirs.ToList(); + } + + if (configurationFileUpdated || didPathUpdate) { ToFileFormat().SaveConfig(configurationFilePath); - RyuLogger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}"); + if (configurationFileUpdated) + RyuLogger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}"); } } @@ -484,7 +527,13 @@ namespace Ryujinx.Ava.Systems.Configuration }; } ), - (69, static cff => cff.SkipUserProfiles = false) + (69, static cff => cff.SkipUserProfiles = false), + // no migration needed for 70 + (71, static cff => + { + if (cff.AudioBackend is AudioBackend.SDL2) + cff.AudioBackend = AudioBackend.SDL3; + }) ); } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs index 405400b81..2b4c8f991 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs @@ -916,8 +916,8 @@ namespace Ryujinx.Ava.Systems.Configuration public HleConfiguration CreateHleConfiguration() => new( System.DramSize, - System.Language.Value.ToHLE(), - System.Region.Value.ToHLE(), + System.Language.Value.Horizon, + System.Region.Value.Horizon, Graphics.VSyncMode, System.EnableDockedMode, System.EnablePtc, diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs index 185aedf64..0e2f6aaec 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs @@ -207,7 +207,7 @@ namespace Ryujinx.Ava.Systems.Configuration System.EnableInternetAccess.Value = false; System.EnableFsIntegrityChecks.Value = true; System.FsGlobalAccessLogMode.Value = 0; - System.AudioBackend.Value = AudioBackend.SDL2; + System.AudioBackend.Value = AudioBackend.SDL3; System.AudioVolume.Value = 1; System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe; System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB; diff --git a/src/Ryujinx/Systems/Configuration/System/Language.cs b/src/Ryujinx/Systems/Configuration/System/Language.cs index ff1476b73..3087653e9 100644 --- a/src/Ryujinx/Systems/Configuration/System/Language.cs +++ b/src/Ryujinx/Systems/Configuration/System/Language.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.SystemState; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.System @@ -28,10 +29,14 @@ namespace Ryujinx.Ava.Systems.Configuration.System public static class LanguageEnumHelper { - public static Language ToUI(this HLE.HOS.SystemState.SystemLanguage hleLanguage) - => (Language)hleLanguage; + extension(SystemLanguage hle) + { + public Language Ui => (Language)hle; + } - public static HLE.HOS.SystemState.SystemLanguage ToHLE(this Language uiLanguage) - => (HLE.HOS.SystemState.SystemLanguage)uiLanguage; + extension(Language ui) + { + public SystemLanguage Horizon => (SystemLanguage)ui; + } } } diff --git a/src/Ryujinx/Systems/Configuration/System/Region.cs b/src/Ryujinx/Systems/Configuration/System/Region.cs index 0c86093cc..2ba657876 100644 --- a/src/Ryujinx/Systems/Configuration/System/Region.cs +++ b/src/Ryujinx/Systems/Configuration/System/Region.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.SystemState; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.System @@ -17,10 +18,14 @@ namespace Ryujinx.Ava.Systems.Configuration.System public static class RegionEnumHelper { - public static Region ToUI(this HLE.HOS.SystemState.RegionCode hleRegion) - => (Region)hleRegion; + extension(RegionCode hle) + { + public Region Ui => (Region)hle; + } - public static HLE.HOS.SystemState.RegionCode ToHLE(this Region uiRegion) - => (HLE.HOS.SystemState.RegionCode)uiRegion; + extension(Region ui) + { + public RegionCode Horizon => (RegionCode)ui; + } } } diff --git a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs index 17f01c136..deb515797 100644 --- a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs +++ b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs @@ -88,14 +88,10 @@ namespace Ryujinx.Ava.Systems { if (showVersionUpToDate) { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); - } + string.Empty, + _versionResponse.ReleaseUrlFormat.Format(currentVersion)); } Logger.Info?.Print(LogClass.Application, "Up to date."); diff --git a/src/Ryujinx/Systems/Updater/Updater.cs b/src/Ryujinx/Systems/Updater/Updater.cs index add940deb..bc45f8ff6 100644 --- a/src/Ryujinx/Systems/Updater/Updater.cs +++ b/src/Ryujinx/Systems/Updater/Updater.cs @@ -60,14 +60,10 @@ namespace Ryujinx.Ava.Systems { if (showVersionUpToDate) { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); - } + string.Empty, + changelogUrl: _versionResponse.ReleaseUrlFormat.Format(currentVersion)); } Logger.Info?.Print(LogClass.Application, "Up to date."); @@ -104,24 +100,20 @@ namespace Ryujinx.Ava.Systems ? $"Canary {currentVersion} → Canary {newVersion}" : $"{currentVersion} → {newVersion}"; - Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString}"); + Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString.Replace("→", "->")}"); - RequestUserToUpdate: // Show a message asking the user if they want to update UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog( LocaleManager.Instance[LocaleKeys.RyujinxUpdater], LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - newVersionString); + newVersionString, + ReleaseInformation.GetChangelogUrl(currentVersion, newVersion)); switch (shouldUpdate) { case UserResult.Yes: await UpdateRyujinx(_versionResponse.ArtifactUrl); break; - // Secondary button maps to no, which in this case is the show changelog button. - case UserResult.No: - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion)); - goto RequestUserToUpdate; default: _running = false; break; diff --git a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs index 838a2f10a..6fdfaab5a 100644 --- a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs @@ -18,6 +18,12 @@ namespace Ryujinx.Ava.UI.Applet { public partial class ProfileSelectorDialog : RyujinxControl { + //Fix compiler warning + public ProfileSelectorDialog() + { + + } + public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel) { DataContext = ViewModel = viewModel; diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs index dd5b7d9f1..bef6fc47d 100644 --- a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -167,10 +167,7 @@ namespace Ryujinx.Ava.UI.Controls private void Message_TextInput(object sender, TextInputEventArgs e) { - if (_host != null) - { - _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); - } + _host?.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); } private void Message_KeyUp(object sender, KeyEventArgs e) diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs index e8730913c..65de07e6e 100644 --- a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs @@ -10,6 +10,7 @@ using FluentAvalonia.Core; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using System; using System.Threading; @@ -102,6 +103,25 @@ namespace Ryujinx.Ava.UI.Helpers return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); } + + public async static Task ShowTextDialogWithButton( + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + string buttonText, + Action onClick, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler deferCloseAction = null) + { + Grid content = CreateTextDialogContentWithButton(primaryText, secondaryText, iconSymbol, buttonText, onClick); + + return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); + } public static async Task ShowDeferredContentDialog( Window window, @@ -173,43 +193,109 @@ namespace Ryujinx.Ava.UI.Helpers MinHeight = 80, }; - SymbolIcon icon = new() + content.Children.Add(new SymbolIcon { Symbol = (Symbol)symbol, Margin = new Thickness(10), FontSize = 40, FlowDirection = FlowDirection.LeftToRight, VerticalAlignment = VerticalAlignment.Center, - }; + GridColumn = 0, + GridRow = 0, + GridRowSpan = 2 + }); - Grid.SetColumn(icon, 0); - Grid.SetRowSpan(icon, 2); - Grid.SetRow(icon, 0); - - TextBlock primaryLabel = new() + content.Children.Add(new TextBlock { Text = primaryText, Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, MaxWidth = 450, - }; + GridColumn = 1, + GridRow = 0 + }); - TextBlock secondaryLabel = new() + content.Children.Add(new TextBlock { Text = secondaryText, Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, MaxWidth = 450, + GridColumn = 1, + GridRow = 1 + }); + + return content; + } + + private static Grid CreateTextDialogContentWithButton(string primaryText, string secondaryText, int symbol, string buttonName, Action onClick) + { + Grid content = new() + { + RowDefinitions = [new(), new(), new(GridLength.Star), new()], + ColumnDefinitions = [new(GridLength.Auto), new()], + + MinHeight = 80, }; - Grid.SetColumn(primaryLabel, 1); - Grid.SetColumn(secondaryLabel, 1); - Grid.SetRow(primaryLabel, 0); - Grid.SetRow(secondaryLabel, 1); + content.Children.Add(new SymbolIcon + { + Symbol = (Symbol)symbol, + Margin = new Thickness(10), + FontSize = 40, + FlowDirection = FlowDirection.LeftToRight, + VerticalAlignment = VerticalAlignment.Center, + GridColumn = 0, + GridRow = 0, + GridRowSpan = 2 + }); - content.Children.Add(icon); - content.Children.Add(primaryLabel); - content.Children.Add(secondaryLabel); + StackPanel buttonContent = new() + { + Orientation = Orientation.Horizontal, + Spacing = 2 + }; + + buttonContent.Children.Add(new TextBlock + { + Text = buttonName, + Margin = new Thickness(2) + }); + + buttonContent.Children.Add(new SymbolIcon + { + FlowDirection = FlowDirection.LeftToRight, + Symbol = Symbol.Open + }); + + content.Children.Add(new TextBlock + { + Text = primaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + GridColumn = 1, + GridRow = 0 + }); + + content.Children.Add(new TextBlock + { + Text = secondaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + GridColumn = 1, + GridRow = 1 + }); + + content.Children.Add(new Button + { + Content = buttonContent, + HorizontalAlignment = HorizontalAlignment.Center, + Command = Commands.Create(onClick), + GridRow = 2, + GridColumnSpan = 2, + }); return content; } @@ -282,15 +368,20 @@ namespace Ryujinx.Ava.UI.Helpers LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Important); - internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText) - => await ShowTextDialog( + internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText, + string changelogUrl) + { + await ShowTextDialogWithButton( LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], primary, secondaryText, - LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + string.Empty, string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], - (int)Symbol.Important); + (int)Symbol.Important, + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + () => OpenHelper.OpenUrl(changelogUrl)); + } internal static async Task CreateWarningDialog(string primary, string secondaryText) => await ShowTextDialog( @@ -340,7 +431,7 @@ namespace Ryujinx.Ava.UI.Helpers return response == UserResult.Yes; } - internal static async Task CreateUpdaterChoiceDialog(string title, string primary, string secondaryText) + internal static async Task CreateUpdaterChoiceDialog(string title, string primary, string secondaryText, string changelogUrl) { if (_isChoiceDialogOpen) { @@ -349,14 +440,16 @@ namespace Ryujinx.Ava.UI.Helpers _isChoiceDialogOpen = true; - UserResult response = await ShowTextDialog( + UserResult response = await ShowTextDialogWithButton( title, primary, secondaryText, LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogNo], (int)Symbol.Help, + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + () => OpenHelper.OpenUrl(changelogUrl), UserResult.Yes); _isChoiceDialogOpen = false; diff --git a/src/Ryujinx/UI/Helpers/ControlExtensions.cs b/src/Ryujinx/UI/Helpers/ControlExtensions.cs new file mode 100644 index 000000000..734128757 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ControlExtensions.cs @@ -0,0 +1,40 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class ControlExtensions + { + extension(Control ctrl) + { + public int GridRow + { + get => Grid.GetRow(ctrl); + set => Grid.SetRow(ctrl, value); + } + + public int GridColumn + { + get => Grid.GetColumn(ctrl); + set => Grid.SetColumn(ctrl, value); + } + + public int GridRowSpan + { + get => Grid.GetRowSpan(ctrl); + set => Grid.SetRowSpan(ctrl, value); + } + + public int GridColumnSpan + { + get => Grid.GetColumnSpan(ctrl); + set => Grid.SetColumnSpan(ctrl, value); + } + + public bool GridIsSharedSizeScope + { + get => Grid.GetIsSharedSizeScope(ctrl); + set => Grid.SetIsSharedSizeScope(ctrl, value); + } + } + } +} diff --git a/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileStatusDetailConverter.cs b/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileStatusDetailConverter.cs index b83fe485d..34734661b 100644 --- a/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileStatusDetailConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileStatusDetailConverter.cs @@ -29,9 +29,11 @@ namespace Ryujinx.Ava.UI.Helpers return null; } - return app.PercentageProgress != null ? null : - app.ProcessingOutcome is not OperationOutcome.Successful and not OperationOutcome.Undetermined ? app.ProcessingOutcome.ToLocalisedText() : - null; + return app.PercentageProgress != null + ? null + : app.ProcessingOutcome is not OperationOutcome.Successful and not OperationOutcome.Undetermined + ? app.ProcessingOutcome.LocalizedText + : null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index fc4193948..0ff7f5fde 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -112,5 +112,8 @@ namespace Ryujinx.Ava.UI.Helpers [LibraryImport("user32.dll", SetLastError = true)] public static partial ushort GetAsyncKeyState(int nVirtKey); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); } } diff --git a/src/Ryujinx/UI/Helpers/XCITrimmerOperationOutcomeHelper.cs b/src/Ryujinx/UI/Helpers/XCITrimmerOperationOutcomeHelper.cs index 1a0e126c8..d58056469 100644 --- a/src/Ryujinx/UI/Helpers/XCITrimmerOperationOutcomeHelper.cs +++ b/src/Ryujinx/UI/Helpers/XCITrimmerOperationOutcomeHelper.cs @@ -5,32 +5,23 @@ namespace Ryujinx.Ava.UI.Helpers { public static class XCIFileTrimmerOperationOutcomeExtensions { - public static string ToLocalisedText(this OperationOutcome operationOutcome) + extension(OperationOutcome opOutcome) { - switch (operationOutcome) + public string LocalizedText => opOutcome switch { - case OperationOutcome.NoTrimNecessary: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary]; - case OperationOutcome.NoUntrimPossible: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileNoUntrimPossible]; - case OperationOutcome.ReadOnlyFileCannotFix: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileReadOnlyFileCannotFix]; - case OperationOutcome.FreeSpaceCheckFailed: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileFreeSpaceCheckFailed]; - case OperationOutcome.InvalidXCIFile: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile]; - case OperationOutcome.FileIOWriteError: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError]; - case OperationOutcome.FileSizeChanged: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged]; - case OperationOutcome.Cancelled: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileCancelled]; - case OperationOutcome.Undetermined: - return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileUndertermined]; - case OperationOutcome.Successful: - default: - return null; - } + OperationOutcome.NoTrimNecessary => LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary], + OperationOutcome.NoUntrimPossible => LocaleManager.Instance[LocaleKeys.TrimXCIFileNoUntrimPossible], + OperationOutcome.ReadOnlyFileCannotFix => LocaleManager.Instance[ + LocaleKeys.TrimXCIFileReadOnlyFileCannotFix], + OperationOutcome.FreeSpaceCheckFailed => LocaleManager.Instance[ + LocaleKeys.TrimXCIFileFreeSpaceCheckFailed], + OperationOutcome.InvalidXCIFile => LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile], + OperationOutcome.FileIOWriteError => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError], + OperationOutcome.FileSizeChanged => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged], + OperationOutcome.Cancelled => LocaleManager.Instance[LocaleKeys.TrimXCIFileCancelled], + OperationOutcome.Undetermined => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileUndertermined], + _ => null + }; } } } diff --git a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs index 526e63afe..0eeef45f5 100644 --- a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs @@ -27,82 +27,136 @@ namespace Ryujinx.Ava.UI.Models.Input public ControllerType ControllerType { get; set; } public PlayerIndex PlayerIndex { get; set; } - [ObservableProperty] private StickInputId _leftJoystick; - [ObservableProperty] private bool _leftInvertStickX; - [ObservableProperty] private bool _leftInvertStickY; - [ObservableProperty] private bool _leftRotate90; - [ObservableProperty] private GamepadInputId _leftStickButton; + [ObservableProperty] + public partial StickInputId LeftJoystick { get; set; } - [ObservableProperty] private StickInputId _rightJoystick; - [ObservableProperty] private bool _rightInvertStickX; - [ObservableProperty] private bool _rightInvertStickY; - [ObservableProperty] private bool _rightRotate90; - [ObservableProperty] private GamepadInputId _rightStickButton; + [ObservableProperty] + public partial bool LeftInvertStickX { get; set; } - [ObservableProperty] private GamepadInputId _dpadUp; - [ObservableProperty] private GamepadInputId _dpadDown; - [ObservableProperty] private GamepadInputId _dpadLeft; - [ObservableProperty] private GamepadInputId _dpadRight; + [ObservableProperty] + public partial bool LeftInvertStickY { get; set; } - [ObservableProperty] private GamepadInputId _buttonMinus; - [ObservableProperty] private GamepadInputId _buttonPlus; + [ObservableProperty] + public partial bool LeftRotate90 { get; set; } - [ObservableProperty] private GamepadInputId _buttonA; - [ObservableProperty] private GamepadInputId _buttonB; - [ObservableProperty] private GamepadInputId _buttonX; - [ObservableProperty] private GamepadInputId _buttonY; + [ObservableProperty] + public partial GamepadInputId LeftStickButton { get; set; } - [ObservableProperty] private GamepadInputId _buttonZl; - [ObservableProperty] private GamepadInputId _buttonZr; + [ObservableProperty] + public partial StickInputId RightJoystick { get; set; } - [ObservableProperty] private GamepadInputId _buttonL; - [ObservableProperty] private GamepadInputId _buttonR; + [ObservableProperty] + public partial bool RightInvertStickX { get; set; } - [ObservableProperty] private GamepadInputId _leftButtonSl; - [ObservableProperty] private GamepadInputId _leftButtonSr; + [ObservableProperty] + public partial bool RightInvertStickY { get; set; } - [ObservableProperty] private GamepadInputId _rightButtonSl; - [ObservableProperty] private GamepadInputId _rightButtonSr; + [ObservableProperty] + public partial bool RightRotate90 { get; set; } - [ObservableProperty] private float _deadzoneLeft; - [ObservableProperty] private float _deadzoneRight; + [ObservableProperty] + public partial GamepadInputId RightStickButton { get; set; } - [ObservableProperty] private float _rangeLeft; - [ObservableProperty] private float _rangeRight; + [ObservableProperty] + public partial GamepadInputId DpadUp { get; set; } - [ObservableProperty] private float _triggerThreshold; + [ObservableProperty] + public partial GamepadInputId DpadDown { get; set; } - [ObservableProperty] private bool _enableMotion; + [ObservableProperty] + public partial GamepadInputId DpadLeft { get; set; } - [ObservableProperty] private bool _enableRumble; + [ObservableProperty] + public partial GamepadInputId DpadRight { get; set; } - [ObservableProperty] private bool _enableLedChanging; + [ObservableProperty] + public partial GamepadInputId ButtonMinus { get; set; } - [ObservableProperty] private Color _ledColor; + [ObservableProperty] + public partial GamepadInputId ButtonPlus { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonA { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonB { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonX { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonY { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonZl { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonZr { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonL { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonR { get; set; } + + [ObservableProperty] + public partial GamepadInputId LeftButtonSl { get; set; } + + [ObservableProperty] + public partial GamepadInputId LeftButtonSr { get; set; } + + [ObservableProperty] + public partial GamepadInputId RightButtonSl { get; set; } + + [ObservableProperty] + public partial GamepadInputId RightButtonSr { get; set; } + + [ObservableProperty] + public partial float DeadzoneLeft { get; set; } + + [ObservableProperty] + public partial float DeadzoneRight { get; set; } + + [ObservableProperty] + public partial float RangeLeft { get; set; } + + [ObservableProperty] + public partial float RangeRight { get; set; } + + [ObservableProperty] + public partial float TriggerThreshold { get; set; } + + [ObservableProperty] + public partial bool EnableMotion { get; set; } + + [ObservableProperty] + public partial bool EnableRumble { get; set; } + + [ObservableProperty] + public partial bool EnableLedChanging { get; set; } + + [ObservableProperty] + public partial Color LedColor { get; set; } public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed; - private bool _turnOffLed; - public bool TurnOffLed { - get => _turnOffLed; + get; set { - _turnOffLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } } - private bool _useRainbowLed; - public bool UseRainbowLed { - get => _useRainbowLed; + get; set { - _useRainbowLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } @@ -205,7 +259,7 @@ namespace Ryujinx.Ava.UI.Models.Input { Id = Id, Name = Name, - Backend = InputBackendType.GamepadSDL2, + Backend = InputBackendType.GamepadSDL3, PlayerIndex = PlayerIndex, ControllerType = ControllerType, LeftJoycon = new LeftJoyconCommonConfig diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs index 9e557d7b1..545af7876 100644 --- a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -6,31 +6,44 @@ namespace Ryujinx.Ava.UI.Models.Input { public partial class HotkeyConfig : BaseModel { - [ObservableProperty] private Key _toggleVSyncMode; + [ObservableProperty] + public partial Key ToggleVSyncMode { get; set; } - [ObservableProperty] private Key _screenshot; + [ObservableProperty] + public partial Key Screenshot { get; set; } - [ObservableProperty] private Key _showUI; + [ObservableProperty] + public partial Key ShowUI { get; set; } - [ObservableProperty] private Key _pause; + [ObservableProperty] + public partial Key Pause { get; set; } - [ObservableProperty] private Key _toggleMute; + [ObservableProperty] + public partial Key ToggleMute { get; set; } - [ObservableProperty] private Key _resScaleUp; + [ObservableProperty] + public partial Key ResScaleUp { get; set; } - [ObservableProperty] private Key _resScaleDown; + [ObservableProperty] + public partial Key ResScaleDown { get; set; } - [ObservableProperty] private Key _volumeUp; + [ObservableProperty] + public partial Key VolumeUp { get; set; } - [ObservableProperty] private Key _volumeDown; + [ObservableProperty] + public partial Key VolumeDown { get; set; } - [ObservableProperty] private Key _customVSyncIntervalIncrement; + [ObservableProperty] + public partial Key CustomVSyncIntervalIncrement { get; set; } - [ObservableProperty] private Key _customVSyncIntervalDecrement; + [ObservableProperty] + public partial Key CustomVSyncIntervalDecrement { get; set; } - [ObservableProperty] private Key _turboMode; + [ObservableProperty] + public partial Key TurboMode { get; set; } - [ObservableProperty] private bool _turboModeWhileHeld; + [ObservableProperty] + public partial bool TurboModeWhileHeld { get; set; } public HotkeyConfig(KeyboardHotkeys config) { diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs index c8169b6d5..de51d9d70 100644 --- a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -12,42 +12,89 @@ namespace Ryujinx.Ava.UI.Models.Input public ControllerType ControllerType { get; set; } public PlayerIndex PlayerIndex { get; set; } - [ObservableProperty] private Key _leftStickUp; - [ObservableProperty] private Key _leftStickDown; - [ObservableProperty] private Key _leftStickLeft; - [ObservableProperty] private Key _leftStickRight; - [ObservableProperty] private Key _leftStickButton; + [ObservableProperty] + public partial Key LeftStickUp { get; set; } - [ObservableProperty] private Key _rightStickUp; - [ObservableProperty] private Key _rightStickDown; - [ObservableProperty] private Key _rightStickLeft; - [ObservableProperty] private Key _rightStickRight; - [ObservableProperty] private Key _rightStickButton; + [ObservableProperty] + public partial Key LeftStickDown { get; set; } - [ObservableProperty] private Key _dpadUp; - [ObservableProperty] private Key _dpadDown; - [ObservableProperty] private Key _dpadLeft; - [ObservableProperty] private Key _dpadRight; + [ObservableProperty] + public partial Key LeftStickLeft { get; set; } - [ObservableProperty] private Key _buttonMinus; - [ObservableProperty] private Key _buttonPlus; + [ObservableProperty] + public partial Key LeftStickRight { get; set; } - [ObservableProperty] private Key _buttonA; - [ObservableProperty] private Key _buttonB; - [ObservableProperty] private Key _buttonX; - [ObservableProperty] private Key _buttonY; + [ObservableProperty] + public partial Key LeftStickButton { get; set; } - [ObservableProperty] private Key _buttonL; - [ObservableProperty] private Key _buttonR; + [ObservableProperty] + public partial Key RightStickUp { get; set; } - [ObservableProperty] private Key _buttonZl; - [ObservableProperty] private Key _buttonZr; + [ObservableProperty] + public partial Key RightStickDown { get; set; } - [ObservableProperty] private Key _leftButtonSl; - [ObservableProperty] private Key _leftButtonSr; + [ObservableProperty] + public partial Key RightStickLeft { get; set; } - [ObservableProperty] private Key _rightButtonSl; - [ObservableProperty] private Key _rightButtonSr; + [ObservableProperty] + public partial Key RightStickRight { get; set; } + + [ObservableProperty] + public partial Key RightStickButton { get; set; } + + [ObservableProperty] + public partial Key DpadUp { get; set; } + + [ObservableProperty] + public partial Key DpadDown { get; set; } + + [ObservableProperty] + public partial Key DpadLeft { get; set; } + + [ObservableProperty] + public partial Key DpadRight { get; set; } + + [ObservableProperty] + public partial Key ButtonMinus { get; set; } + + [ObservableProperty] + public partial Key ButtonPlus { get; set; } + + [ObservableProperty] + public partial Key ButtonA { get; set; } + + [ObservableProperty] + public partial Key ButtonB { get; set; } + + [ObservableProperty] + public partial Key ButtonX { get; set; } + + [ObservableProperty] + public partial Key ButtonY { get; set; } + + [ObservableProperty] + public partial Key ButtonL { get; set; } + + [ObservableProperty] + public partial Key ButtonR { get; set; } + + [ObservableProperty] + public partial Key ButtonZl { get; set; } + + [ObservableProperty] + public partial Key ButtonZr { get; set; } + + [ObservableProperty] + public partial Key LeftButtonSl { get; set; } + + [ObservableProperty] + public partial Key LeftButtonSr { get; set; } + + [ObservableProperty] + public partial Key RightButtonSl { get; set; } + + [ObservableProperty] + public partial Key RightButtonSr { get; set; } public KeyboardInputConfig(InputConfig config) { diff --git a/src/Ryujinx/UI/Models/ModModel.cs b/src/Ryujinx/UI/Models/ModModel.cs index 91804d365..307674d81 100644 --- a/src/Ryujinx/UI/Models/ModModel.cs +++ b/src/Ryujinx/UI/Models/ModModel.cs @@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Models { public partial class ModModel : BaseModel { - [ObservableProperty] private bool _enabled; - + [ObservableProperty] + public partial bool Enabled { get; set; } public bool InSd { get; } public string Path { get; } public string Name { get; } diff --git a/src/Ryujinx/UI/Models/ProfileImageModel.cs b/src/Ryujinx/UI/Models/ProfileImageModel.cs index f12aa7bd4..ef313d225 100644 --- a/src/Ryujinx/UI/Models/ProfileImageModel.cs +++ b/src/Ryujinx/UI/Models/ProfileImageModel.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Ava.UI.Models public string Name { get; set; } public byte[] Data { get; set; } - [ObservableProperty] private SolidColorBrush _backgroundColor = new(Colors.White); + [ObservableProperty] + public partial SolidColorBrush BackgroundColor { get; set; } = new(Colors.White); } } diff --git a/src/Ryujinx/UI/Models/TempProfile.cs b/src/Ryujinx/UI/Models/TempProfile.cs index 51e86fb7f..a4a4fe32f 100644 --- a/src/Ryujinx/UI/Models/TempProfile.cs +++ b/src/Ryujinx/UI/Models/TempProfile.cs @@ -7,24 +7,26 @@ namespace Ryujinx.Ava.UI.Models { public partial class TempProfile : BaseModel { - [ObservableProperty] private byte[] _image; - [ObservableProperty] private string _name = String.Empty; - private UserId _userId; + [ObservableProperty] + public partial byte[] Image { get; set; } + + [ObservableProperty] + public partial string Name { get; set; } = string.Empty; public static uint MaxProfileNameLength => 0x20; public UserId UserId { - get => _userId; + get; set { - _userId = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(UserIdString)); } } - public string UserIdString => _userId.ToString(); + public string UserIdString => UserId.ToString(); public TempProfile(UserProfile profile) { diff --git a/src/Ryujinx/UI/Models/UserProfile.cs b/src/Ryujinx/UI/Models/UserProfile.cs index f14e74d07..9ebecf8d4 100644 --- a/src/Ryujinx/UI/Models/UserProfile.cs +++ b/src/Ryujinx/UI/Models/UserProfile.cs @@ -13,11 +13,20 @@ namespace Ryujinx.Ava.UI.Models { private readonly Profile _profile; private readonly NavigationDialogHost _owner; - [ObservableProperty] private byte[] _image; - [ObservableProperty] private string _name; - [ObservableProperty] private UserId _userId; - [ObservableProperty] private bool _isPointerOver; - [ObservableProperty] private IBrush _backgroundColor; + [ObservableProperty] + public partial byte[] Image { get; set; } + + [ObservableProperty] + public partial string Name { get; set; } + + [ObservableProperty] + public partial UserId UserId { get; set; } + + [ObservableProperty] + public partial bool IsPointerOver { get; set; } + + [ObservableProperty] + public partial IBrush BackgroundColor { get; set; } public UserProfile(Profile profile, NavigationDialogHost owner) { @@ -39,7 +48,7 @@ namespace Ryujinx.Ava.UI.Models private void UpdateBackground() { - Application currentApplication = Avalonia.Application.Current; + Application currentApplication = Application.Current; currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color); if (color is not null) diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index 727294992..47a99d886 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -11,13 +11,17 @@ namespace Ryujinx.Ava.UI.ViewModels { public partial class AboutWindowViewModel : BaseModel, IDisposable { - [ObservableProperty] private Bitmap _gitLabLogo; - [ObservableProperty] private Bitmap _discordLogo; - [ObservableProperty] private string _version; + [ObservableProperty] public partial Bitmap GitLabLogo { get; set; } - public string Developers => "GreemDev, LotP"; + [ObservableProperty] public partial Bitmap DiscordLogo { get; set; } - public string FormerDevelopers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); + [ObservableProperty] public partial string Version { get; set; } + + public static string Developers => "GreemDev, LotP"; + + public static string FormerDevelopers => LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.AboutPageDeveloperListMore, + "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); public AboutWindowViewModel() { @@ -36,7 +40,8 @@ namespace Ryujinx.Ava.UI.ViewModels private void UpdateLogoTheme(string theme) { - bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark); + bool isDarkTheme = theme == "Dark" || + (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark); string themeName = isDarkTheme ? "Dark" : "Light"; diff --git a/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs index 0ba071475..053972c2c 100644 --- a/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs @@ -2,6 +2,7 @@ using Avalonia; using Avalonia.Collections; using Avalonia.Media.Imaging; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Models.Amiibo; using Ryujinx.Ava.UI.Helpers; @@ -23,7 +24,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels { - public class AmiiboWindowViewModel : BaseModel, IDisposable + public partial class AmiiboWindowViewModel : BaseModel, IDisposable { // ReSharper disable once InconsistentNaming private static bool _cachedUseRandomUuid; @@ -36,17 +37,13 @@ namespace Ryujinx.Ava.UI.ViewModels private readonly HttpClient _httpClient; private readonly AmiiboWindow _owner; - private Bitmap _amiiboImage; private List _amiiboList; private AvaloniaList _amiibos; private ObservableCollection _amiiboSeries; private int _amiiboSelectedIndex; private int _seriesSelectedIndex; - private bool _enableScanning; private bool _showAllAmiibo; - private bool _useRandomUuid = _cachedUseRandomUuid; - private string _usage; private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); @@ -83,14 +80,14 @@ namespace Ryujinx.Ava.UI.ViewModels public bool UseRandomUuid { - get => _useRandomUuid; + get; set { - _cachedUseRandomUuid = _useRandomUuid = value; + _cachedUseRandomUuid = field = value; OnPropertyChanged(); } - } + } = _cachedUseRandomUuid; public bool ShowAllAmiibo { @@ -154,38 +151,14 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public Bitmap AmiiboImage - { - get => _amiiboImage; - set - { - _amiiboImage = value; + [ObservableProperty] + public partial Bitmap AmiiboImage { get; set; } - OnPropertyChanged(); - } - } + [ObservableProperty] + public partial string Usage { get; set; } - public string Usage - { - get => _usage; - set - { - _usage = value; - - OnPropertyChanged(); - } - } - - public bool EnableScanning - { - get => _enableScanning; - set - { - _enableScanning = value; - - OnPropertyChanged(); - } - } + [ObservableProperty] + public partial bool EnableScanning { get; set; } public void Scan() { diff --git a/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs b/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs index 643614d6e..b280c96c9 100644 --- a/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs @@ -7,14 +7,16 @@ namespace Ryujinx.Ava.UI.ViewModels { public partial class DlcSelectViewModel : BaseModel { - [ObservableProperty] private DownloadableContentModel[] _dlcs; + [ObservableProperty] + public partial DownloadableContentModel[] Dlcs { get; set; } #nullable enable - [ObservableProperty] private DownloadableContentModel? _selectedDlc; + [ObservableProperty] + public partial DownloadableContentModel? SelectedDlc { get; set; } #nullable disable public DlcSelectViewModel(ulong titleId, ApplicationLibrary appLibrary) { - _dlcs = appLibrary.FindDlcsFor(titleId) + Dlcs = appLibrary.FindDlcsFor(titleId) .OrderBy(it => it.IsBundled ? 0 : 1) .ThenBy(it => it.TitleId) .ToArray(); diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs index c048b481c..39e53184f 100644 --- a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -19,10 +19,14 @@ namespace Ryujinx.Ava.UI.ViewModels public partial class DownloadableContentManagerViewModel : BaseModel { private readonly ApplicationLibrary _applicationLibrary; - private AvaloniaList _downloadableContents = []; - [ObservableProperty] private AvaloniaList _selectedDownloadableContents = []; - [ObservableProperty] private AvaloniaList _views = []; - [ObservableProperty] private bool _showBundledContentNotice = false; + [ObservableProperty] + public partial AvaloniaList SelectedDownloadableContents { get; set; } = []; + + [ObservableProperty] + public partial AvaloniaList Views { get; set; } = []; + + [ObservableProperty] + public partial bool ShowBundledContentNotice { get; set; } = false; private string _search; private readonly ApplicationData _applicationData; @@ -30,15 +34,15 @@ namespace Ryujinx.Ava.UI.ViewModels public AvaloniaList DownloadableContents { - get => _downloadableContents; + get; set { - _downloadableContents = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(UpdateCount)); Sort(); } - } + } = []; public string Search { @@ -51,10 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public string UpdateCount - { - get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); - } + public string UpdateCount => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData) { diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs index 61d3ffd59..2949b69a8 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -4,55 +4,50 @@ using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Common.Utilities; using Ryujinx.UI.Views.Input; -using System.Drawing; namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class ControllerInputViewModel : BaseModel { - private GamepadInputConfig _config; public GamepadInputConfig Config { - get => _config; + get; set { - _config = value; + field = value; OnPropertyChanged(); } } - private StickVisualizer _visualizer; public StickVisualizer Visualizer { - get => _visualizer; + get; set { - _visualizer = value; + field = value; OnPropertyChanged(); } } - private bool _isLeft; public bool IsLeft { - get => _isLeft; + get; set { - _isLeft = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasSides)); } } - private bool _isRight; public bool IsRight { - get => _isRight; + get; set { - _isRight = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasSides)); } @@ -60,8 +55,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool HasSides => IsLeft ^ IsRight; - [ObservableProperty] private SvgImage _image; - + [ObservableProperty] + public partial SvgImage Image { get; set; } public InputViewModel ParentModel { get; } public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer) @@ -75,7 +70,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input if (args.PropertyName is nameof(Config.UseRainbowLed)) { if (Config is { UseRainbowLed: true, TurnOffLed: false, EnableLedChanging: true }) - Rainbow.Updated += (ref Color color) => ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb()); + Rainbow.Updated += (ref color) => ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb()); else { Rainbow.Reset(); diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index fe2dd4db8..289dc0e9c 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -48,36 +48,41 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private int _controller; private string _controllerImage; private int _device; - private object _configViewModel; private bool _isChangeTrackingActive; - private string _chosenProfile; - [ObservableProperty] private bool _isModified; - [ObservableProperty] private string _profileName; - [ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged() - [ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged() + [ObservableProperty] + public partial bool IsModified { get; set; } + + [ObservableProperty] + public partial string ProfileName { get; set; } + + [ObservableProperty] + public partial bool NotificationIsVisible { get; set; } // Automatically call the NotificationView property with OnPropertyChanged() + + [ObservableProperty] + public partial string NotificationText { get; set; } // Automatically call the NotificationText property with OnPropertyChanged() + private bool _isLoaded; private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); public IGamepadDriver AvaloniaKeyboardDriver { get; } - private IGamepad _selectedGamepad; - public IGamepad SelectedGamepad { - get => _selectedGamepad; + get; private set { Rainbow.Reset(); - _selectedGamepad = value; + field = value; if (ConfigViewModel is ControllerInputViewModel { Config.UseRainbowLed: true }) - Rainbow.Updated += (ref Color color) => _selectedGamepad.SetLed((uint)color.ToArgb()); + Rainbow.Updated += (ref Color color) => field.SetLed((uint)color.ToArgb()); OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); } } + public StickVisualizer VisualStick { get; private set; } public ObservableCollection PlayerIndexes { get; set; } @@ -95,19 +100,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool IsRight { get; set; } public bool IsLeft { get; set; } public string RevertDeviceId { get; set; } - public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led); + public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); public event Action NotifyChangesEvent; - + public string ChosenProfile { - get => _chosenProfile; + get; set { // When you select a profile, the settings from the profile will be applied. // To save the settings, you still need to click the apply button - _chosenProfile = value; + field = value; LoadProfile(); OnPropertyChanged(); } @@ -115,10 +120,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public object ConfigViewModel { - get => _configViewModel; + get; set { - _configViewModel = value; + field = value; VisualStick.UpdateConfig(value); @@ -714,7 +719,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input config = new StandardControllerInputConfig { Version = InputConfig.CurrentVersion, - Backend = InputBackendType.GamepadSDL2, + Backend = InputBackendType.GamepadSDL3, Id = id, Name = name, ControllerType = ControllerType.ProController, diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs index bab8db7ce..178e2c955 100644 --- a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -6,49 +6,45 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class KeyboardInputViewModel : BaseModel { - private KeyboardInputConfig _config; public KeyboardInputConfig Config { - get => _config; + get; set { - _config = value; + field = value; OnPropertyChanged(); } } - private StickVisualizer _visualizer; public StickVisualizer Visualizer { - get => _visualizer; + get; set { - _visualizer = value; + field = value; OnPropertyChanged(); } } - private bool _isLeft; public bool IsLeft { - get => _isLeft; + get; set { - _isLeft = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasSides)); } } - private bool _isRight; public bool IsRight { - get => _isRight; + get; set { - _isRight = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasSides)); } @@ -56,7 +52,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool HasSides => IsLeft ^ IsRight; - [ObservableProperty] private SvgImage _image; + [ObservableProperty] + public partial SvgImage Image { get; set; } public readonly InputViewModel ParentModel; diff --git a/src/Ryujinx/UI/ViewModels/Input/LedInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/LedInputViewModel.cs index 71c404c21..516b892b5 100644 --- a/src/Ryujinx/UI/ViewModels/Input/LedInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/LedInputViewModel.cs @@ -23,8 +23,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input ParentModel.SelectedGamepad.SetLed(LedColor.ToUInt32()); }); - [ObservableProperty] private bool _enableLedChanging; - [ObservableProperty] private Color _ledColor; + [ObservableProperty] + public partial bool EnableLedChanging { get; set; } + + [ObservableProperty] + public partial Color LedColor { get; set; } public string RainbowSpeedText => RainbowSpeed.ToString(CultureInfo.CurrentCulture).Truncate(4, string.Empty); @@ -41,27 +44,23 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed; - private bool _turnOffLed; - public bool TurnOffLed { - get => _turnOffLed; + get; set { - _turnOffLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } } - private bool _useRainbowLed; - public bool UseRainbowLed { - get => _useRainbowLed; + get; set { - _useRainbowLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } diff --git a/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs index ba8686831..ad9a70eaa 100644 --- a/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs @@ -4,20 +4,28 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class MotionInputViewModel : BaseModel { - [ObservableProperty] private int _slot; + [ObservableProperty] + public partial int Slot { get; set; } - [ObservableProperty] private int _altSlot; + [ObservableProperty] + public partial int AltSlot { get; set; } - [ObservableProperty] private string _dsuServerHost; + [ObservableProperty] + public partial string DsuServerHost { get; set; } - [ObservableProperty] private int _dsuServerPort; + [ObservableProperty] + public partial int DsuServerPort { get; set; } - [ObservableProperty] private bool _mirrorInput; + [ObservableProperty] + public partial bool MirrorInput { get; set; } - [ObservableProperty] private int _sensitivity; + [ObservableProperty] + public partial int Sensitivity { get; set; } - [ObservableProperty] private double _gyroDeadzone; + [ObservableProperty] + public partial double GyroDeadzone { get; set; } - [ObservableProperty] private bool _enableCemuHookMotion; + [ObservableProperty] + public partial bool EnableCemuHookMotion { get; set; } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs index c4158fced..e2323f567 100644 --- a/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs @@ -4,8 +4,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class RumbleInputViewModel : BaseModel { - [ObservableProperty] private float _strongRumble; + [ObservableProperty] + public partial float StrongRumble { get; set; } - [ObservableProperty] private float _weakRumble; + [ObservableProperty] + public partial float WeakRumble { get; set; } } } diff --git a/src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs b/src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs index cdce9262d..91ebccabb 100644 --- a/src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs @@ -91,10 +91,8 @@ namespace Ryujinx.Ava.UI.ViewModels OnPropertyChanged(nameof(VisibleEntries)); } - [ObservableProperty] private bool _isRefreshing; - private bool _onlyShowForOwnedGames; - private bool _onlyShowPublicGames = true; - private bool _onlyShowJoinableGames = true; + [ObservableProperty] + public partial bool IsRefreshing { get; set; } public async Task RefreshAsync() { @@ -109,12 +107,12 @@ namespace Ryujinx.Ava.UI.ViewModels public bool OnlyShowForOwnedGames { - get => _onlyShowForOwnedGames; + get; set { OnPropertyChanging(); OnPropertyChanging(nameof(VisibleEntries)); - _onlyShowForOwnedGames = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(VisibleEntries)); } @@ -122,29 +120,29 @@ namespace Ryujinx.Ava.UI.ViewModels public bool OnlyShowPublicGames { - get => _onlyShowPublicGames; + get; set { OnPropertyChanging(); OnPropertyChanging(nameof(VisibleEntries)); - _onlyShowPublicGames = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(VisibleEntries)); } - } + } = true; public bool OnlyShowJoinableGames { - get => _onlyShowJoinableGames; + get; set { OnPropertyChanging(); OnPropertyChanging(nameof(VisibleEntries)); - _onlyShowJoinableGames = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(VisibleEntries)); } - } + } = true; public void NameSorting(int nameSort = 0) diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index b665f23c9..2236b27f6 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -64,55 +64,101 @@ namespace Ryujinx.Ava.UI.ViewModels public partial class MainWindowViewModel : BaseModel { private const int HotKeyPressDelayMs = 500; + private delegate int LoadContentFromFolderDelegate(List dirs, out int numRemoved); - [ObservableProperty] private ObservableCollectionExtended _applications; - [ObservableProperty] private string _aspectRatioStatusText; - [ObservableProperty] private string _loadHeading; - [ObservableProperty] private string _cacheLoadStatus; - [ObservableProperty] private string _dockedStatusText; - [ObservableProperty] private string _fifoStatusText; - [ObservableProperty] private string _gameStatusText; - [ObservableProperty] private string _volumeStatusText; - [ObservableProperty] private string _gpuNameText; - [ObservableProperty] private string _backendText; - [ObservableProperty] private string _shaderCountText; - [ObservableProperty] private bool _showShaderCompilationHint; - [ObservableProperty] private bool _isFullScreen; - [ObservableProperty] private int _progressMaximum; - [ObservableProperty] private int _progressValue; - [ObservableProperty] private bool _showMenuAndStatusBar = true; - [ObservableProperty] private bool _showStatusSeparator; - [ObservableProperty] private Brush _progressBarForegroundColor; - [ObservableProperty] private Brush _progressBarBackgroundColor; - [ObservableProperty] private Brush _vSyncModeColor; -#nullable enable - [ObservableProperty] private byte[]? _selectedIcon; -#nullable disable - [ObservableProperty] private int _statusBarProgressMaximum; - [ObservableProperty] private int _statusBarProgressValue; - [ObservableProperty] private string _statusBarProgressStatusText; - [ObservableProperty] private bool _statusBarProgressStatusVisible; - [ObservableProperty] private bool _isPaused; - [ObservableProperty] private bool _isLoadingIndeterminate = true; - [ObservableProperty] private bool _showAll; - [ObservableProperty] private string _lastScannedAmiiboId; + [ObservableProperty] public partial ObservableCollectionExtended Applications { get; set; } + + [ObservableProperty] public partial string AspectRatioStatusText { get; set; } + + [ObservableProperty] public partial string LoadHeading { get; set; } + + [ObservableProperty] public partial string CacheLoadStatus { get; set; } + + [ObservableProperty] public partial string DockedStatusText { get; set; } + + [ObservableProperty] public partial string FifoStatusText { get; set; } + + [ObservableProperty] public partial string GameStatusText { get; set; } + + [ObservableProperty] public partial string VolumeStatusText { get; set; } + + [ObservableProperty] public partial string GpuNameText { get; set; } + + [ObservableProperty] public partial string BackendText { get; set; } + + [ObservableProperty] public partial string ShaderCountText { get; set; } + + [ObservableProperty] public partial bool ShowShaderCompilationHint { get; set; } + + [ObservableProperty] public partial bool IsFullScreen { get; set; } + + [ObservableProperty] public partial int ProgressMaximum { get; set; } + + [ObservableProperty] public partial int ProgressValue { get; set; } + + [ObservableProperty] public partial bool ShowMenuAndStatusBar { get; set; } = true; + + [ObservableProperty] public partial bool ShowStatusSeparator { get; set; } + + [ObservableProperty] public partial Brush ProgressBarForegroundColor { get; set; } + + [ObservableProperty] public partial Brush ProgressBarBackgroundColor { get; set; } + +#pragma warning disable MVVMTK0042 // Must stay a normal observable field declaration since this is used as an out parameter target [ObservableProperty] private ReadOnlyObservableCollection _appsObservableList; - [ObservableProperty] private long _lastFullscreenToggle = Environment.TickCount64; - [ObservableProperty] private bool _showContent = true; - [ObservableProperty] private float _volumeBeforeMute; - [ObservableProperty] private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered; - [ObservableProperty] private Cursor _cursor; - [ObservableProperty] private string _title; - [ObservableProperty] private WindowState _windowState; - [ObservableProperty] private double _windowWidth; - [ObservableProperty] private double _windowHeight; - [ObservableProperty] private bool _isActive; - [ObservableProperty] private bool _isSubMenuOpen; - [ObservableProperty] private ApplicationContextMenu _listAppContextMenu; - [ObservableProperty] private ApplicationContextMenu _gridAppContextMenu; - [ObservableProperty] private bool _isRyuLdnEnabled; - [ObservableProperty] private bool _updateAvailable; +#pragma warning restore MVVMTK0042 + + [ObservableProperty] public partial Brush VSyncModeColor { get; set; } +#nullable enable + [ObservableProperty] public partial byte[]? SelectedIcon { get; set; } +#nullable disable + [ObservableProperty] public partial int StatusBarProgressMaximum { get; set; } + + [ObservableProperty] public partial int StatusBarProgressValue { get; set; } + + [ObservableProperty] public partial string StatusBarProgressStatusText { get; set; } + + [ObservableProperty] public partial bool StatusBarProgressStatusVisible { get; set; } + + [ObservableProperty] public partial bool IsPaused { get; set; } + + [ObservableProperty] public partial bool IsLoadingIndeterminate { get; set; } = true; + + [ObservableProperty] public partial bool ShowAll { get; set; } + + [ObservableProperty] public partial string LastScannedAmiiboId { get; set; } + + [ObservableProperty] + public partial long LastFullscreenToggle { get; set; } = Environment.TickCount64; + [ObservableProperty] public partial bool ShowContent { get; set; } = true; + + [ObservableProperty] public partial float VolumeBeforeMute { get; set; } + + [ObservableProperty] + public partial bool AreMimeTypesRegistered { get; set; } = FileAssociationHelper.AreMimeTypesRegistered; + + [ObservableProperty] public partial Cursor Cursor { get; set; } + + [ObservableProperty] public partial string Title { get; set; } + + [ObservableProperty] public partial WindowState WindowState { get; set; } + + [ObservableProperty] public partial double WindowWidth { get; set; } + + [ObservableProperty] public partial double WindowHeight { get; set; } + + [ObservableProperty] public partial bool IsActive { get; set; } + + [ObservableProperty] public partial bool IsSubMenuOpen { get; set; } + + [ObservableProperty] public partial ApplicationContextMenu ListAppContextMenu { get; set; } + + [ObservableProperty] public partial ApplicationContextMenu GridAppContextMenu { get; set; } + + [ObservableProperty] public partial bool IsRyuLdnEnabled { get; set; } + + [ObservableProperty] public partial bool UpdateAvailable { get; set; } public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () => { @@ -120,27 +166,17 @@ namespace Ryujinx.Ava.UI.ViewModels await Updater.BeginUpdateAsync(true); }); - private bool _showTotalTimePlayed; - private bool _showLoadProgress; private bool _isGameRunning; - private bool _isAmiiboRequested; - private bool _isAmiiboBinRequested; private string _searchText; private Timer _searchTimer; - private string _vSyncModeText; private string _showUiKey = "F4"; private string _pauseKey = "F5"; private string _screenshotKey = "F8"; private float _volume; - private bool _isAppletMenuActive; - private bool _statusBarVisible; - private bool _canUpdate = true; private ApplicationData _currentApplicationData; private readonly AutoResetEvent _rendererWaitEvent; private int _customVSyncInterval; private int _customVSyncIntervalPercentageProxy; - private ApplicationData _listSelectedApplication; - private ApplicationData _gridSelectedApplication; // Key is Title ID /// @@ -267,20 +303,20 @@ namespace Ryujinx.Ava.UI.ViewModels public bool CanUpdate { - get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(); + get => field && EnableNonGameRunningControls && Updater.CanUpdate(); set { - _canUpdate = value; + field = value; OnPropertyChanged(); } - } + } = true; public bool StatusBarVisible { - get => _statusBarVisible && EnableNonGameRunningControls; + get => field && EnableNonGameRunningControls; set { - _statusBarVisible = value; + field = value; OnPropertyChanged(); } @@ -312,20 +348,21 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsAmiiboRequested { - get => _isAmiiboRequested && _isGameRunning; + get => field && _isGameRunning; set { - _isAmiiboRequested = value; + field = value; OnPropertyChanged(); } } + public bool IsAmiiboBinRequested { - get => _isAmiiboBinRequested && _isGameRunning; + get => field && _isGameRunning; set { - _isAmiiboBinRequested = value; + field = value; OnPropertyChanged(); } @@ -335,10 +372,10 @@ namespace Ryujinx.Ava.UI.ViewModels public bool ShowLoadProgress { - get => _showLoadProgress; + get; set { - _showLoadProgress = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowFirmwareStatus)); @@ -360,24 +397,24 @@ namespace Ryujinx.Ava.UI.ViewModels public bool ShowTotalTimePlayed { - get => _showTotalTimePlayed && EnableNonGameRunningControls; + get => field && EnableNonGameRunningControls; set { - _showTotalTimePlayed = value; + field = value; OnPropertyChanged(); } } public ApplicationData ListSelectedApplication { - get => _listSelectedApplication; + get; set { - _listSelectedApplication = value; + field = value; - if (_listSelectedApplication != null && ListAppContextMenu == null) + if (field != null && ListAppContextMenu == null) ListAppContextMenu = new ApplicationContextMenu(); - else if (_listSelectedApplication == null && ListAppContextMenu != null) + else if (field == null && ListAppContextMenu != null) ListAppContextMenu = null!; OnPropertyChanged(); @@ -386,14 +423,14 @@ namespace Ryujinx.Ava.UI.ViewModels public ApplicationData GridSelectedApplication { - get => _gridSelectedApplication; + get; set { - _gridSelectedApplication = value; + field = value; - if (_gridSelectedApplication != null && GridAppContextMenu == null) + if (field != null && GridAppContextMenu == null) GridAppContextMenu = new ApplicationContextMenu(); - else if (_gridSelectedApplication == null && GridAppContextMenu != null) + else if (field == null && GridAppContextMenu != null) GridAppContextMenu = null!; OnPropertyChanged(); @@ -422,13 +459,18 @@ namespace Ryujinx.Ava.UI.ViewModels public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id); - public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; + public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && + SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; - public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; + public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder && + SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; - public bool TrimXCIEnabled => XCIFileTrimmer.CanTrim(SelectedApplication.Path, new XCITrimmerLog.MainWindow(this)); + public bool TrimXCIEnabled => + XCIFileTrimmer.CanTrim(SelectedApplication.Path, new XCITrimmerLog.MainWindow(this)); - public bool OpenBcatSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + public bool OpenBcatSaveDirectoryEnabled => SelectedApplication.HasControlHolder && + SelectedApplication.ControlHolder.Value + .BcatDeliveryCacheStorageSize > 0; public bool ShowCustomVSyncIntervalPicker => _isGameRunning && AppHost.Device.VSyncMode == VSyncMode.Custom; @@ -466,7 +508,6 @@ namespace Ryujinx.Ava.UI.ViewModels } set { - } } @@ -492,10 +533,10 @@ namespace Ryujinx.Ava.UI.ViewModels public string VSyncModeText { - get => _vSyncModeText; + get; set { - _vSyncModeText = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker)); @@ -524,10 +565,10 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsAppletMenuActive { - get => _isAppletMenuActive && EnableNonGameRunningControls; + get => field && EnableNonGameRunningControls; set { - _isAppletMenuActive = value; + field = value; OnPropertyChanged(); } @@ -799,7 +840,8 @@ namespace Ryujinx.Ava.UI.ViewModels #region PrivateMethods - private static SortExpressionComparer CreateComparer(bool ascending, Func selector) => + private static SortExpressionComparer CreateComparer(bool ascending, + Func selector) => ascending ? SortExpressionComparer.Ascending(selector) : SortExpressionComparer.Descending(selector); @@ -808,15 +850,15 @@ namespace Ryujinx.Ava.UI.ViewModels => SortMode switch { #pragma warning disable IDE0055 // Disable formatting - ApplicationSort.Title => CreateComparer(IsAscending, app => app.Name), - ApplicationSort.Developer => CreateComparer(IsAscending, app => app.Developer), - ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), + ApplicationSort.Title => CreateComparer(IsAscending, app => app.Name), + ApplicationSort.Developer => CreateComparer(IsAscending, app => app.Developer), + ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), - ApplicationSort.FileType => CreateComparer(IsAscending, app => app.FileExtension), - ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize), - ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path), - ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)), - ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id), + ApplicationSort.FileType => CreateComparer(IsAscending, app => app.FileExtension), + ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize), + ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path), + ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)), + ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id), _ => null, #pragma warning restore IDE0055 }; @@ -848,7 +890,8 @@ namespace Ryujinx.Ava.UI.ViewModels CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; - return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; + return compareInfo.IndexOf(app.Name, _searchText, + CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; } return false; @@ -862,21 +905,27 @@ namespace Ryujinx.Ava.UI.ViewModels if (firmwareVersion == null) { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); return; } - string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); - string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); + string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); + string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); if (currentVersion != null) { - dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); + dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); } - dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; + dialogMessage += + LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; UserResult result = await ContentDialogHelper.CreateConfirmationDialog( dialogTitle, @@ -885,7 +934,8 @@ namespace Ryujinx.Ava.UI.ViewModels LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); + UpdateWaitWindow waitingDialog = new(dialogTitle, + LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); if (result == UserResult.Yes) { @@ -906,7 +956,9 @@ namespace Ryujinx.Ava.UI.ViewModels { waitingDialog.Close(); - string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); + string message = LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, + firmwareVersion.VersionString); await ContentDialogHelper.CreateInfoDialog( dialogTitle, @@ -919,7 +971,8 @@ namespace Ryujinx.Ava.UI.ViewModels // Purge Applet Cache. - DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, + "0100000000001009", "cache")); if (miiEditorCacheFolder.Exists) { @@ -940,10 +993,7 @@ namespace Ryujinx.Ava.UI.ViewModels { RefreshFirmwareStatus(); } - }) - { - Name = "GUI.FirmwareInstallerThread", - }; + }) { Name = "GUI.FirmwareInstallerThread", }; thread.Start(); } @@ -968,17 +1018,22 @@ namespace Ryujinx.Ava.UI.ViewModels try { string systemDirectory = AppDataManager.KeysDirPath; - if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && Directory.Exists(AppDataManager.KeysDirPathUser)) + if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && + Directory.Exists(AppDataManager.KeysDirPathUser)) { systemDirectory = AppDataManager.KeysDirPathUser; } - string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle); - string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); - + string dialogTitle = + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle); + string dialogMessage = + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); + if (ContentManager.AreKeysAlredyPresent(systemDirectory)) { - dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage); + dialogMessage += + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys + .DialogKeysInstallerKeysInstallSubMessage); } dialogMessage += LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallConfirmMessage]; @@ -990,7 +1045,8 @@ namespace Ryujinx.Ava.UI.ViewModels LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]); + UpdateWaitWindow waitingDialog = new(dialogTitle, + LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]); if (result == UserResult.Yes) { @@ -1011,7 +1067,9 @@ namespace Ryujinx.Ava.UI.ViewModels { waitingDialog.Close(); - string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSuccessMessage); + string message = + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys + .DialogKeysInstallerKeysInstallSuccessMessage); await ContentDialogHelper.CreateInfoDialog( dialogTitle, @@ -1032,7 +1090,8 @@ namespace Ryujinx.Ava.UI.ViewModels string message = ex.Message; if (ex is FormatException) { - message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename); + message = LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename); } await ContentDialogHelper.CreateErrorDialog(message); @@ -1042,10 +1101,7 @@ namespace Ryujinx.Ava.UI.ViewModels { VirtualFileSystem.ReloadKeySet(); } - }) - { - Name = "GUI.KeysInstallerThread", - }; + }) { Name = "GUI.KeysInstallerThread", }; thread.Start(); } @@ -1064,6 +1120,7 @@ namespace Ryujinx.Ava.UI.ViewModels await ContentDialogHelper.CreateErrorDialog(ex.Message); } } + private void ProgressHandler(T state, int current, int total) where T : Enum { Dispatcher.UIThread.Post(() => @@ -1083,7 +1140,8 @@ namespace Ryujinx.Ava.UI.ViewModels IsLoadingIndeterminate = false; break; case LoadState.Loaded: - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, + _currentApplicationData.Name); IsLoadingIndeterminate = true; CacheLoadStatus = string.Empty; break; @@ -1104,7 +1162,8 @@ namespace Ryujinx.Ava.UI.ViewModels IsLoadingIndeterminate = false; break; case ShaderCacheLoadingState.Loaded: - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, + _currentApplicationData.Name); IsLoadingIndeterminate = true; CacheLoadStatus = string.Empty; break; @@ -1204,12 +1263,12 @@ namespace Ryujinx.Ava.UI.ViewModels _rendererWaitEvent.Set(); } - private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle) + private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, + LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle) { - Optional> result = await StorageProvider.OpenMultiFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[dirSelectDialogTitle] - }); + Optional> result = + await StorageProvider.OpenMultiFolderPickerAsync( + new FolderPickerOpenOptions { Title = LocaleManager.Instance[dirSelectDialogTitle] }); if (result.TryGet(out IReadOnlyList foldersToLoad)) { @@ -1224,7 +1283,8 @@ namespace Ryujinx.Ava.UI.ViewModels await Dispatcher.UIThread.InvokeAsync(async () => { await ContentDialogHelper.ShowTextDialog( - LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo], + LocaleManager.Instance[ + numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo], msg, string.Empty, string.Empty, @@ -1253,17 +1313,21 @@ namespace Ryujinx.Ava.UI.ViewModels public void LoadConfigurableHotKeys() { - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out Avalonia.Input.Key showUiKey)) + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, + out Avalonia.Input.Key showUiKey)) { ShowUiKey = new KeyGesture(showUiKey); } - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out Avalonia.Input.Key screenshotKey)) + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey( + (Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, + out Avalonia.Input.Key screenshotKey)) { ScreenshotKey = new KeyGesture(screenshotKey); } - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out Avalonia.Input.Key pauseKey)) + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, + out Avalonia.Input.Key pauseKey)) { PauseKey = new KeyGesture(pauseKey); } @@ -1283,7 +1347,8 @@ namespace Ryujinx.Ava.UI.ViewModels public void SetGridMode() => Glyph = Glyph.Grid; - public void SetAspectRatio(AspectRatio aspectRatio) => ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; + public void SetAspectRatio(AspectRatio aspectRatio) => + ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; public async Task InstallFirmwareFromFile() { @@ -1375,7 +1440,8 @@ namespace Ryujinx.Ava.UI.ViewModels } catch (Exception ex) { - Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot"); + Logger.Error?.Print(LogClass.Application, + $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot"); return; } @@ -1448,7 +1514,8 @@ namespace Ryujinx.Ava.UI.ViewModels public async Task ManageProfiles() { - await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); + await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, + LibHacHorizonManager.RyujinxClient); } public void SimulateWakeUpMessage() @@ -1525,7 +1592,8 @@ namespace Ryujinx.Ava.UI.ViewModels } else { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]); } } } @@ -1550,17 +1618,17 @@ namespace Ryujinx.Ava.UI.ViewModels public async Task OpenFolder() { - Optional result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle] - }); + Optional result = await StorageProvider.OpenSingleFolderPickerAsync( + new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle] + }); if (result.TryGet(out IStorageFolder value)) { ApplicationData applicationData = new() { - Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath), - Path = value.Path.LocalPath, + Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath), Path = value.Path.LocalPath, }; await LoadApplication(applicationData); @@ -1570,29 +1638,34 @@ namespace Ryujinx.Ava.UI.ViewModels public static bool InitializeUserConfig(ApplicationData application) { // Code where conditions will be met before loading the user configuration (Global Config) - string backendThreadingInit = Program.BackendThreadingArg ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString(); + string backendThreadingInit = Program.BackendThreadingArg ?? + ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString(); // If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting. string idGame = application.IdBaseString; - if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat)) + if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), + out ConfigurationFileFormat configurationFileFormat)) { // Loads the user configuration, having previously changed the global configuration to the user configuration - ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true), idGame); + ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true), + idGame); - if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra)) + if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, + out ConfigurationFileFormat configurationFileFormatExtra)) { //This is where the global configuration will be stored. //This allows you to change the global configuration settings during the game (for example, the global input setting) - ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath); + ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, + Program.GlobalConfigurationPath); } } // Code where conditions will be executed after loading user configuration if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit) { - Rebooter.RebootAppWithGame(application.Path, + Rebooter.RebootAppWithGame(application.Path, [ - "--bt", + "--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() ]); @@ -1602,7 +1675,8 @@ namespace Ryujinx.Ava.UI.ViewModels return false; } - public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct? customNacpData = null) + public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, + BlitStruct? customNacpData = null) { if (InitializeUserConfig(application)) return; @@ -1626,7 +1700,8 @@ namespace Ryujinx.Ava.UI.ViewModels Logger.RestartTime(); - SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); + SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, + ConfigurationState.Instance.System.Language, application.Id); PrepareLoadScreen(); @@ -1664,7 +1739,6 @@ namespace Ryujinx.Ava.UI.ViewModels Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; gameThread.Start(); - } public void SwitchToRenderer(bool startFullscreen) => @@ -1696,7 +1770,8 @@ namespace Ryujinx.Ava.UI.ViewModels if (version != null) { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, + version.VersionString); hasApplet = version.Major > 3; } @@ -1749,7 +1824,7 @@ namespace Ryujinx.Ava.UI.ViewModels if (AppHost.Device.System.SearchingForAmiibo(out int deviceId) && IsGameRunning) { string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); - AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId); + AmiiboWindow window = new(ShowAll, LastScannedAmiiboId ?? string.Empty, titleId); await StyleableAppWindow.ShowAsync(window); @@ -1762,22 +1837,24 @@ namespace Ryujinx.Ava.UI.ViewModels } } } + public async Task OpenBinFile() { if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning) { - Optional result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], - FileTypeFilter = new List + Optional result = await StorageProvider.OpenSingleFilePickerAsync( + new FilePickerOpenOptions { - new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], + FileTypeFilter = new List { - Patterns = ["*.bin"], + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = ["*.bin"], + } } - } - }); - + }); + if (result.HasValue) { AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath); @@ -1828,9 +1905,11 @@ namespace Ryujinx.Ava.UI.ViewModels if (ConfigurationState.Instance.Logger.EnableTrace.Value) { string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; - string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; + string secondaryMessage = + LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; - UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); + UserResult result = + await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); if (result == UserResult.Yes) { @@ -1843,9 +1922,11 @@ namespace Ryujinx.Ava.UI.ViewModels if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) { string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; - string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; + string secondaryMessage = + LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; - UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); + UserResult result = + await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); if (result == UserResult.Yes) { @@ -1900,7 +1981,7 @@ namespace Ryujinx.Ava.UI.ViewModels public async void ProcessTrimResult(String filename, XCIFileTrimmer.OperationOutcome operationOutcome) { - string notifyUser = operationOutcome.ToLocalisedText(); + string notifyUser = operationOutcome.LocalizedText; if (notifyUser != null) { @@ -1934,7 +2015,8 @@ namespace Ryujinx.Ava.UI.ViewModels double savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0; double currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0; double cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0; - string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings); + string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings); UserResult result = await ContentDialogHelper.CreateConfirmationDialog( LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText], @@ -1950,7 +2032,9 @@ namespace Ryujinx.Ava.UI.ViewModels { Dispatcher.UIThread.Post(() => { - StatusBarProgressStatusText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, Path.GetFileName(filename)); + StatusBarProgressStatusText = + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, + Path.GetFileName(filename)); StatusBarProgressStatusVisible = true; StatusBarProgressMaximum = 1; StatusBarProgressValue = 0; @@ -1975,11 +2059,7 @@ namespace Ryujinx.Ava.UI.ViewModels StatusBarVisible = false; }); } - }) - { - Name = "GUI.XCIFileTrimmerThread", - IsBackground = true, - }; + }) { Name = "GUI.XCIFileTrimmerThread", IsBackground = true, }; XCIFileTrimThread.Start(); } } @@ -2041,7 +2121,8 @@ namespace Ryujinx.Ava.UI.ViewModels public static RelayCommand OpenUserSaveDirectory { get; } = Commands.CreateConditional(vm => vm?.SelectedApplication != null, viewModel => - OpenSaveDirectory(viewModel, SaveDataType.Account, viewModel.AccountManager.LastOpenedUser.UserId.ToLibHac()) + OpenSaveDirectory(viewModel, SaveDataType.Account, + viewModel.AccountManager.LastOpenedUser.UserId.ToLibHac()) ); public static RelayCommand OpenDeviceSaveDirectory { get; } = @@ -2097,7 +2178,8 @@ namespace Ryujinx.Ava.UI.ViewModels viewModel => { string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); + string titleModsPath = + ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); OpenHelper.OpenFolder(titleModsPath); }); @@ -2107,7 +2189,8 @@ namespace Ryujinx.Ava.UI.ViewModels viewModel => { string sdModsBasePath = ModLoader.GetSdModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); + string titleModsPath = + ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); OpenHelper.OpenFolder(titleModsPath); }); diff --git a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs index abfe5a4c5..45e67add0 100644 --- a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs @@ -24,9 +24,11 @@ namespace Ryujinx.Ava.UI.ViewModels { private readonly string _modJsonPath; - private AvaloniaList _mods = []; - [ObservableProperty] private AvaloniaList _views = []; - [ObservableProperty] private AvaloniaList _selectedMods = []; + [ObservableProperty] + public partial AvaloniaList Views { get; set; } = []; + + [ObservableProperty] + public partial AvaloniaList SelectedMods { get; set; } = []; private string _search; private readonly ulong _applicationId; @@ -37,15 +39,15 @@ namespace Ryujinx.Ava.UI.ViewModels public AvaloniaList Mods { - get => _mods; + get; set { - _mods = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ModCount)); Sort(); } - } + } = []; public string Search { @@ -58,10 +60,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public string ModCount - { - get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); - } + public string ModCount => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); public ModManagerViewModel(ulong applicationId, ulong applicationIdBase, ApplicationLibrary appLibrary) { diff --git a/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs b/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs index 979e1616a..0aa326b55 100644 --- a/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs @@ -7,8 +7,10 @@ namespace Ryujinx.Ava.UI.ViewModels public partial class ProfileSelectorDialogViewModel : BaseModel { - [ObservableProperty] private UserId _selectedUserId; + [ObservableProperty] + public partial UserId SelectedUserId { get; set; } - [ObservableProperty] private ObservableCollection _profiles = []; + [ObservableProperty] + public partial ObservableCollection Profiles { get; set; } = []; } } diff --git a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs index 230887e34..65e91f41f 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs @@ -15,9 +15,13 @@ namespace Ryujinx.Ava.UI.ViewModels _baseViewModel = settingsVm; } - [ObservableProperty] private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; - [ObservableProperty] private bool _nifmDisableIsAnyInternetRequestAccepted = ConfigurationState.Instance.Hacks.DisableNifmIsAnyInternetRequestAccepted; + [ObservableProperty] + public partial bool Xc2MenuSoftlockFix { get; set; } = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; + [ObservableProperty] + public partial bool NifmDisableIsAnyInternetRequestAccepted { get; set; } = ConfigurationState.Instance.Hacks.DisableNifmIsAnyInternetRequestAccepted; + + public static string Xc2MenuFixTooltip { get; } = Lambda.String(sb => { sb.AppendLine( diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index acf7517d8..d5d9b8218 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -6,7 +6,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Systems.Configuration; @@ -19,7 +19,6 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Helper; -using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE; @@ -46,47 +45,54 @@ namespace Ryujinx.Ava.UI.ViewModels private readonly Dictionary _networkInterfaces; - private float _customResolutionScale; private int _resolutionScale; - private int _graphicsBackendMultithreadingIndex; - private float _volume; - [ObservableProperty] private bool _isVulkanAvailable = true; - [ObservableProperty] private bool _gameListNeedsRefresh; + [ObservableProperty] + public partial bool IsVulkanAvailable { get; set; } = true; + + [ObservableProperty] + public partial bool GameListNeedsRefresh { get; set; } + private readonly List _gpuIds = []; - public bool _useInputGlobalConfig; - private int _graphicsBackendIndex; private int _scalingFilter; - private int _scalingFilterLevel; private int _customVSyncInterval; - private bool _enableCustomVSyncInterval; private int _customVSyncIntervalPercentageProxy; private VSyncMode _vSyncMode; - private long _turboModeMultiplier; public event Action CloseWindow; public event Action SaveSettingsEvent; public event Action LocalGlobalInputSwitchEvent; - private int _networkInterfaceIndex; - private int _multiplayerModeIndex; - private string _ldnPassphrase; - [ObservableProperty] private string _ldnServer; - - private bool _enableGDBStub; - private ushort _gdbStubPort; - private bool _debuggerSuspendOnStart; - public SettingsHacksViewModel DirtyHacks { get; } - private readonly bool _isGameRunning; - private readonly Bitmap _gameIcon; - private readonly string _gameTitle; - private readonly string _gamePath; - private readonly string _gameId; - public bool IsGameRunning => _isGameRunning; - public Bitmap GameIcon => _gameIcon; - public string GamePath => _gamePath; - public string GameTitle => _gameTitle; - public string GameId => _gameId; + public bool IsGameRunning + { + get; + } + + public Bitmap GameIcon + { + get; + } + + public string GamePath + { + get; + } + + public string GameTitle + { + get; + } + + public string GameId + { + get; + } + + public bool IsCustomConfig + { + get; + } + public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle); public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1; @@ -104,17 +110,18 @@ namespace Ryujinx.Ava.UI.ViewModels public int GraphicsBackendMultithreadingIndex { - get => _graphicsBackendMultithreadingIndex; + get; set { - _graphicsBackendMultithreadingIndex = value; + field = value; - if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) + if (field != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) { Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], - string.Empty, - string.Empty, + ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], + string.Empty, + string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) ); @@ -126,10 +133,10 @@ namespace Ryujinx.Ava.UI.ViewModels public float CustomResolutionScale { - get => _customResolutionScale; + get; set { - _customResolutionScale = MathF.Round(value, 1); + field = MathF.Round(value, 1); OnPropertyChanged(); } @@ -152,12 +159,12 @@ namespace Ryujinx.Ava.UI.ViewModels public int FocusLostActionType { get; set; } public bool UseGlobalInputConfig - { - get => _useInputGlobalConfig; + { + get; set { - _useInputGlobalConfig = value; - LocalGlobalInputSwitchEvent?.Invoke(_useInputGlobalConfig); + field = value; + LocalGlobalInputSwitchEvent?.Invoke(field); OnPropertyChanged(nameof(InputPanelOpacity)); OnPropertyChanged(); } @@ -196,10 +203,10 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableCustomVSyncInterval { - get => _enableCustomVSyncInterval; + get; set { - _enableCustomVSyncInterval = value; + field = value; if (_vSyncMode == VSyncMode.Custom && !value) { VSyncMode = VSyncMode.Switch; @@ -233,12 +240,12 @@ namespace Ryujinx.Ava.UI.ViewModels public long TurboMultiplier { - get => _turboModeMultiplier; + get; set { - if (_turboModeMultiplier != value) + if (field != value) { - _turboModeMultiplier = value; + field = value; OnPropertyChanged(); OnPropertyChanged((nameof(TurboMultiplierPercentageText))); @@ -269,7 +276,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableDebug { get; set; } public bool IsOpenAlEnabled { get; set; } public bool IsSoundIoEnabled { get; set; } - public bool IsSDL2Enabled { get; set; } + public bool IsSDL3Enabled { get; set; } public bool IsCustomResolutionScaleActive => _resolutionScale == 4; public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; @@ -285,10 +292,10 @@ namespace Ryujinx.Ava.UI.ViewModels public string LdnPassphrase { - get => _ldnPassphrase; + get; set { - _ldnPassphrase = value; + field = value; IsInvalidLdnPassphraseVisible = !ValidateLdnPassphrase(value); OnPropertyChanged(); @@ -304,29 +311,33 @@ namespace Ryujinx.Ava.UI.ViewModels public int AspectRatio { get; set; } public int AntiAliasingEffect { get; set; } public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0"); + public int ScalingFilterLevel { - get => _scalingFilterLevel; + get; set { - _scalingFilterLevel = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ScalingFilterLevelText)); } } + public int OpenglDebugLevel { get; set; } public int MemoryMode { get; set; } public int BaseStyleIndex { get; set; } + public int GraphicsBackendIndex { - get => _graphicsBackendIndex; + get; set { - _graphicsBackendIndex = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsVulkanSelected)); } } + public int ScalingFilter { get => _scalingFilter; @@ -342,19 +353,20 @@ namespace Ryujinx.Ava.UI.ViewModels public float Volume { - get => _volume; + get; set { - _volume = value; + field = value; - ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100; + ConfigurationState.Instance.System.AudioVolume.Value = field / 100; OnPropertyChanged(); } } - [ObservableProperty] private bool _matchSystemTime; - + [ObservableProperty] + public partial bool MatchSystemTime { get; set; } + public DateTimeOffset CurrentDate { get; set; } public TimeSpan CurrentTime { get; set; } @@ -373,19 +385,19 @@ namespace Ryujinx.Ava.UI.ViewModels public int NetworkInterfaceIndex { - get => _networkInterfaceIndex; + get; set { - _networkInterfaceIndex = value != -1 ? value : 0; + field = value != -1 ? value : 0; } } public int MultiplayerModeIndex { - get => _multiplayerModeIndex; + get; set { - _multiplayerModeIndex = value; + field = value; } } @@ -393,31 +405,31 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableGdbStub { - get => _enableGDBStub; + get; set { - _enableGDBStub = value; - ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub; + field = value; + ConfigurationState.Instance.Debug.EnableGdbStub.Value = field; } } public ushort GDBStubPort { - get => _gdbStubPort; + get; set { - _gdbStubPort = value; - ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort; + field = value; + ConfigurationState.Instance.Debug.GdbStubPort.Value = field; } } public bool DebuggerSuspendOnStart { - get => _debuggerSuspendOnStart; + get; set { - _debuggerSuspendOnStart = value; - ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart; + field = value; + ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = field; } } @@ -450,13 +462,13 @@ namespace Ryujinx.Ava.UI.ViewModels if (gameIconData is { Length: > 0 }) { using MemoryStream ms = new(gameIconData); - _gameIcon = new Bitmap(ms); + GameIcon = new Bitmap(ms); } - - _isGameRunning = gameRunning; - _gamePath = gamePath; - _gameTitle = gameName; - _gameId = gameId; + IsCustomConfig = customConfig; + IsGameRunning = gameRunning; + GamePath = gamePath; + GameTitle = gameName; + GameId = gameId; if (customConfig) // During the game. If there is no user config, then load the global config window { @@ -481,7 +493,6 @@ namespace Ryujinx.Ava.UI.ViewModels if (Program.PreviewerDetached) { Task.Run(LoadTimeZones); - } } @@ -512,13 +523,13 @@ namespace Ryujinx.Ava.UI.ViewModels { IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; - IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; + IsSDL3Enabled = SDL3HardwareDeviceDriver.IsSupported; await Dispatcher.UIThread.InvokeAsync(() => { OnPropertyChanged(nameof(IsOpenAlEnabled)); OnPropertyChanged(nameof(IsSoundIoEnabled)); - OnPropertyChanged(nameof(IsSDL2Enabled)); + OnPropertyChanged(nameof(IsSDL3Enabled)); }); } @@ -718,7 +729,6 @@ namespace Ryujinx.Ava.UI.ViewModels MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; DisableP2P = config.Multiplayer.DisableP2p; LdnPassphrase = config.Multiplayer.LdnPassphrase; - LdnServer = config.Multiplayer.LdnServer; // Debug EnableGdbStub = config.Debug.EnableGdbStub.Value; @@ -817,8 +827,6 @@ namespace Ryujinx.Ava.UI.ViewModels if (audioBackend != config.System.AudioBackend.Value) { config.System.AudioBackend.Value = audioBackend; - - Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}"); } config.System.AudioVolume.Value = Volume / 100; @@ -845,7 +853,6 @@ namespace Ryujinx.Ava.UI.ViewModels config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; config.Multiplayer.DisableP2p.Value = DisableP2P; config.Multiplayer.LdnPassphrase.Value = LdnPassphrase; - config.Multiplayer.LdnServer.Value = LdnServer; // Debug config.Debug.EnableGdbStub.Value = EnableGdbStub; @@ -867,16 +874,11 @@ namespace Ryujinx.Ava.UI.ViewModels GameListNeedsRefresh = false; } - private static void RevertIfNotSaved() + private static void RevertIfNotSaved(bool isCustomConfig = false, bool isGameRunning = false) { - /* - maybe this is an unnecessary check(all options need to be tested) - if (string.IsNullOrEmpty(Program.GlobalConfigurationPath)) - { - Program.ReloadConfig(); - } - */ - Program.ReloadConfig(); + // Restores settings for a custom configuration during a game, if the condition is met. + // If the condition is not met (parameter is false), restores global (default) configuration instead. + Program.ReloadConfig(isCustomConfig && isGameRunning); } public void ApplyButton() @@ -893,14 +895,14 @@ namespace Ryujinx.Ava.UI.ViewModels File.Delete(gameDir); } - RevertIfNotSaved(); + RevertIfNotSaved(IsCustomConfig, IsGameRunning); CloseWindow?.Invoke(); } public void SaveUserConfig() { SaveSettings(); - RevertIfNotSaved(); // Revert global configuration after saving user configuration + RevertIfNotSaved(IsCustomConfig, IsGameRunning); // Revert global or custom configuration after saving user configuration CloseWindow?.Invoke(); } @@ -932,7 +934,7 @@ namespace Ryujinx.Ava.UI.ViewModels public void CancelButton() { - RevertIfNotSaved(); + RevertIfNotSaved(IsCustomConfig, IsGameRunning); CloseWindow?.Invoke(); } } diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs index 74876774e..3d34643ab 100644 --- a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs @@ -21,10 +21,17 @@ namespace Ryujinx.Ava.UI.ViewModels private ApplicationLibrary ApplicationLibrary { get; } private ApplicationData ApplicationData { get; } - [ObservableProperty] private AvaloniaList _titleUpdates = []; - [ObservableProperty] private AvaloniaList _views = []; - [ObservableProperty] private object _selectedUpdate = new TitleUpdateViewModelNoUpdate(); - [ObservableProperty] private bool _showBundledContentNotice; + [ObservableProperty] + public partial AvaloniaList TitleUpdates { get; set; } = []; + + [ObservableProperty] + public partial AvaloniaList Views { get; set; } = []; + + [ObservableProperty] + public partial object SelectedUpdate { get; set; } = new TitleUpdateViewModelNoUpdate(); + + [ObservableProperty] + public partial bool ShowBundledContentNotice { get; set; } private readonly IStorageProvider _storageProvider; diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs index 8c6a71038..9b1c75b2e 100644 --- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -25,14 +25,15 @@ namespace Ryujinx.Ava.UI.ViewModels { private static readonly Dictionary _avatarStore = new(); - [ObservableProperty] private ObservableCollection _images; - [ObservableProperty] private Color _backgroundColor = Colors.White; + [ObservableProperty] + public partial ObservableCollection Images { get; set; } - private int _selectedIndex; + [ObservableProperty] + public partial Color BackgroundColor { get; set; } = Colors.White; public UserFirmwareAvatarSelectorViewModel() { - _images = []; + Images = []; LoadImagesFromStore(); PropertyChanged += (_, args) => @@ -44,21 +45,17 @@ namespace Ryujinx.Ava.UI.ViewModels public int SelectedIndex { - get => _selectedIndex; + get; set { - _selectedIndex = value; + field = value; - if (_selectedIndex == -1) - { - SelectedImage = null; - } - else - { - SelectedImage = Images[_selectedIndex].Data; - } + SelectedImage = field == -1 + ? null + : Images[field].Data; OnPropertyChanged(); + OnPropertyChanged(nameof(SelectedImage)); } } diff --git a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs index f9f9ca2f5..5a6af229d 100644 --- a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs @@ -4,6 +4,7 @@ namespace Ryujinx.Ava.UI.ViewModels { public partial class UserProfileImageSelectorViewModel : BaseModel { - [ObservableProperty] private bool _firmwareFound; + [ObservableProperty] + public partial bool FirmwareFound { get; set; } } } diff --git a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs index eb7fa0fef..34c83372a 100644 --- a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs @@ -10,11 +10,21 @@ namespace Ryujinx.Ava.UI.ViewModels { public partial class UserSaveManagerViewModel : BaseModel { - [ObservableProperty] private int _sortIndex; - [ObservableProperty] private int _orderIndex; - [ObservableProperty] private string _search; - [ObservableProperty] private ObservableCollection _saves = []; - [ObservableProperty] private ObservableCollection _views = []; + [ObservableProperty] + public partial int SortIndex { get; set; } + + [ObservableProperty] + public partial int OrderIndex { get; set; } + + [ObservableProperty] + public partial string Search { get; set; } + + [ObservableProperty] + public partial ObservableCollection Saves { get; set; } = []; + + [ObservableProperty] + public partial ObservableCollection Views { get; set; } = []; + private readonly AccountManager _accountManager; public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); diff --git a/src/Ryujinx/UI/ViewModels/XciTrimmerViewModel.cs b/src/Ryujinx/UI/ViewModels/XciTrimmerViewModel.cs index dab42fbf8..1281cc834 100644 --- a/src/Ryujinx/UI/ViewModels/XciTrimmerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/XciTrimmerViewModel.cs @@ -18,7 +18,8 @@ namespace Ryujinx.Ava.UI.ViewModels { public class XciTrimmerViewModel : BaseModel { - private const long _bytesPerMB = 1024 * 1024; + private const long BytesPerMb = 1024 * 1024; + private enum ProcessingMode { Trimming, @@ -44,7 +45,6 @@ namespace Ryujinx.Ava.UI.ViewModels private string _search; private ProcessingMode _processingMode; private SortField _sortField = SortField.Name; - private bool _sortAscending = true; public XciTrimmerViewModel(MainWindowViewModel mainWindowViewModel) { @@ -472,15 +472,16 @@ namespace Ryujinx.Ava.UI.ViewModels }; } } + public bool SortingAscending { - get => _sortAscending; + get; set { - _sortAscending = value; + field = value; SortingChanged(); } - } + } = true; public bool IsSortedByName { @@ -516,7 +517,7 @@ namespace Ryujinx.Ava.UI.ViewModels { get { - return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / _bytesPerMB)); + return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / BytesPerMb)); } } @@ -524,7 +525,7 @@ namespace Ryujinx.Ava.UI.ViewModels { get { - return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / _bytesPerMB)); + return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / BytesPerMb)); } } diff --git a/src/Ryujinx/UI/Views/Dialog/AboutView.axaml b/src/Ryujinx/UI/Views/Dialog/AboutView.axaml index 5788f533f..5700de6b9 100644 --- a/src/Ryujinx/UI/Views/Dialog/AboutView.axaml +++ b/src/Ryujinx/UI/Views/Dialog/AboutView.axaml @@ -6,6 +6,7 @@ xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:interopServices="clr-namespace:System.Runtime.InteropServices;assembly=System.Runtime" MinWidth="550" MinHeight="260" MaxWidth="600" @@ -70,6 +71,13 @@ LineHeight="12" Text="{Binding Version}" TextAlignment="Center" /> + { + //Fix compiler warning + public LedInputView() + { + + } + public LedInputView(ControllerInputViewModel viewModel) { ViewModel = new LedInputViewModel diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml index b09c6ef6c..22dfc57ac 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml @@ -44,8 +44,8 @@ IsEnabled="{Binding IsSoundIoEnabled}" Content="{ext:Locale SettingsTabSystemAudioBackendSoundIO}" /> + IsEnabled="{Binding IsSDL3Enabled}" + Content="{ext:Locale SettingsTabSystemAudioBackendSDL3}" /> diff --git a/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs index dc9ce56d6..6fe583224 100644 --- a/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs @@ -11,6 +11,12 @@ namespace Ryujinx.Ava.UI.Windows { internal readonly SettingsViewModel ViewModel; + //Fix compiler warning + public GameSpecificSettingsWindow() + { + + } + public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true) { Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString); diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml index 498b77100..684b39ef3 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml @@ -23,8 +23,8 @@ mc:Ignorable="d" WindowStartupLocation="Manual" Focusable="True" - GotFocus="InputElement_OnGotFocus" - LostFocus="InputElement_OnLostFocus"> + GotFocus="AppWindow_OnGotFocus" + LostFocus="AppWindow_OnLostFocus">