143 Commits
Author SHA1 Message Date
KeatonTheBot c3bfce3378 Update Kenji-NX to 2.0.5 2025-10-06 08:57:12 -05:00
KeatonTheBot 88e8d1309c UI: RPC: Asset images
* Final Fantasy Tactics: The Ivalice Chronicles

* Hades II

* Sonic Racing: CrossWorlds

* Super Mario Galaxy 1 & 2
2025-10-03 10:06:02 -05:00
KeatonTheBot ebeb532099 Fix build error: Replace StartsWithIgnoreCase with standard .NET equivalent 2025-10-02 15:18:35 -05:00
KeatonTheBot c501fe264b Fix push descriptors bugfix logic for Intel Arc on Linux 2025-10-01 15:30:02 -05:00
GreemDev 61d0430006 vulkan: Intel Arc on Linux also has the push descriptors bug. 2025-10-01 14:20:02 -05:00
KeatonTheBot cd477cf744 Ryujinx.csproj: Move PublishTrimmed setting for win-arm64 into existing PropertyGroup 2025-10-01 08:49:24 -05:00
KeatonTheBot 0487b781dc Revert NullReferenceException change in IntervalTree.cs
* Not technically needed because newNode.Parent is the same value as parent
2025-09-30 17:53:09 -05:00
KeatonTheBot 51b32981b6 misc: chore: Fix possible NullReferenceExceptions, InvalidOperationExceptions 2025-09-30 15:05:14 -05:00
KeatonTheBot fa682d406e misc: chore: Merge into pattern 2025-09-24 13:51:15 -05:00
KeatonTheBot 503dea74c2 misc: chore: Remove unnecessary usings 2025-09-24 13:48:36 -05:00
KeatonTheBot 1f979e5c6f misc: chore: Fix object creation 2025-09-24 13:44:00 -05:00
KeatonTheBot 378dc33cca misc: chore: Discard unused parameters 2025-09-24 13:26:50 -05:00
KeatonTheBot 9c37a557dd misc: chore: Merge duplicated 'if' branches 2025-09-24 13:26:50 -05:00
Mcost45 6e5bd0c9f2 Include SL/SR default bindings for single joycons
Single L/R Joycons default to unbound for the SL/SR inputs - so by default you can't progress past 'press L + R to continue' type screens.
But

* ConfigGamepadInputId.SingleLeftTrigger0(L)

* ConfigGamepadInputId.SingleRightTrigger0(L)

* ConfigGamepadInputId.SingleLeftTrigger1(R)

* ConfigGamepadInputId.SingleRightTrigger1(R)

already exist (and I verified these are the inputs triggered by the SL/SR buttons), so my change would default to these instead.
2025-09-23 09:27:59 -05:00
Alula 0f2b11d4d5 feat: resolve real module names in HLE debugger 2025-09-20 11:32:15 -05:00
KeatonTheBot 1abf5b82c5 Fix: Configuration migration for Turbo Mode 2025-09-19 16:28:59 -05:00
KeatonTheBot d751cfa81a UI: Update Svg.Controls.Avalonia group to 11.3.6.2 2025-09-19 08:20:24 -05:00
KeatonTheBot af14fadb2f UI: Update Avalonia to 11.3.6 2025-09-17 09:15:01 -05:00
KeatonTheBot 4fd4e3a4ff Increase # of maximum log files from 4 to 5 2025-09-15 16:21:11 -05:00
KeatonTheBot a1834900be Revert stream loader changes
"add stream based loaders" - Commit c16559f2

"Fix some DLCs crashing due to stream loader change" - Commit a62deb8c
2025-09-14 14:51:46 -05:00
KeatonTheBot fe7a30c747 Revert Android changes
* "Android: NCE support" - Commit cd3221ab

* "Android: Numerous fixes" - Commit 19dd23c2

* "Android: Memory specific switches" - Commit 98b4ff33

* "Android: Remove unmanaged code" - Commit 846b5b6e
2025-09-14 14:51:44 -05:00
KeatonTheBot 3a387309b4 Memory changes part 2 crash fix
* This was an oversight: Two lines were left out when initially cherry-picking from Ryubing
2025-09-12 17:58:00 -05:00
KeatonTheBot 867a92a8bc nuget: bump System group to 9.0.9 2025-09-10 15:41:20 -05:00
Coxxs 45bf443c01 Fix headless mode
Fix the error

`Error setting value to option 'gdb-stub-port': Check if Option or Value attribute values are set properly for the given type.`

https://github.com/commandlineparser/commandline/issues/612
2025-09-10 13:14:07 -05:00
LotP a09568d1b7 Memory changes 2.2.1
Cleans up some leftover comments i forgot to remove.

Potentially fixes 1 more crash.
2025-09-07 13:11:57 -05:00
LotP dae7ae7eaf Memory changes 2.2
A few more internal changes to the RangeList systems.

* No longer using a QuickAccess dictionary.

  * The performance of the dictionary wasn't much faster than just doing binary searches.

  * Using just binary searches allows us to take advantage of span and array returns as they're are faster than linked lists when iterating or copying the overlaps.

Small code optimizations.

Fixes a few leftover crashes.
2025-09-06 12:26:08 -05:00
KeatonTheBot c2f54d0a5c UI: RPC: Hollow Knight Silksong asset image 2025-09-05 08:59:38 -05:00
LotP f4dfdd35b2 hle: Basic event handle implementation for IApplicationFunctions 210
Lets Hollow Knight: Silksong boot.

* remove stub

* Add version comment
2025-09-05 08:59:26 -05:00
GreemDevandKeatonTheBot c56d04a9a9 feature: Turbo Mode
Adds an elapsed tick multiplier feature which speeds up games which are built upon delta time.

More information: https://web.archive.org/web/20240713135029/https://github.com/Ryujinx/Ryujinx/pull/6456

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
2025-09-04 23:49:32 -05:00
KeatonTheBot 5c6175cebf UI: Fix Match System Time setting not appropriately disabling/enabling System Time options 2025-09-04 23:49:26 -05:00
KeatonTheBot 9addccb50f misc: chore: Replace Gommon functions with standard .NET equivalents (part 3) 2025-09-04 14:28:54 -05:00
KeatonTheBot 17946c78e6 UI: Update Avalonia to 11.3.5 2025-09-04 12:31:51 -05:00
KeatonTheBot 29647b6351 Update SDL to 2.32.10 2025-09-02 10:33:51 -05:00
KeatonTheBot 6e6983799e Update FFmpeg runtimes to 6.1.3 for Windows/Linux 2025-09-01 12:44:24 -05:00
LotP 61da23cb9e Memory changes 2.1
* Fixes a few crashes

* Simplifies a few functions

* Changes a few calls to use faster methods
2025-08-31 20:42:31 -05:00
KeatonTheBot a62deb8cfd Fix some DLCs crashing due to stream loader change 2025-08-31 20:42:28 -05:00
KeatonTheBot 8ea79ae5d5 UI: Update Svg.Controls.Avalonia group to 11.3.0.4 2025-08-28 16:26:13 -05:00
GreemDev 09f3bf4b55 [ci skip] chore: Change LDN server URL (it's the same server, just a more official URL) 2025-08-28 09:50:29 -05:00
LotP 171624e7f0 Memory Changes part 2
* Slightly refactors RangeLists from the last Memory Changes MR, which fixes issue 61.

* Convert as many const size array iterators to span iterators as possible. When iterating over a const size array, every iteration created a Span, now only the first iteration does in most places.

* Now using object pooling for a few object types that were rapidly deleted and recreated.

* Converted a few flag checks to binary operations to save memory allocations.
2025-08-26 15:30:12 -05:00
LotP 01cb33f658 Memory Changes
* Refactors the RangeList and derivative classes used for handling lists of regions

* The Binary searches are now more performant, relying on edge searches instead of just returning the first matching hit and manually iterating until the edge is found

* Most look-ups now return a RangeItem, which acts as a linked list node now, instead where possible, moving away from Array copies. This should help with some specific lag spikes.

* Made IntrusiveRedBlackTreeNodes act like linked list nodes too to improve the lookup time of minimums, maximums and successors.

* Changed a few cases of HasFlag() into binary operations to save on memory allocations.

In general, [these changes] should increase frame time stability and lag spikes, but at the cost of some overhead to memory look-ups, the result being a very slightly better average fps from my testing (~1-2%).
2025-08-26 15:28:43 -05:00
KeatonTheBot 121bb5dc76 UI: Update Avalonia to 11.3.4
* Fix text and ComboBox alignment in Save Manager
2025-08-21 11:36:23 -05:00
KeatonTheBot d73ef0e3fb nuget: bump packages
* Microsoft.CodeAnalysis.CSharp to 4.12.0

* Microsoft.IdentityModel.JsonWebTokens to 8.14.0

* UnicornEngine.Unicorn to 2.1.3
2025-08-20 16:51:15 -05:00
MaxLastBreathandGreemDev 46b2da2435 Fix Avalonia Native MouseWheel-Support
* Rename timer interval constant

* Also cut the delay after which scrolling is considered ended in half

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2025-08-20 11:51:46 -05:00
gdkchan aca897c70c Protect against stack overflow caused by deep recursive calls
* PPTC version bump

* Also reset call depth when not using the unmanaged dispatch loop

* Increment call depth on function start rather than before call
2025-08-20 11:50:15 -05:00
gdkchan e7d423cd07 Avoid lookup of invalid textures if pool did not change 2025-08-17 00:41:30 -05:00
KeatonTheBot 48c809a80d Revert "Memory Changes"
This reverts commit d5c9bc662c.

Solves crash in Kirby Star Allies after a few mins of gameplay, possibly other titles.
2025-08-17 00:19:01 -05:00
Coxxs 7fd01214cf gdb: Improve stepping 2025-08-11 15:06:46 -05:00
Coxxs ef72e9b790 Add GDB Stub 2025-08-11 15:06:41 -05:00
LotP d5c9bc662c Memory Changes 2025-08-07 11:59:44 -05:00
KeatonTheBot 3cf90e808d nuget: bump System group to 9.0.8 2025-08-06 08:49:45 -05:00
KeatonTheBot 326f744093 nuget: bump DiscordRichPresence to 1.6.1.70 2025-08-05 14:04:33 -05:00
KeatonTheBot 6cecbaa625 UI: Update FluentAvalonia.NoAnim to 2.4.0-build2, revert ColorPicker changes 2025-07-30 21:19:15 -05:00
KeatonTheBot 1443e1a2ff UI: Update Avalonia to 11.3.2, FluentAvalonia to 2.4.0
* FluentAvalonia: Disabled NavigationView selection indicator animations due to bugged implementation in 2.1.0+, restoring previous behavior

* Avalonia: Fixed text on certain buttons being larger than normal

* Avalonia: Fixed ComboBox code inserting extra space to the left of selected items
2025-07-29 18:01:11 -05:00
KeatonTheBot 12d7f95468 nuget: bump DiscordRichPresence to 1.4.1.37, Microsoft.IdentityModel.JsonWebTokens to 8.13.0 2025-07-29 17:48:55 -05:00
KeatonTheBot b59a833467 UI: RPC: Pokémon Friends asset image 2025-07-28 20:10:33 -05:00
GreemDev 50a2caf799 Update Ryujinx.LibHac
This should fix crashes with mods that worked on Ryubing 1.3.1.

Thanks @cyphix!

https://git.ryujinx.app/ryubing/libhac/-/commit/e39169ab5053ff179b7fef38f624dfc608d2596c
2025-07-21 22:43:51 -05:00
KeatonTheBot 7d1fd99d3d nuget: bump System group to 9.0.7 2025-07-12 16:22:42 -05:00
Emmanuel Hansen c16559f20e add stream based loaders
* extend stream loading support

* fix content manager rebase

* fix update searching
2025-06-28 13:41:52 -05:00
Coxxs 6ecd6111d5 fix: UI deadlock when launching a game with "Trace Logs" enabled
This fixes https://github.com/Ryubing/Issues/issues/30

* Switch to "Release" build config (PerformanceCheck(); will only be called in Release build config)

* Enable "Trace Logs" in Ryujinx settings

* Double-click a game to launch

* Ryujinx will attempt to open a confirmation dialog box that never opens, causing UI deadlock
2025-06-20 10:40:29 -05:00
KeatonTheBot 0ee71fa064 misc: chore: Replace Gommon functions with standard .NET equivalents (part 2) 2025-06-20 10:36:56 -05:00
KeatonTheBot 6e8f7e7fcb misc: chore: Replace additional instances of Gommon ForEach with 'foreach' statements 2025-06-18 16:19:17 -05:00
KeatonTheBot f9780b33bb misc: chore: Use 'foreach' statement in place of Gommon ForEach in "delete all" mod manager crash fix
* Simplify statement for valid mod folder checking
2025-06-18 02:41:02 -05:00
KeatonTheBot d2a532f971 misc: chore: Fix numerous NullReferenceExceptions, InvalidOperationExceptions 2025-06-18 02:25:06 -05:00
Coxxs a90db3464d fix: socket blocking flag is inverted when setting it 2025-06-13 17:00:28 -05:00
KeatonTheBot c61d171e52 nuget: bump packages
* DynamicData to 9.4.1

* Microsoft.IdentityModel.JsonWebTokens to 8.12.0
2025-06-12 19:16:04 -05:00
KeatonTheBot 8927a78c4d nuget: bump DiscordRichPresence to 1.3.0.28 2025-06-12 17:05:53 -05:00
KeatonTheBot aa836dd924 nuget: bump System group to 9.0.6 2025-06-11 14:07:59 -05:00
mqudsi 878ea70aa9 Work around Escape hotkey race with exit confirmation dialog 2025-06-11 10:47:53 -05:00
rockingdice 9f0a35ee9b fix: crash caused by cursor overflow
* This fixes a crash that occurred when opening the soft keyboard for the second time
2025-06-10 18:17:48 -05:00
rockingdice ed0ebdec5b fix: use the correct font family for CJK characters 2025-06-10 18:16:56 -05:00
KeatonTheBot b5a1272141 Update SDL2 to 2.32.8 2025-06-09 03:08:12 -05:00
GreemDev 6768e1fa71 infra: Update to Ryujinx.LibHac 0.20.0
This is identical to the previous version, it's just on NuGet.org so we can comment out the LibHacAlpha source in nuget.config.
2025-06-08 23:20:47 -05:00
GreemDev 5c2d710fbb misc: Update LibHac
* _lastFileOffset now correctly stores the offset for the last file in the chain

  * Fixes an issue where a RomFS mod with both overridden and new files in the same folder would load incorrectly

* Renamed a variable with the wrong name

* Now behaves correctly when the same file is added twice in a row compared to V1 MR
2025-06-06 17:06:35 -05:00
LotP d3f3eacc41 fix: use accurate length for enumerating
See merge request ryubing/ryujinx!49
2025-06-04 11:42:21 -05:00
LotP 5983cb9e5f Update LibHac
* Fixes Avalonia timeout

* Cuts RomFS rebuilding times by up to a factor of 1000 in games with big RomFS like TotK
2025-06-01 02:26:43 -05:00
KeatonTheBot e09f16838e Suppress CA1416 warnings, correct argument kind in IFileSystem 2025-06-01 02:26:15 -05:00
KeatonTheBot 18f302c0b0 Update OpenTK to 4.9.4, OpenAL to 1.24.3 2025-06-01 02:26:01 -05:00
KeatonTheBot 8f77076117 Change stick visualizer color to system accent color 2025-06-01 02:25:39 -05:00
KeatonTheBot 39544e323c Update Kenji-NX to 2.0.4 2025-05-29 22:01:40 -05:00
KeatonTheBot 24932b7e1b infra: Remove Ryujinx.Common references from project files that indirectly reference the same file 2025-05-29 11:10:57 -05:00
KeatonTheBot 2aaf34937d infra: Fix missing libarmeilleure-jitsupport.dylib on macOS (arm64) builds 2025-05-29 10:28:00 -05:00
KeatonTheBot bce3426d26 Linux: Fix games not launching (from Bionic code) 2025-05-28 03:37:23 -05:00
MrKev 51ca73cb10 Fix JWT Claims and Socket Flag Handling to Improve Just Dance® Server Connection 2025-05-27 18:28:15 -05:00
KeatonTheBot cd3221abdc Android: NCE support 2025-05-26 16:02:20 -05:00
KeatonTheBot 19dd23c288 Android: Numerous fixes
* GetSerialNumber in ISystemSettingsServer

* Bionic qualifiers

* Revert application pool size to 3285 MB for 4 GB DRAM

* Fix PPTC recompilation if mod or patch is applied
2025-05-26 16:01:23 -05:00
GreemDev 20f1cef9b2 fix: Super Mario Party Jamboree audio renderer crashing 2025-05-26 10:22:13 -05:00
KeatonTheBot 2f400491d5 ffmpeg: Fix green screen issues on Linux 2025-05-25 22:34:06 -05:00
ChromJ f6351e3fff Add Ctrl+Comma shortcut for settings
See merge request kenji-nx/ryujinx!2
2025-05-24 23:47:13 -05:00
KeatonTheBot f15ddcf9c4 UI: Fix GitLab logos not showing on git server in Ryujinx.UI.Common\Resources folder due to capitalization error 2025-05-24 22:23:44 -05:00
Evan Husted 88d904acde Headless in Avalonia v2
Launch the Ryujinx.exe, first argument --no-gui or nogui, and the rest of the arguments should be your normal headless script. You can include the new option --use-main-config which will provide any arguments that you don't, filled in from your main config made by the UI.
2025-05-24 19:32:45 -05:00
KeatonTheBot fbcf57bef5 UI: RPC: Add LUNAR Remastered Collection 2025-05-22 15:29:39 -05:00
KeatonTheBot 8bfb670e65 infra: Update SDL2 to 2.32.6 2025-05-21 15:50:27 -05:00
KeatonTheBot e00bece65a nuget: Remove GtkSharp packages 2025-05-20 02:26:44 -05:00
GreemDev 2a03f7b1d4 infra: Update to Ryujinx.LibHac 0.20.0.
This time it's pulled in via GitLab package registry.
2025-05-20 00:28:49 -05:00
KeatonTheBot 6c7b7d6fc4 UI: Change the GitHub button in the About window to GitLab 2025-05-19 23:03:37 -05:00
KeatonTheBot c43db7d5f6 Update README.md and other documents 2025-05-17 13:35:37 -05:00
KeatonTheBot cc92af12a4 infra: Fix clean installation crash 2025-05-16 17:23:10 -05:00
GreemDev df21f6019e infra: Switch to [Ryujinx.LibHac](https://git.ryujinx.app/ryubing/libhac)
The original repository disappeared a few days ago, and we had a backup.
2025-05-15 17:48:35 -05:00
KeatonTheBot 232dc2653e Windows: Fix missing soundio.dll 2025-05-15 14:37:35 -05:00
KeatonTheBot 98b4ff331c Android: Memory specific switches 2025-05-15 14:37:35 -05:00
KeatonTheBot 846b5b6e8a Android: Remove unmanaged code 2025-05-15 14:37:17 -05:00
KeatonTheBot fc0528876f nuget: bump System group to 9.0.5 2025-05-13 16:59:44 -05:00
Evan Husted 299b4cfe1d UI: Match System Time is now an active setting which you can toggle on/off. 2025-05-09 18:45:53 -05:00
Evan Husted d640f50203 UI: Button to set emulator time based on system time in settings, under the time settings 2025-05-08 17:05:03 -05:00
KeatonTheBot 95ac0a7a51 Assign DRAM IDs and Hardware Types to 10GiB-12GiB sizes
* Fix incorrect Hardware Type for 8GiB-12GiB DRAM sizes
2025-05-06 19:09:05 -05:00
KeatonTheBot 0dc506317c Add missing texture cache size for 10 GiB DRAM option
* Convert 'if' statement into 'switch' expression
2025-05-05 17:00:26 -05:00
KeatonTheBot 3e3b7d22e6 nuget: Split FFmpeg dependencies into separate packages per OS (Linux/macOS/Windows) 2025-05-03 23:51:42 -05:00
KeatonTheBot e02463d779 Vulkan: Revise feedback loop restriction to RDNA 3 GPUs
* Use RegEx to define RDNA 3 GPU name pattern

* Add device IDs for ROG Ally (X), since these are RDNA 3 devices
2025-05-01 21:40:51 -05:00
KeatonTheBot cb37aea614 Update Kenji-NX to 2.0.3 2025-04-27 21:39:26 -05:00
KeatonTheBot b40265e029 Revert "UI: Update Avalonia to 11.2.8, FluentAvalonia to 2.3.0"
This reverts commit 01f037ae83.
2025-04-27 21:24:58 -05:00
KeatonTheBot 10c9e46fbe nuget: bump Microsoft.IdentityModel.JsonWebToken to 8.9.0 2025-04-27 20:03:49 -05:00
KeatonTheBot 01f037ae83 UI: Update Avalonia to 11.2.8, FluentAvalonia to 2.3.0
* FluentAvalonia: Disabled NavigationView selection indicator animations due to bugged implementation in 2.1.0+, restoring previous behavior

* Avalonia: Fixed text on certain buttons being larger than normal

* Avalonia: Fixed ComboBox code inserting extra space to the left of selected items
2025-04-27 20:01:53 -05:00
KeatonTheBot bf618dc0b3 Revert "Custom refresh rate default value changed from 200% to 100%"
This reverts commit c48866c9ec.
2025-04-27 19:38:22 -05:00
LotP1 8b36e9fb39 fix: PPTC blacklist trigger conditions 2025-04-27 19:23:23 -05:00
Evan Husted 0cf29113c0 UI: Button to open screenshots folder in File menu 2025-04-27 18:25:40 -05:00
Evan Husted e75d162dbd UI: Always save screenshots to the Ryujinx data directory. 2025-04-27 18:25:31 -05:00
KeatonTheBot 038f8352e0 Vulkan: Minimize errors with feedback loop detection for AMD Radeon RX GPUs + Qualcomm SoCs 2025-04-27 00:37:22 -05:00
KeatonTheBot 2f48a5007a Update 'About' window 2025-04-26 23:04:35 -05:00
KeatonTheBot 45db10220e Increase # of maximum log files from 3 to 4 2025-04-26 20:05:51 -05:00
Evan HustedandMutantAura ebe623bc07 Stick Visualizer
![](https://i.imgur.com/iSaXRMr.png)

---------

Co-authored-by: MutantAura <domw0401@gmail.com>
2025-04-26 00:56:19 -05:00
LotP1 4bcfae5905 Reset PPTC Carriers on invalidation 2025-04-24 14:09:26 -05:00
LotP1 eb6a7b9fea reset infoStreams when the cache is invalid 2025-04-23 14:39:18 -05:00
LotP1 df322e6d57 Fix loading multiple mods with partially matching names
* Fix all mods always active
2025-04-23 14:38:48 -05:00
KeatonTheBot d46a6bfed5 Vulkan: Restrict feedback loop detection to AMD Radeon RX GPUs + Qualcomm SoCs 2025-04-21 22:18:59 -05:00
KeatonTheBot 0e810e1e96 Re-merge "Vulkan: Feedback loop detection and barriers (#7226)"
This re-merges commit ca59c3f499.
2025-04-21 22:18:59 -05:00
KeatonTheBot 5da6c490b3 Revert "Support VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2"
This reverts commit 0cef9647
2025-04-21 22:18:58 -05:00
gdkchan 2ec9dda408 Optimize XMAD instruction sequence into a single 32-bit multiply when possible 2025-04-21 16:40:54 -05:00
KeatonTheBot 5b03721db6 misc: chore: Merge duplicated switch sections 2025-04-12 12:27:26 -05:00
KeatonTheBot 3c644a712d misc: chore: Fix possible System.NullReferenceExceptions 2025-04-12 12:27:15 -05:00
KeatonTheBot 19013d360a misc: chore: Remove redundant qualifiers 2025-04-11 22:08:30 -05:00
KeatonTheBot 9bcb744a6a misc: chore: Remove redundant initializer, join declaration and assignment 2025-04-11 22:07:48 -05:00
KeatonTheBot 06ea0c32d3 misc: chore: Remove unnecessary usings 2025-04-11 21:56:23 -05:00
KeatonTheBot feb3d9d31f misc: chore: Fix XML errors 2025-04-11 21:56:20 -05:00
RyllGanda17 198ee01437 Implement GetCacheStorageMax (#5)
Fixes this problem
`|W| HLE.OsThread.47 KernelIpc CallCmifMethod: Missing service Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.IApplicationFunctions: 29 ignored`

Allows add-on content from Just Dance 2023/2024/2025 to be loaded.
2025-04-10 23:03:16 -05:00
KeatonTheBot 58e3b5489b Update Kenji-NX to 2.0.2 2025-04-10 19:42:46 -05:00
KeatonTheBot 73b2eb6aeb nuget: bump Microsoft.IdentityModel.JsonWebTokens to 8.8.0, System group to 9.0.4 2025-04-10 17:14:36 -05:00
KeatonTheBot 6202526666 misc: chore: Fix warnings in virtual dual Joy-Con code 2025-04-08 23:19:02 -05:00
GreemDev 25bb6e8af7 feature: Virtual dual Joy-Con 2025-04-08 19:30:40 -05:00
Evan Husted 191819488e misc: chore: rename IgnoreApplet to IgnoreControllerApplet, change localization & redo tooltip 2025-04-04 15:49:26 -05:00
KeatonTheBot 85eb4761e8 misc: chore: Fix logger message when configuration is migrated to version 57 2025-04-02 22:33:45 -05:00
KeatonTheBot 90bf2ece82 Update translations for 'Start Games with UI Hidden' option 2025-04-02 22:20:51 -05:00
asfasagag 772233d003 UI: Option to automatically Hide UI when game launches
Quality of life feature
Similar in function to the "Start Games in Fullscreen" toggle
For users who want to run games in windowed/non-fullscreen mode with
menu UI hidden, this eliminates the need to always click "Hide UI"
2025-04-02 22:06:21 -05:00
Tartifless 33955c1cb0 sdl2 guid, remove the CRC bytes (4 first characters) and replace with 0000 when creating guid 2025-03-28 02:36:36 -05:00
623 changed files with 13555 additions and 6821 deletions
+19 -33
View File
@@ -1,6 +1,6 @@
# Contribution to Ryujinx # Contribution to Kenji-NX
You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter. You can contribute to Kenji-NX with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter.
Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time. Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time.
# Quick Links # Quick Links
@@ -14,13 +14,13 @@ We always welcome bug reports, feature proposals and overall feedback. Here are
### Finding Existing Issues ### Finding Existing Issues
Before filing a new issue, please search our [open issues](https://github.com/KeatonTheBot/Ryujinx/issues) to check if it already exists. Before filing a new issue, please search our [open issues](https://git.ryujinx.app/kenji-nx/ryujinx/-/issues) to check if it already exists.
If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog. If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog.
### Writing a Good Feature Request ### Writing a Good Feature Request
Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D). Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please go [here](https://git.ryujinx.app/kenji-nx/ryujinx/-/issues).
### Writing a Good Bug Report ### Writing a Good Bug Report
@@ -34,13 +34,13 @@ Ideally, a bug report should contain the following information:
* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically. * A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically.
* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds? * Additional information, e.g. is it a regression from previous versions? Are there any known workarounds?
When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/KeatonTheBot/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D). When ready to submit a bug report, please go [here](https://git.ryujinx.app/kenji-nx/ryujinx/-/issues.
## Contributing Changes ## Contributing Changes
Project maintainers will merge changes that both improve the project and meet our standards for code quality. Project maintainers will merge changes that both improve the project and meet our standards for code quality.
The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/KeatonTheBot/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance. The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](LICENSE.txt) docs define additional guidance.
### DOs and DON'Ts ### DOs and DON'Ts
@@ -67,30 +67,25 @@ Please do not:
We use and recommend the following workflow: We use and recommend the following workflow:
1. Create or find an issue for your work. 1. Create or find an issue for your work.
- You can skip this step for trivial changes. - You can skip this step for trivial changes.
- Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality. - Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality.
- Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person. - Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person.
2. Create a personal fork of the repository on GitHub (if you don't already have one). 2. Create a personal fork of the repository on GitHub (if you don't already have one).
3. In your fork, create a branch off of main (`git checkout -b mybranch`). 3. In your fork, create a branch off of main (`git checkout -b mybranch`).
- Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork. - Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork.
4. Make and commit your changes to your branch. 4. Make and commit your changes to your branch.
- [Build Instructions](https://github.com/KeatonTheBot/Ryujinx#building) explains how to build and test. - [Build Instructions](https://git.ryujinx.app/kenji-nx/ryujinx#building) explains how to build and test.
- Commit messages should be clear statements of action and intent. - Commit messages should be clear statements of action and intent.
6. Build the repository with your changes. 6. Build the repository with your changes.
- Make sure that the builds are clean. - Make sure that the builds are clean.
- Make sure that `dotnet format` has been run and any corrections tested and committed. - Make sure that `dotnet format` has been run and any corrections tested and committed.
7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch. 7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch.
- State in the description what issue or improvement your change is addressing. - State in the description what issue or improvement your change is addressing.
- Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/KeatonTheBot/Ryujinx/actions) to check for outstanding errors.
8. Wait for feedback or approval of your changes from the core development team 8. Wait for feedback or approval of your changes from the core development team
- Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md). - Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md).
9. When the team members have signed off, and all checks are green, your PR will be merged. 9. When the team members have signed off, and all checks are green, your PR will be merged.
- The next official build will automatically include your change. - The next official build will automatically include your change.
- You can delete the branch you used for making the change. - You can delete the branch you used for making the change.
### Good First Issues
The team marks the most straightforward issues as [good first issues](https://github.com/KeatonTheBot/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase.
### Commit Messages ### Commit Messages
@@ -111,15 +106,6 @@ Fix #42
Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits. Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits.
### PR - CI Process
The [Ryujinx continuous integration](https://github.com/KeatonTheBot/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change.
If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure:
* `dotnet format` has not been run on the PR and has outstanding stylistic issues.
* There is an error within the PR that fails a test or errors the compiler.
* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job.
### PR Feedback ### PR Feedback
Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback. Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback.
@@ -134,5 +120,5 @@ Ryujinx uses some implementations and frameworks from other projects. The follow
- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence). - The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence).
- The license of the file is left in-tact. - The license of the file is left in-tact.
- The contribution is correctly attributed in the [3rd party notices](https://github.com/KeatonTheBot/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed. - The contribution is correctly attributed in the [3rd party notices](distribution/legal/THIRDPARTY.md) file in the repository, as needed.
+25 -26
View File
@@ -3,42 +3,41 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.13" /> <PackageVersion Include="Avalonia" Version="11.3.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.13" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.13" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.13" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.13" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.19" /> <PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.19" /> <PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="2.2.2" /> <PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.0.4" /> <PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build2" />
<PackageVersion Include="Gommon" Version="2.7.1.1" /> <PackageVersion Include="Gommon" Version="2.7.1.1" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="Humanizer" Version="2.14.1" /> <PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.6.1" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.14.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" /> <PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.8.2" /> <PackageVersion Include="OpenTK.Core" Version="4.9.4" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" /> <PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" /> <PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" /> <PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" /> <PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.24.3" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Linux" Version="6.1.3-build5" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.macOS" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.3-build5" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.116" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS-Redux" Version="2.32.10" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
@@ -48,8 +47,8 @@
<PackageVersion Include="SkiaSharp" Version="2.88.9" /> <PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.3" /> <PackageVersion Include="System.IO.Hashing" Version="9.0.9" />
<PackageVersion Include="System.Management" Version="9.0.3" /> <PackageVersion Include="System.Management" Version="9.0.9" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+5 -5
View File
@@ -1,11 +1,11 @@
<h1 align="center"> <h1 align="center">
<br> <br>
<img src="distribution/misc/Logo.png" alt="Kenji-NX"> <img src="https://git.ryujinx.app/kenji-nx/ryujinx/-/raw/master/distribution/misc/Logo.png" alt="Kenji-NX">
<br> <br>
<b>Kenji-NX</b> <b>Kenji-NX</b>
<br> <br>
<a href="https://github.com/KeatonTheBot/Kenji-NX/releases/latest"> <a href="https://github.com/Kenji-NX/Releases/releases/latest">
<img src="https://img.shields.io/github/v/release/KeatonTheBot/Kenji-NX" <img src="https://img.shields.io/github/v/release/Kenji-NX/Releases"
alt="Latest Release"> alt="Latest Release">
</a> </a>
</h1> </h1>
@@ -14,7 +14,7 @@
Kenji-NX is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#. Kenji-NX is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017. It was written from scratch and development on the project began in September 2017.
Kenji-NX is available on GitHub under the <a href="https://github.com/KeatonTheBot/Kenji-NX/blob/master/LICENSE.txt" target="_blank">MIT license</a>. Kenji-NX is available on GitHub under the <a href="https://git.ryujinx.app/kenji-nx/ryujinx/-/raw/master/LICENSE.txt" target="_blank">MIT license</a>.
<br><br> <br><br>
On October 1st 2024, Ryujinx was discontinued as the creator was forced to abandon the project. On October 1st 2024, Ryujinx was discontinued as the creator was forced to abandon the project.
<br><br> <br><br>
@@ -63,7 +63,7 @@ Make sure your SDK version is higher or equal to the required version specified
### Step 2 ### Step 2
Either use `git clone https://github.com/KeatonTheBot/Kenji-NX` on the command line to clone the repository or use Code --> Download zip button to get the files. Either use `git clone https://git.ryujinx.app/kenji-nx/ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
### Step 3 ### Step 3
-6
View File
@@ -63,8 +63,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\
EndProject 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.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
@@ -201,10 +199,6 @@ Global
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.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.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.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.ActiveCfg = Release|Any CPU
+3 -3
View File
@@ -1,11 +1,11 @@
# Documents Index # Documents Index
This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code. This repo includes several documents that explain both high-level and low-level concepts about Kenji-NX and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code.
Intro to Ryujinx Intro to Kenji-NX
================== ==================
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. Kenji-NX is an open-source Nintendo Switch emulator written in C#. It is based on Ryujinx, which was originally created by gdkchan.
* 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 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. * 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 SDL2, with OpenAL & libsoundio as fallbacks.
+4 -9
View File
@@ -2,7 +2,7 @@
## Contributing Rules ## Contributing Rules
All contributions to KeatonTheBot/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team. All contributions to Kenji-NX repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team.
To merge pull requests, you must have write permissions in the repository. To merge pull requests, you must have write permissions in the repository.
@@ -18,17 +18,13 @@ To merge pull requests, you must have write permissions in the repository.
## Pull Request Ownership ## Pull Request Ownership
Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned. Every pull request will automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned.
If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github). If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github).
## Pull Request Builds
When submitting a PR to the `KeatonTheBot/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/KeatonTheBot/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion.
## Review Turnaround Times ## Review Turnaround Times
Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory. Kenji-NX is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory.
* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable. * Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable.
* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion. * If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion.
@@ -41,8 +37,7 @@ To re-iterate, make the review as easy for us as possible, respond promptly and
Anyone with write access can merge a pull request manually when the following conditions have been met: Anyone with write access can merge a pull request manually when the following conditions have been met:
* The PR has been approved by two reviewers and any other objections are addressed. * The PR has been approved by two reviewers and any other objections are addressed.
* You can request follow up reviews from the original reviewers if they requested changes. * You can request follow-up reviews from the original reviewers if they requested changes.
* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/KeatonTheBot/Ryujinx/actions) tab of your PR.
Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them. Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them.
+16 -4
View File
@@ -1,7 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources> <!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
</packageSources>
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<!-- These are defined and .NET still yells about multiple package sources with no mappings. Not sure what to do, this is in the docs lol -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="LibHacAlpha">
<package pattern="Ryujinx.LibHac" />
</packageSource>
</packageSourceMapping>
</configuration> </configuration>
+1 -1
View File
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<RuntimeIdentifiers>osx-arm64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup> </ItemGroup>
@@ -254,7 +254,7 @@ namespace ARMeilleure.CodeGen.Arm64
private static bool IsMemoryLoadOrStore(Instruction inst) private static bool IsMemoryLoadOrStore(Instruction inst)
{ {
return inst == Instruction.Load || inst == Instruction.Store; return inst is Instruction.Load or Instruction.Store;
} }
private static bool ConstTooLong(Operand constOp, OperandType accessType) private static bool ConstTooLong(Operand constOp, OperandType accessType)
@@ -52,7 +52,7 @@ namespace ARMeilleure.CodeGen.Arm64
// Any value AND all ones will be equal itself, so it's effectively a no-op. // Any value AND all ones will be equal itself, so it's effectively a no-op.
// Any value OR all ones will be equal all ones, so one can just use MOV. // Any value OR all ones will be equal all ones, so one can just use MOV.
// Any value XOR all ones will be equal its inverse, so one can just use MVN. // Any value XOR all ones will be equal its inverse, so one can just use MVN.
if (value == 0 || value == ulong.MaxValue) if (value is 0 or ulong.MaxValue)
{ {
immN = 0; immN = 0;
immS = 0; immS = 0;
@@ -189,8 +189,7 @@ namespace ARMeilleure.CodeGen.Arm64
// The only blocks which can have 0 successors are exit blocks. // The only blocks which can have 0 successors are exit blocks.
Operation last = block.Operations.Last; Operation last = block.Operations.Last;
Debug.Assert(last.Instruction == Instruction.Tailcall || Debug.Assert(last.Instruction is Instruction.Tailcall or Instruction.Return);
last.Instruction == Instruction.Return);
} }
else else
{ {
@@ -464,7 +463,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination; Operand dest = operation.Destination;
Operand source = operation.GetSource(0); Operand source = operation.GetSource(0);
Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); Debug.Assert(dest.Type is OperandType.FP32 or OperandType.FP64);
Debug.Assert(dest.Type != source.Type); Debug.Assert(dest.Type != source.Type);
Debug.Assert(source.Type != OperandType.V128); Debug.Assert(source.Type != OperandType.V128);
@@ -483,7 +482,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operand dest = operation.Destination; Operand dest = operation.Destination;
Operand source = operation.GetSource(0); Operand source = operation.GetSource(0);
Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); Debug.Assert(dest.Type is OperandType.FP32 or OperandType.FP64);
Debug.Assert(dest.Type != source.Type); Debug.Assert(dest.Type != source.Type);
Debug.Assert(source.Type.IsInteger()); Debug.Assert(source.Type.IsInteger());
@@ -1463,7 +1462,7 @@ namespace ARMeilleure.CodeGen.Arm64
private static bool IsLoadOrStore(Operation operation) private static bool IsLoadOrStore(Operation operation)
{ {
return operation.Instruction == Instruction.Load || operation.Instruction == Instruction.Store; return operation.Instruction is Instruction.Load or Instruction.Store;
} }
private static OperandType GetMemOpValueType(Operation operation) private static OperandType GetMemOpValueType(Operation operation)
@@ -1553,7 +1552,7 @@ namespace ARMeilleure.CodeGen.Arm64
private static void EnsureSameReg(Operand op1, Operand op2) private static void EnsureSameReg(Operand op1, Operand op2)
{ {
Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); Debug.Assert(op1.Kind is OperandKind.Register or OperandKind.Memory);
Debug.Assert(op1.Kind == op2.Kind); Debug.Assert(op1.Kind == op2.Kind);
Debug.Assert(op1.Value == op2.Value); Debug.Assert(op1.Value == op2.Value);
} }
+14 -14
View File
@@ -736,19 +736,19 @@ namespace ARMeilleure.CodeGen.Arm64
{ {
IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask));
return info.Type == IntrinsicType.ScalarBinaryRd || return info.Type is IntrinsicType.ScalarBinaryRd
info.Type == IntrinsicType.ScalarTernaryFPRdByElem || or IntrinsicType.ScalarTernaryFPRdByElem
info.Type == IntrinsicType.ScalarTernaryShlRd || or IntrinsicType.ScalarTernaryShlRd
info.Type == IntrinsicType.ScalarTernaryShrRd || or IntrinsicType.ScalarTernaryShrRd
info.Type == IntrinsicType.Vector128BinaryRd || or IntrinsicType.Vector128BinaryRd
info.Type == IntrinsicType.VectorBinaryRd || or IntrinsicType.VectorBinaryRd
info.Type == IntrinsicType.VectorInsertByElem || or IntrinsicType.VectorInsertByElem
info.Type == IntrinsicType.VectorTernaryRd || or IntrinsicType.VectorTernaryRd
info.Type == IntrinsicType.VectorTernaryRdBitwise || or IntrinsicType.VectorTernaryRdBitwise
info.Type == IntrinsicType.VectorTernaryFPRdByElem || or IntrinsicType.VectorTernaryFPRdByElem
info.Type == IntrinsicType.VectorTernaryRdByElem || or IntrinsicType.VectorTernaryRdByElem
info.Type == IntrinsicType.VectorTernaryShlRd || or IntrinsicType.VectorTernaryShlRd
info.Type == IntrinsicType.VectorTernaryShrRd; or IntrinsicType.VectorTernaryShrRd;
} }
private static bool HasConstSrc1(Operation node, ulong value) private static bool HasConstSrc1(Operation node, ulong value)
@@ -849,7 +849,7 @@ namespace ARMeilleure.CodeGen.Arm64
var compType = (Comparison)comp.AsInt32(); var compType = (Comparison)comp.AsInt32();
return compType == Comparison.Equal || compType == Comparison.NotEqual; return compType is Comparison.Equal or Comparison.NotEqual;
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Gets an empty <see cref="RelocInfo"/>. /// Gets an empty <see cref="RelocInfo"/>.
/// </summary> /// </summary>
public static RelocInfo Empty { get; } = new RelocInfo(null); public static RelocInfo Empty { get; } = new(null);
private readonly RelocEntry[] _entries; private readonly RelocEntry[] _entries;
@@ -227,11 +227,11 @@ namespace ARMeilleure.CodeGen.Optimizations
private static bool HasSideEffects(Operation node) private static bool HasSideEffects(Operation node)
{ {
return node.Instruction == Instruction.Call return node.Instruction is Instruction.Call
|| node.Instruction == Instruction.Tailcall or Instruction.Tailcall
|| node.Instruction == Instruction.CompareAndSwap or Instruction.CompareAndSwap
|| node.Instruction == Instruction.CompareAndSwap16 or Instruction.CompareAndSwap16
|| node.Instruction == Instruction.CompareAndSwap8; or Instruction.CompareAndSwap8;
} }
private static bool IsPropagableCompare(Operation operation) private static bool IsPropagableCompare(Operation operation)
@@ -839,7 +839,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{ {
dest.NumberLocal(_intervals.Count); dest.NumberLocal(_intervals.Count);
LiveInterval interval = new LiveInterval(dest); LiveInterval interval = new(dest);
_intervals.Add(interval); _intervals.Add(interval);
SetVisited(dest); SetVisited(dest);
@@ -847,7 +847,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// If this is a copy (or copy-like operation), set the copy source interval as well. // If this is a copy (or copy-like operation), set the copy source interval as well.
// This is used for register preferencing later on, which allows the copy to be eliminated // This is used for register preferencing later on, which allows the copy to be eliminated
// in some cases. // in some cases.
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32) if (node.Instruction is Instruction.Copy or Instruction.ZeroExtend32)
{ {
Operand source = node.GetSource(0); Operand source = node.GetSource(0);
@@ -1120,8 +1120,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private static bool IsLocalOrRegister(OperandKind kind) private static bool IsLocalOrRegister(OperandKind kind)
{ {
return kind == OperandKind.LocalVariable || return kind is OperandKind.LocalVariable or OperandKind.Register;
kind == OperandKind.Register;
} }
} }
} }
+1 -1
View File
@@ -1477,7 +1477,7 @@ namespace ARMeilleure.CodeGen.X86
private static bool Is64Bits(OperandType type) private static bool Is64Bits(OperandType type)
{ {
return type == OperandType.I64 || type == OperandType.FP64; return type is OperandType.I64 or OperandType.FP64;
} }
private static bool IsImm8(ulong immediate, OperandType type) private static bool IsImm8(ulong immediate, OperandType type)
+4 -5
View File
@@ -175,8 +175,7 @@ namespace ARMeilleure.CodeGen.X86
// The only blocks which can have 0 successors are exit blocks. // The only blocks which can have 0 successors are exit blocks.
Operation last = block.Operations.Last; Operation last = block.Operations.Last;
Debug.Assert(last.Instruction == Instruction.Tailcall || Debug.Assert(last.Instruction is Instruction.Tailcall or Instruction.Return);
last.Instruction == Instruction.Return);
} }
else else
{ {
@@ -478,7 +477,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(HardwareCapabilities.SupportsVexEncoding); Debug.Assert(HardwareCapabilities.SupportsVexEncoding);
Debug.Assert(dest.Kind == OperandKind.Register && src1.Kind == OperandKind.Register && src2.Kind == OperandKind.Register); Debug.Assert(dest.Kind == OperandKind.Register && src1.Kind == OperandKind.Register && src2.Kind == OperandKind.Register);
Debug.Assert(src3.Kind == OperandKind.Register || src3.Kind == OperandKind.Memory); Debug.Assert(src3.Kind is OperandKind.Register or OperandKind.Memory);
EnsureSameType(dest, src1, src2, src3); EnsureSameType(dest, src1, src2, src3);
Debug.Assert(dest.Type == OperandType.V128); Debug.Assert(dest.Type == OperandType.V128);
@@ -788,7 +787,7 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = operation.Destination; Operand dest = operation.Destination;
Operand source = operation.GetSource(0); Operand source = operation.GetSource(0);
Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); Debug.Assert(dest.Type is OperandType.FP32 or OperandType.FP64);
if (dest.Type == OperandType.FP32) if (dest.Type == OperandType.FP32)
{ {
@@ -1723,7 +1722,7 @@ namespace ARMeilleure.CodeGen.X86
return; return;
} }
Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); Debug.Assert(op1.Kind is OperandKind.Register or OperandKind.Memory);
Debug.Assert(op1.Kind == op2.Kind); Debug.Assert(op1.Kind == op2.Kind);
Debug.Assert(op1.Value == op2.Value); Debug.Assert(op1.Value == op2.Value);
} }
+6 -6
View File
@@ -312,9 +312,9 @@ namespace ARMeilleure.CodeGen.X86
case Instruction.Extended: case Instruction.Extended:
{ {
bool isBlend = node.Intrinsic == Intrinsic.X86Blendvpd || bool isBlend = node.Intrinsic is Intrinsic.X86Blendvpd
node.Intrinsic == Intrinsic.X86Blendvps || or Intrinsic.X86Blendvps
node.Intrinsic == Intrinsic.X86Pblendvb; or Intrinsic.X86Pblendvb;
// BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported.
// SHA256RNDS2 always has an implied XMM0 as a last operand. // SHA256RNDS2 always has an implied XMM0 as a last operand.
@@ -513,8 +513,8 @@ namespace ARMeilleure.CodeGen.X86
Operand dest = node.Destination; Operand dest = node.Destination;
Operand source = node.GetSource(0); Operand source = node.GetSource(0);
Debug.Assert(dest.Type == OperandType.FP32 || Debug.Assert(dest.Type is OperandType.FP32 or OperandType.FP64,
dest.Type == OperandType.FP64, $"Invalid destination type \"{dest.Type}\"."); $"Invalid destination type \"{dest.Type}\".");
Operation currentNode = node; Operation currentNode = node;
@@ -761,7 +761,7 @@ namespace ARMeilleure.CodeGen.X86
var compType = (Comparison)comp.AsInt32(); var compType = (Comparison)comp.AsInt32();
return compType == Comparison.Equal || compType == Comparison.NotEqual; return compType is Comparison.Equal or Comparison.NotEqual;
} }
} }
+6 -6
View File
@@ -248,12 +248,12 @@ namespace ARMeilleure.CodeGen.X86
private static bool IsMemoryLoadOrStore(Instruction inst) private static bool IsMemoryLoadOrStore(Instruction inst)
{ {
return inst == Instruction.Load || return inst is Instruction.Load
inst == Instruction.Load16 || or Instruction.Load16
inst == Instruction.Load8 || or Instruction.Load8
inst == Instruction.Store || or Instruction.Store
inst == Instruction.Store16 || or Instruction.Store16
inst == Instruction.Store8; or Instruction.Store8;
} }
} }
} }
+5 -11
View File
@@ -254,8 +254,7 @@ namespace ARMeilleure.Decoders
} }
// Compare and branch instructions are always conditional. // Compare and branch instructions are always conditional.
if (opCode.Instruction.Name == InstName.Cbz || if (opCode.Instruction.Name is InstName.Cbz or InstName.Cbnz)
opCode.Instruction.Name == InstName.Cbnz)
{ {
return false; return false;
} }
@@ -284,7 +283,7 @@ namespace ARMeilleure.Decoders
// register (Rt == 15 or (mask & (1 << 15)) != 0), and cases where there is // register (Rt == 15 or (mask & (1 << 15)) != 0), and cases where there is
// a write back to PC (wback == true && Rn == 15), however the later may // a write back to PC (wback == true && Rn == 15), however the later may
// be "undefined" depending on the CPU, so compilers should not produce that. // be "undefined" depending on the CPU, so compilers should not produce that.
if (opCode is IOpCode32Mem || opCode is IOpCode32MemMult) if (opCode is IOpCode32Mem or IOpCode32MemMult)
{ {
int rt, rn; int rt, rn;
@@ -326,15 +325,12 @@ namespace ARMeilleure.Decoders
} }
// Explicit branch instructions. // Explicit branch instructions.
return opCode is IOpCode32BImm || return opCode is IOpCode32BImm or IOpCode32BReg;
opCode is IOpCode32BReg;
} }
private static bool IsCall(OpCode opCode) private static bool IsCall(OpCode opCode)
{ {
return opCode.Instruction.Name == InstName.Bl || return opCode.Instruction.Name is InstName.Bl or InstName.Blr or InstName.Blx;
opCode.Instruction.Name == InstName.Blr ||
opCode.Instruction.Name == InstName.Blx;
} }
private static bool IsException(OpCode opCode) private static bool IsException(OpCode opCode)
@@ -344,9 +340,7 @@ namespace ARMeilleure.Decoders
private static bool IsTrap(OpCode opCode) private static bool IsTrap(OpCode opCode)
{ {
return opCode.Instruction.Name == InstName.Brk || return opCode.Instruction.Name is InstName.Brk or InstName.Trap or InstName.Und;
opCode.Instruction.Name == InstName.Trap ||
opCode.Instruction.Name == InstName.Und;
} }
public static OpCode DecodeOpCode(IMemoryManager memory, ulong address, ExecutionMode mode) public static OpCode DecodeOpCode(IMemoryManager memory, ulong address, ExecutionMode mode)
+1 -2
View File
@@ -28,8 +28,7 @@ namespace ARMeilleure.Decoders
MemOp type = WBack ? (MemOp)((opCode >> 10) & 3) : MemOp.Unsigned; MemOp type = WBack ? (MemOp)((opCode >> 10) & 3) : MemOp.Unsigned;
PostIdx = type == MemOp.PostIndexed; PostIdx = type == MemOp.PostIndexed;
Unscaled = type == MemOp.Unscaled || Unscaled = type is MemOp.Unscaled or MemOp.Unprivileged;
type == MemOp.Unprivileged;
// Unscaled and Unprivileged doesn't write back, // Unscaled and Unprivileged doesn't write back,
// but they do use the 9-bits Signed Immediate. // but they do use the 9-bits Signed Immediate.
+1 -2
View File
@@ -235,8 +235,7 @@ namespace ARMeilleure.Diagnostics
{ {
_builder.Append('.').Append(operation.Intrinsic); _builder.Append('.').Append(operation.Intrinsic);
} }
else if (operation.Instruction == Instruction.BranchIf || else if (operation.Instruction is Instruction.BranchIf or Instruction.Compare)
operation.Instruction == Instruction.Compare)
{ {
comparison = true; comparison = true;
} }
@@ -283,8 +283,6 @@ namespace ARMeilleure.Instructions
switch (op.ShiftType) switch (op.ShiftType)
{ {
case ShiftType.Lsr: case ShiftType.Lsr:
shift = 32;
break;
case ShiftType.Asr: case ShiftType.Asr:
shift = 32; shift = 32;
break; break;
@@ -332,8 +330,6 @@ namespace ARMeilleure.Instructions
switch (shiftType) switch (shiftType)
{ {
case ShiftType.Lsr: case ShiftType.Lsr:
shift = 32;
break;
case ShiftType.Asr: case ShiftType.Asr:
shift = 32; shift = 32;
break; break;
@@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext(); context.LoadFromContext();
context.Return(Const(op.Address)); InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
} }
public static void Svc(ArmEmitterContext context) public static void Svc(ArmEmitterContext context)
@@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext(); context.LoadFromContext();
context.Return(Const(op.Address)); InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
} }
} }
} }
@@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext(); context.LoadFromContext();
context.Return(Const(context.CurrOp.Address)); InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address));
} }
} }
} }
+1 -1
View File
@@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
{ {
OpCodeBReg op = (OpCodeBReg)context.CurrOp; 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); public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true);
@@ -12,6 +12,10 @@ namespace ARMeilleure.Instructions
{ {
static class InstEmitFlowHelper 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) public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond)
{ {
if (cond != Condition.Al) if (cond != Condition.Al)
@@ -142,6 +146,12 @@ namespace ARMeilleure.Instructions
public static void EmitCall(ArmEmitterContext context, ulong immediate) public static void EmitCall(ArmEmitterContext context, ulong immediate)
{ {
if (context.IsSingleStep)
{
context.Return(Const(immediate));
return;
}
bool isRecursive = immediate == context.EntryAddress; bool isRecursive = immediate == context.EntryAddress;
if (isRecursive) if (isRecursive)
@@ -156,12 +166,7 @@ namespace ARMeilleure.Instructions
public static void EmitVirtualCall(ArmEmitterContext context, Operand target) public static void EmitVirtualCall(ArmEmitterContext context, Operand target)
{ {
EmitTableBranch(context, target, isJump: false); if (context.IsSingleStep)
}
public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn)
{
if (isReturn)
{ {
if (target.Type == OperandType.I32) if (target.Type == OperandType.I32)
{ {
@@ -171,11 +176,36 @@ namespace ARMeilleure.Instructions
context.Return(target); context.Return(target);
} }
else else
{
EmitTableBranch(context, target, isJump: false);
}
}
public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn)
{
if (isReturn || context.IsSingleStep)
{
EmitReturn(context, target);
}
else
{ {
EmitTableBranch(context, target, isJump: true); EmitTableBranch(context, target, isJump: true);
} }
} }
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) private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump)
{ {
context.StoreToContext(); context.StoreToContext();
@@ -238,6 +268,8 @@ namespace ARMeilleure.Instructions
if (isJump) if (isJump)
{ {
DecreaseCallDepth(context, nativeContext);
context.Tailcall(hostAddress, nativeContext); context.Tailcall(hostAddress, nativeContext);
} }
else else
@@ -259,8 +291,42 @@ namespace ARMeilleure.Instructions
Operand lblContinue = context.GetLabel(nextAddr.Value); Operand lblContinue = context.GetLabel(nextAddr.Value);
context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold);
DecreaseCallDepth(context, nativeContext);
context.Return(returnAddress); 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)));
}
} }
} }
@@ -140,7 +140,7 @@ namespace ARMeilleure.Instructions
if (pair) if (pair)
{ {
Debug.Assert(op.Size == 2 || op.Size == 3, "Invalid size for pairwise store."); Debug.Assert(op.Size is 2 or 3, "Invalid size for pairwise store.");
Operand t2 = GetIntOrZR(context, op.Rt2); Operand t2 = GetIntOrZR(context, op.Rt2);
@@ -59,7 +59,7 @@ namespace ARMeilleure.Instructions
{ {
Operand value = GetInt(context, rt); Operand value = GetInt(context, rt);
if (ext == Extension.Sx32 || ext == Extension.Sx64) if (ext is Extension.Sx32 or Extension.Sx64)
{ {
OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32; OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32;
@@ -124,8 +124,7 @@ namespace ARMeilleure.Instructions
private static bool IsSimd(ArmEmitterContext context) private static bool IsSimd(ArmEmitterContext context)
{ {
return context.CurrOp is IOpCodeSimd && return context.CurrOp is IOpCodeSimd &&
!(context.CurrOp is OpCodeSimdMemMs || !(context.CurrOp is OpCodeSimdMemMs or OpCodeSimdMemSs);
context.CurrOp is OpCodeSimdMemSs);
} }
public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size) public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size)
@@ -305,8 +304,6 @@ namespace ARMeilleure.Instructions
context.Store16(physAddr, value); context.Store16(physAddr, value);
break; break;
case 2: case 2:
context.Store(physAddr, value);
break;
case 3: case 3:
context.Store(physAddr, value); context.Store(physAddr, value);
break; break;
@@ -591,8 +588,6 @@ namespace ARMeilleure.Instructions
value = context.VectorInsert16(vector, value, elem); value = context.VectorInsert16(vector, value, elem);
break; break;
case 2: case 2:
value = context.VectorInsert(vector, value, elem);
break;
case 3: case 3:
value = context.VectorInsert(vector, value, elem); value = context.VectorInsert(vector, value, elem);
break; break;
@@ -733,8 +728,6 @@ namespace ARMeilleure.Instructions
switch (op.ShiftType) switch (op.ShiftType)
{ {
case ShiftType.Lsr: case ShiftType.Lsr:
shift = 32;
break;
case ShiftType.Asr: case ShiftType.Asr:
shift = 32; shift = 32;
break; break;
@@ -1119,7 +1119,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, int size, bool signed) private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, int size, bool signed)
{ {
Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); Debug.Assert(value.Type is OperandType.I32 or OperandType.I64);
Debug.Assert((uint)size < 2); Debug.Assert((uint)size < 2);
OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64; OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64;
@@ -1136,7 +1136,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitScalarFcvts(ArmEmitterContext context, Operand value, int fBits) private static Operand EmitScalarFcvts(ArmEmitterContext context, Operand value, int fBits)
{ {
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); Debug.Assert(value.Type is OperandType.FP32 or OperandType.FP64);
value = EmitF2iFBitsMul(context, value, fBits); value = EmitF2iFBitsMul(context, value, fBits);
@@ -1160,7 +1160,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitScalarFcvtu(ArmEmitterContext context, Operand value, int fBits) private static Operand EmitScalarFcvtu(ArmEmitterContext context, Operand value, int fBits)
{ {
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); Debug.Assert(value.Type is OperandType.FP32 or OperandType.FP64);
value = EmitF2iFBitsMul(context, value, fBits); value = EmitF2iFBitsMul(context, value, fBits);
@@ -1184,7 +1184,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitF2iFBitsMul(ArmEmitterContext context, Operand value, int fBits) private static Operand EmitF2iFBitsMul(ArmEmitterContext context, Operand value, int fBits)
{ {
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); Debug.Assert(value.Type is OperandType.FP32 or OperandType.FP64);
if (fBits == 0) if (fBits == 0)
{ {
@@ -1203,7 +1203,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitI2fFBitsMul(ArmEmitterContext context, Operand value, int fBits) private static Operand EmitI2fFBitsMul(ArmEmitterContext context, Operand value, int fBits)
{ {
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); Debug.Assert(value.Type is OperandType.FP32 or OperandType.FP64);
if (fBits == 0) if (fBits == 0)
{ {
@@ -635,7 +635,7 @@ namespace ARMeilleure.Instructions
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed) private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)
{ {
Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); Debug.Assert(value.Type is OperandType.I32 or OperandType.I64);
if (signed) if (signed)
{ {
@@ -363,7 +363,7 @@ namespace ARMeilleure.Instructions
public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.). public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.).
{ {
Debug.Assert(op.Type == OperandType.I32 || op.Type == OperandType.I64); Debug.Assert(op.Type is OperandType.I32 or OperandType.I64);
Operand op0 = context.Subtract(op, context.BitwiseAnd(context.ShiftRightUI(op, Const(1)), Const(op.Type, 0x55L))); Operand op0 = context.Subtract(op, context.BitwiseAnd(context.ShiftRightUI(op, Const(1)), Const(op.Type, 0x55L)));
@@ -489,7 +489,7 @@ namespace ARMeilleure.Instructions
public static Operand EmitRoundByRMode(ArmEmitterContext context, Operand op) public static Operand EmitRoundByRMode(ArmEmitterContext context, Operand op)
{ {
Debug.Assert(op.Type == OperandType.FP32 || op.Type == OperandType.FP64); Debug.Assert(op.Type is OperandType.FP32 or OperandType.FP64);
Operand lbl1 = Label(); Operand lbl1 = Label();
Operand lbl2 = Label(); Operand lbl2 = Label();
@@ -1676,7 +1676,7 @@ namespace ARMeilleure.Instructions
int eSize = 8 << size; int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand lbl1 = Label(); Operand lbl1 = Label();
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -1709,7 +1709,7 @@ namespace ARMeilleure.Instructions
int eSize = 8 << size; int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -1735,7 +1735,7 @@ namespace ARMeilleure.Instructions
int eSizeDst = 8 << sizeDst; int eSizeDst = 8 << sizeDst;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); Debug.Assert(eSizeDst is 8 or 16 or 32);
Operand lbl1 = Label(); Operand lbl1 = Label();
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -1768,7 +1768,7 @@ namespace ARMeilleure.Instructions
int eSizeDst = 8 << sizeDst; int eSizeDst = 8 << sizeDst;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); Debug.Assert(eSizeDst is 8 or 16 or 32);
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -2082,8 +2082,6 @@ namespace ARMeilleure.Instructions
vector = context.VectorInsert16(vector, value, index); vector = context.VectorInsert16(vector, value, index);
break; break;
case 2: case 2:
vector = context.VectorInsert(vector, value, index);
break;
case 3: case 3:
vector = context.VectorInsert(vector, value, index); vector = context.VectorInsert(vector, value, index);
break; break;
@@ -31,7 +31,7 @@ namespace ARMeilleure.Instructions
{ {
Debug.Assert(type != OperandType.V128); Debug.Assert(type != OperandType.V128);
if (type == OperandType.FP64 || type == OperandType.I64) if (type is OperandType.FP64 or OperandType.I64)
{ {
// From dreg. // From dreg.
return context.VectorExtract(type, GetVecA32(reg >> 1), reg & 1); return context.VectorExtract(type, GetVecA32(reg >> 1), reg & 1);
@@ -48,7 +48,7 @@ namespace ARMeilleure.Instructions
Debug.Assert(value.Type != OperandType.V128); Debug.Assert(value.Type != OperandType.V128);
Operand vec, insert; Operand vec, insert;
if (value.Type == OperandType.FP64 || value.Type == OperandType.I64) if (value.Type is OperandType.FP64 or OperandType.I64)
{ {
// From dreg. // From dreg.
vec = GetVecA32(reg >> 1); vec = GetVecA32(reg >> 1);
@@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions
public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value) public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value)
{ {
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.I32); Debug.Assert(value.Type is OperandType.FP32 or OperandType.I32);
Operand vec, insert; Operand vec, insert;
vec = GetVecA32(reg >> 2); vec = GetVecA32(reg >> 2);
@@ -1634,7 +1634,7 @@ namespace ARMeilleure.Instructions
int eSize = 8 << size; int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand res = context.AllocateLocal(OperandType.I64); Operand res = context.AllocateLocal(OperandType.I64);
@@ -1657,7 +1657,7 @@ namespace ARMeilleure.Instructions
int eSize = 8 << size; int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -1732,7 +1732,7 @@ namespace ARMeilleure.Instructions
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(shiftLsB.Type == OperandType.I32); Debug.Assert(shiftLsB.Type == OperandType.I32);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand lbl1 = Label(); Operand lbl1 = Label();
Operand lblEnd = Label(); Operand lblEnd = Label();
@@ -1769,7 +1769,7 @@ namespace ARMeilleure.Instructions
Debug.Assert(op.Type == OperandType.I64); Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(shiftLsB.Type == OperandType.I32); Debug.Assert(shiftLsB.Type == OperandType.I32);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); Debug.Assert(eSize is 8 or 16 or 32 or 64);
Operand lbl1 = Label(); Operand lbl1 = Label();
Operand lbl2 = Label(); Operand lbl2 = Label();
@@ -1,9 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ARMeilleure.Instructions namespace ARMeilleure.Instructions
{ {
@@ -200,7 +200,11 @@ namespace ARMeilleure.Instructions
ExecutionContext context = GetContext(); ExecutionContext context = GetContext();
context.CheckInterrupt(); // If debugging, we'll handle interrupts outside
if (!Optimizations.EnableDebugging)
{
context.CheckInterrupt();
}
Statistics.ResumeTimer(); Statistics.ResumeTimer();
+12 -12
View File
@@ -322,7 +322,7 @@ namespace ARMeilleure.Instructions
float result; float result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
if ((context.Fpcr & FPCR.Dn) != 0) if ((context.Fpcr & FPCR.Dn) != 0)
{ {
@@ -498,7 +498,7 @@ namespace ARMeilleure.Instructions
double result; double result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
if ((context.Fpcr & FPCR.Dn) != 0) if ((context.Fpcr & FPCR.Dn) != 0)
{ {
@@ -676,7 +676,7 @@ namespace ARMeilleure.Instructions
ushort resultBits; ushort resultBits;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
if (altHp) if (altHp)
{ {
@@ -1522,7 +1522,7 @@ namespace ARMeilleure.Instructions
float result; float result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -1689,7 +1689,7 @@ namespace ARMeilleure.Instructions
float result; float result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -1726,7 +1726,7 @@ namespace ARMeilleure.Instructions
float result; float result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -1920,7 +1920,7 @@ namespace ARMeilleure.Instructions
float result; float result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -2211,7 +2211,7 @@ namespace ARMeilleure.Instructions
ushort resultBits; ushort resultBits;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
if (altHp) if (altHp)
{ {
@@ -3057,7 +3057,7 @@ namespace ARMeilleure.Instructions
double result; double result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -3224,7 +3224,7 @@ namespace ARMeilleure.Instructions
double result; double result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -3261,7 +3261,7 @@ namespace ARMeilleure.Instructions
double result; double result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -3455,7 +3455,7 @@ namespace ARMeilleure.Instructions
double result; double result;
if (type == FPType.SNaN || type == FPType.QNaN) if (type is FPType.SNaN or FPType.QNaN)
{ {
result = FPProcessNaN(type, op, context, fpcr); result = FPProcessNaN(type, op, context, fpcr);
} }
@@ -446,7 +446,7 @@ namespace ARMeilleure.IntermediateRepresentation
Data* data = null; Data* data = null;
// If constant or register, then try to look up in the intern table before allocating. // If constant or register, then try to look up in the intern table before allocating.
if (kind == OperandKind.Constant || kind == OperandKind.Register) if (kind is OperandKind.Constant or OperandKind.Register)
{ {
uint hash = (uint)HashCode.Combine(kind, type, value); uint hash = (uint)HashCode.Combine(kind, type, value);
@@ -16,8 +16,7 @@ namespace ARMeilleure.IntermediateRepresentation
{ {
public static bool IsInteger(this OperandType type) public static bool IsInteger(this OperandType type)
{ {
return type == OperandType.I32 || return type is OperandType.I32 or OperandType.I64;
type == OperandType.I64;
} }
public static RegisterType ToRegisterType(this OperandType type) public static RegisterType ToRegisterType(this OperandType type)
+2 -2
View File
@@ -47,12 +47,12 @@ namespace ARMeilleure.Memory
{ {
public static bool IsHostMapped(this MemoryManagerType type) public static bool IsHostMapped(this MemoryManagerType type)
{ {
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; return type is MemoryManagerType.HostMapped or MemoryManagerType.HostMappedUnsafe;
} }
public static bool IsHostTracked(this MemoryManagerType type) public static bool IsHostTracked(this MemoryManagerType type)
{ {
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe; return type is MemoryManagerType.HostTracked or MemoryManagerType.HostTrackedUnsafe;
} }
public static bool IsHostMappedOrTracked(this MemoryManagerType type) public static bool IsHostMappedOrTracked(this MemoryManagerType type)
+4 -2
View File
@@ -1,7 +1,7 @@
namespace ARMeilleure namespace ARMeilleure
{ {
using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities; using Arm64HardwareCapabilities = CodeGen.Arm64.HardwareCapabilities;
using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities; using X86HardwareCapabilities = CodeGen.X86.HardwareCapabilities;
public static class Optimizations public static class Optimizations
{ {
@@ -12,6 +12,8 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { 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 UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true; public static bool UseArm64AesIfAvailable { get; set; } = true;
+39 -4
View File
@@ -1,5 +1,6 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System; using System;
using System.Threading;
namespace ARMeilleure.State namespace ARMeilleure.State
{ {
@@ -11,7 +12,7 @@ namespace ARMeilleure.State
internal IntPtr NativeContextPtr => _nativeContext.BasePtr; internal IntPtr NativeContextPtr => _nativeContext.BasePtr;
private bool _interrupted; internal bool Interrupted { get; private set; }
private readonly ICounter _counter; private readonly ICounter _counter;
@@ -68,6 +69,8 @@ namespace ARMeilleure.State
public bool IsAarch32 { get; set; } public bool IsAarch32 { get; set; }
public ulong ThreadUid { get; set; }
internal ExecutionMode ExecutionMode internal ExecutionMode ExecutionMode
{ {
get get
@@ -93,14 +96,19 @@ namespace ARMeilleure.State
private readonly ExceptionCallbackNoArgs _interruptCallback; private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback; private readonly ExceptionCallback _breakCallback;
private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback; private readonly ExceptionCallback _undefinedCallback;
internal int ShouldStep;
public ulong DebugPc { get; set; }
public ExecutionContext( public ExecutionContext(
IJitMemoryAllocator allocator, IJitMemoryAllocator allocator,
ICounter counter, ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null, ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null, ExceptionCallback breakCallback = null,
ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null, ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null) ExceptionCallback undefinedCallback = null)
{ {
@@ -108,6 +116,7 @@ namespace ARMeilleure.State
_counter = counter; _counter = counter;
_interruptCallback = interruptCallback; _interruptCallback = interruptCallback;
_breakCallback = breakCallback; _breakCallback = breakCallback;
_stepCallback = stepCallback;
_supervisorCallback = supervisorCallback; _supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback; _undefinedCallback = undefinedCallback;
@@ -128,11 +137,16 @@ namespace ARMeilleure.State
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag); public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value); public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
internal void ResetCallDepth()
{
_nativeContext.ResetCallDepth();
}
internal void CheckInterrupt() internal void CheckInterrupt()
{ {
if (_interrupted) if (Interrupted)
{ {
_interrupted = false; Interrupted = false;
_interruptCallback?.Invoke(this); _interruptCallback?.Invoke(this);
} }
@@ -142,16 +156,37 @@ namespace ARMeilleure.State
public void RequestInterrupt() public void RequestInterrupt()
{ {
_interrupted = true; Interrupted = true;
}
public void StepHandler()
{
_stepCallback?.Invoke(this);
}
public void RequestDebugStep()
{
Interlocked.Exchange(ref ShouldStep, 1);
RequestInterrupt();
} }
internal void OnBreak(ulong address, int imm) internal void OnBreak(ulong address, int imm)
{ {
if (Optimizations.EnableDebugging)
{
DebugPc = Pc;
}
_breakCallback?.Invoke(this, address, imm); _breakCallback?.Invoke(this, address, imm);
} }
internal void OnSupervisorCall(ulong address, int imm) internal void OnSupervisorCall(ulong address, int imm)
{ {
if (Optimizations.EnableDebugging)
{
DebugPc = Pc;
}
_supervisorCallback?.Invoke(this, address, imm); _supervisorCallback?.Invoke(this, address, imm);
} }
+24
View File
@@ -21,7 +21,14 @@ namespace ARMeilleure.State
public ulong ExclusiveValueLow; public ulong ExclusiveValueLow;
public ulong ExclusiveValueHigh; public ulong ExclusiveValueHigh;
public int Running; public int Running;
public int CallDepth;
public long Tpidr2El0; public long Tpidr2El0;
/// <summary>
/// Precise PC value used for debugging.
/// This will only be set when Optimizations.EnableDebugging is true.
/// </summary>
public ulong DebugPrecisePc;
} }
private static NativeCtxStorage _dummyStorage = new(); private static NativeCtxStorage _dummyStorage = new();
@@ -39,6 +46,11 @@ namespace ARMeilleure.State
public ulong GetPc() public ulong GetPc()
{ {
if (Optimizations.EnableDebugging)
{
return GetStorage().DebugPrecisePc;
}
// TODO: More precise tracking of PC value. // TODO: More precise tracking of PC value.
return GetStorage().DispatchAddress; return GetStorage().DispatchAddress;
} }
@@ -186,6 +198,8 @@ namespace ARMeilleure.State
public bool GetRunning() => GetStorage().Running != 0; public bool GetRunning() => GetStorage().Running != 0;
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
public void ResetCallDepth() => GetStorage().CallDepth = 0;
public unsafe static int GetRegisterOffset(Register reg) public unsafe static int GetRegisterOffset(Register reg)
{ {
if (reg.Type == RegisterType.Integer) if (reg.Type == RegisterType.Integer)
@@ -266,6 +280,16 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
} }
public static int GetDebugPrecisePcOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
}
public static int GetCallDepthOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
}
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target) private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
{ {
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target); return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
@@ -53,6 +53,7 @@ namespace ARMeilleure.Translation
public bool HighCq { get; } public bool HighCq { get; }
public bool HasPtc { get; } public bool HasPtc { get; }
public Aarch32Mode Mode { get; } public Aarch32Mode Mode { get; }
public bool IsSingleStep { get; }
private int _ifThenBlockStateIndex = 0; private int _ifThenBlockStateIndex = 0;
private Condition[] _ifThenBlockState = []; private Condition[] _ifThenBlockState = [];
@@ -67,7 +68,8 @@ namespace ARMeilleure.Translation
ulong entryAddress, ulong entryAddress,
bool highCq, bool highCq,
bool hasPtc, bool hasPtc,
Aarch32Mode mode) Aarch32Mode mode,
bool isSingleStep)
{ {
Memory = memory; Memory = memory;
CountTable = countTable; CountTable = countTable;
@@ -77,6 +79,7 @@ namespace ARMeilleure.Translation
HighCq = highCq; HighCq = highCq;
HasPtc = hasPtc; HasPtc = hasPtc;
Mode = mode; Mode = mode;
IsSingleStep = isSingleStep;
_labels = new Dictionary<ulong, Operand>(); _labels = new Dictionary<ulong, Operand>();
} }
+1 -1
View File
@@ -58,7 +58,7 @@ namespace ARMeilleure.Translation
private static string GetKey(MethodInfo info) private static string GetKey(MethodInfo info)
{ {
return $"{info.DeclaringType.Name}.{info.Name}"; return $"{info.DeclaringType?.Name}.{info.Name}";
} }
private static readonly SortedList<string, DelegateInfo> _delegates; private static readonly SortedList<string, DelegateInfo> _delegates;
@@ -668,8 +668,7 @@ namespace ARMeilleure.Translation
Operation last = block.Operations.Last; Operation last = block.Operations.Last;
return last != default && return last != default &&
(last.Instruction == Instruction.Return || last.Instruction is Instruction.Return or Instruction.Tailcall;
last.Instruction == Instruction.Tailcall);
} }
public ControlFlowGraph GetControlFlowGraph() public ControlFlowGraph GetControlFlowGraph()
+18 -64
View File
@@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 6998; //! 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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";
@@ -190,7 +190,7 @@ namespace ARMeilleure.Translation.PTC
_infosStream.Seek(0L, SeekOrigin.Begin); _infosStream.Seek(0L, SeekOrigin.Begin);
bool foundBadFunction = false; bool foundBadFunction = false;
for (int index = 0; index < GetEntriesCount(); index++) for (int index = 0; index < _infosStream.Length / Unsafe.SizeOf<InfoEntry>(); index++)
{ {
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream); InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
foreach (ulong address in blacklist) foreach (ulong address in blacklist)
@@ -244,56 +244,15 @@ namespace ARMeilleure.Translation.PTC
{ {
OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream); OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
if (!outerHeader.IsHeaderValid()) if (!outerHeader.IsHeaderValid() ||
{ outerHeader.Magic != _outerHeaderMagic ||
InvalidateCompressedStream(compressedStream); outerHeader.CacheFileVersion != InternalVersion ||
outerHeader.Endianness != GetEndianness() ||
return false; outerHeader.FeatureInfo != GetFeatureInfo() ||
} outerHeader.MemoryManagerMode != GetMemoryManagerMode() ||
outerHeader.OSPlatform != GetOSPlatform() ||
if (outerHeader.Magic != _outerHeaderMagic) outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture ||
{ outerHeader.DebuggerMode != Optimizations.EnableDebugging)
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.CacheFileVersion != InternalVersion)
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.Endianness != GetEndianness())
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.FeatureInfo != GetFeatureInfo())
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.MemoryManagerMode != GetMemoryManagerMode())
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.OSPlatform != GetOSPlatform())
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture)
{ {
InvalidateCompressedStream(compressedStream); InvalidateCompressedStream(compressedStream);
@@ -324,14 +283,7 @@ namespace ARMeilleure.Translation.PTC
InnerHeader innerHeader = DeserializeStructure<InnerHeader>(stream); InnerHeader innerHeader = DeserializeStructure<InnerHeader>(stream);
if (!innerHeader.IsHeaderValid()) if (!innerHeader.IsHeaderValid() || innerHeader.Magic != _innerHeaderMagic)
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (innerHeader.Magic != _innerHeaderMagic)
{ {
InvalidateCompressedStream(compressedStream); InvalidateCompressedStream(compressedStream);
@@ -476,6 +428,7 @@ namespace ARMeilleure.Translation.PTC
MemoryManagerMode = GetMemoryManagerMode(), MemoryManagerMode = GetMemoryManagerMode(),
OSPlatform = GetOSPlatform(), OSPlatform = GetOSPlatform(),
Architecture = (uint)RuntimeInformation.ProcessArchitecture, Architecture = (uint)RuntimeInformation.ProcessArchitecture,
DebuggerMode = Optimizations.EnableDebugging,
UncompressedStreamSize = UncompressedStreamSize =
(long)Unsafe.SizeOf<InnerHeader>() + (long)Unsafe.SizeOf<InnerHeader>() +
@@ -563,6 +516,7 @@ namespace ARMeilleure.Translation.PTC
{ {
if (AreCarriersEmpty() || ContainsBlacklistedFunctions()) if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
{ {
ResetCarriersIfNeeded();
return; return;
} }
@@ -869,7 +823,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); Debug.Assert(Profiler.IsAddressInStaticCodeRange(address));
TranslatedFunction func = translator.Translate(address, executionMode, highCq); TranslatedFunction func = translator.Translate(address, executionMode, highCq, pptcTranslation: true);
if (func == null) if (func == null)
{ {
@@ -1071,7 +1025,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform; return osPlatform;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)] [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
private struct OuterHeader private struct OuterHeader
{ {
public ulong Magic; public ulong Magic;
@@ -1083,6 +1037,7 @@ namespace ARMeilleure.Translation.PTC
public byte MemoryManagerMode; public byte MemoryManagerMode;
public uint OSPlatform; public uint OSPlatform;
public uint Architecture; public uint Architecture;
public bool DebuggerMode;
public long UncompressedStreamSize; public long UncompressedStreamSize;
@@ -1165,8 +1120,7 @@ namespace ARMeilleure.Translation.PTC
public void Close() public void Close()
{ {
if (State == PtcState.Enabled || if (State is PtcState.Enabled or PtcState.Continuing)
State == PtcState.Continuing)
{ {
State = PtcState.Closing; State = PtcState.Closing;
} }
+5 -24
View File
@@ -189,28 +189,10 @@ namespace ARMeilleure.Translation.PTC
{ {
OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream); OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
if (!outerHeader.IsHeaderValid()) if (!outerHeader.IsHeaderValid() ||
{ outerHeader.Magic != _outerHeaderMagic ||
InvalidateCompressedStream(compressedStream); outerHeader.InfoFileVersion != InternalVersion && !_migrateInternalVersions.Contains(outerHeader.InfoFileVersion) ||
outerHeader.Endianness != Ptc.GetEndianness())
return false;
}
if (outerHeader.Magic != _outerHeaderMagic)
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.InfoFileVersion != InternalVersion && !_migrateInternalVersions.Contains(outerHeader.InfoFileVersion))
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (outerHeader.Endianness != Ptc.GetEndianness())
{ {
InvalidateCompressedStream(compressedStream); InvalidateCompressedStream(compressedStream);
@@ -465,8 +447,7 @@ namespace ARMeilleure.Translation.PTC
public void Start() public void Start()
{ {
if (_ptc.State == PtcState.Enabled || if (_ptc.State is PtcState.Enabled or PtcState.Continuing)
_ptc.State == PtcState.Continuing)
{ {
Enabled = true; Enabled = true;
+1 -1
View File
@@ -95,7 +95,7 @@ namespace ARMeilleure.Translation
// This is required because we have a implicit context load at the start of the function, // This is required because we have a implicit context load at the start of the function,
// but if there is a jump to the start of the function, the context load would trash the modified values. // but if there is a jump to the start of the function, the context load would trash the modified values.
// Here we insert a new entry block that will jump to the existing entry block. // Here we insert a new entry block that will jump to the existing entry block.
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count); BasicBlock newEntry = new(cfg.Blocks.Count);
cfg.UpdateEntry(newEntry); cfg.UpdateEntry(newEntry);
} }
+54 -10
View File
@@ -119,7 +119,25 @@ namespace ARMeilleure.Translation
NativeInterface.RegisterThread(context, Memory, this); NativeInterface.RegisterThread(context, Memory, this);
if (Optimizations.UseUnmanagedDispatchLoop) if (Optimizations.EnableDebugging)
{
context.DebugPc = address;
do
{
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
{
context.DebugPc = Step(context, context.DebugPc);
context.StepHandler();
}
else
{
context.DebugPc = ExecuteSingle(context, context.DebugPc);
}
context.CheckInterrupt();
}
while (context.Running && context.DebugPc != 0);
}
else if (Optimizations.UseUnmanagedDispatchLoop)
{ {
Stubs.DispatchLoop(context.NativeContextPtr, address); Stubs.DispatchLoop(context.NativeContextPtr, address);
} }
@@ -168,6 +186,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer(); Statistics.StartTimer();
context.ResetCallDepth();
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context); ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address); Statistics.StopTimer(address);
@@ -175,7 +194,7 @@ namespace ARMeilleure.Translation
return nextAddr; return nextAddr;
} }
public ulong Step(State.ExecutionContext context, ulong address) private ulong Step(State.ExecutionContext context, ulong address)
{ {
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
@@ -186,6 +205,8 @@ namespace ARMeilleure.Translation
return address; return address;
} }
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{ {
if (!Functions.TryGetValue(address, out TranslatedFunction func)) if (!Functions.TryGetValue(address, out TranslatedFunction func))
@@ -219,7 +240,7 @@ namespace ARMeilleure.Translation
} }
} }
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false) internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false, bool pptcTranslation = false)
{ {
var context = new ArmEmitterContext( var context = new ArmEmitterContext(
Memory, Memory,
@@ -229,7 +250,8 @@ namespace ARMeilleure.Translation
address, address,
highCq, highCq,
_ptc.State != PtcState.Disabled, _ptc.State != PtcState.Disabled,
mode: Aarch32Mode.User); mode: Aarch32Mode.User,
isSingleStep: singleStep);
Logger.StartPass(PassName.Decoding); Logger.StartPass(PassName.Decoding);
@@ -239,6 +261,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Translation); Logger.StartPass(PassName.Translation);
InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address));
EmitSynchronization(context); EmitSynchronization(context);
if (blocks[0].Address != address) if (blocks[0].Address != address)
@@ -246,7 +269,7 @@ namespace ARMeilleure.Translation
context.Branch(context.GetLabel(address)); context.Branch(context.GetLabel(address));
} }
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter); ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter, pptcTranslation);
if (cfg == null) if (cfg == null)
{ {
@@ -326,7 +349,8 @@ namespace ARMeilleure.Translation
ArmEmitterContext context, ArmEmitterContext context,
Block[] blocks, Block[] blocks,
out Range range, out Range range,
out Counter<uint> counter) out Counter<uint> counter,
bool pptcTranslation)
{ {
counter = null; counter = null;
@@ -366,9 +390,13 @@ namespace ARMeilleure.Translation
if (block.Exit) if (block.Exit)
{ {
// Left option here as it may be useful if we need to return to managed rather than tail call in // Return to managed rather than tail call.
// future. (eg. for debug) bool useReturns = Optimizations.EnableDebugging;
bool useReturns = false;
if (Optimizations.EnableDebugging)
{
EmitDebugPrecisePcUpdate(context, block.Address);
}
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
} }
@@ -392,6 +420,11 @@ namespace ARMeilleure.Translation
} }
} }
if (Optimizations.EnableDebugging)
{
EmitDebugPrecisePcUpdate(context, opCode.Address);
}
Operand lblPredicateSkip = default; Operand lblPredicateSkip = default;
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al) if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
@@ -411,7 +444,10 @@ namespace ARMeilleure.Translation
if (opCode.Instruction.Emitter != null) if (opCode.Instruction.Emitter != null)
{ {
opCode.Instruction.Emitter(context); opCode.Instruction.Emitter(context);
if (opCode.Instruction.Name == InstName.Und && blkIndex == 0) // if we're pre-compiling PPTC functions, and we hit an Undefined instruction as the first
// instruction in the block, mark the function as blacklisted
// this way, we don't pre-compile Exlaunch hooks, which allows ExeFS mods to run with PPTC
if (pptcTranslation && opCode.Instruction.Name == InstName.Und && blkIndex == 0)
{ {
range = new Range(rangeStart, rangeEnd); range = new Range(rangeStart, rangeEnd);
return null; return null;
@@ -485,6 +521,14 @@ namespace ARMeilleure.Translation
context.MarkLabel(lblExit); context.MarkLabel(lblExit);
} }
internal static void EmitDebugPrecisePcUpdate(EmitterContext context, ulong address)
{
long debugPrecisePcOffs = NativeContext.GetDebugPrecisePcOffset();
Operand debugPrecisePcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(debugPrecisePcOffs));
context.Store(debugPrecisePcAddr, Const(address));
}
public void InvalidateJitCacheRegion(ulong address, ulong size) public void InvalidateJitCacheRegion(ulong address, ulong size)
{ {
ulong[] overlapAddresses = []; ulong[] overlapAddresses = [];
@@ -262,10 +262,18 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset())); Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
EmitSyncFpContext(context, nativeContext, true); EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl); 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.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext)); context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
context.BranchIfFalse(endLbl, guestAddress); context.BranchIfFalse(endLbl, guestAddress);
@@ -178,7 +178,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
public bool SupportsChannelCount(uint channelCount) public bool SupportsChannelCount(uint channelCount)
{ {
return channelCount == 1 || channelCount == 2 || channelCount == 6; return channelCount is 1 or 2 or 6;
} }
public bool SupportsDirection(Direction direction) public bool SupportsDirection(Direction direction)
@@ -43,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
} }
else else
{ {
_onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err); _onBackendDisconnectNative = (_, err) => _onBackendDisconnect(err);
} }
GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative); GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative);
@@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
} }
else else
{ {
_writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax); _writeCallbackNative = (_, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax);
} }
GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative); GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative);
@@ -11,15 +11,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64' OR '$(RuntimeIdentifier)' == 'win-arm64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath> <TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath> <TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' == 'linux-x64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath> <TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
@@ -64,7 +64,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
try try
{ {
context = SoundIoContext.Create(); context = SoundIoContext.Create();
context.OnBackendDisconnect = err => context.OnBackendDisconnect = _ =>
{ {
backendDisconnected = true; backendDisconnected = true;
}; };
@@ -162,7 +162,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public bool SupportsChannelCount(uint channelCount) public bool SupportsChannelCount(uint channelCount)
{ {
return channelCount == 1 || channelCount == 2 || channelCount == 6; return channelCount is 1 or 2 or 6;
} }
public bool SupportsSampleFormat(SampleFormat sampleFormat) public bool SupportsSampleFormat(SampleFormat sampleFormat)
@@ -184,7 +184,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public bool SupportsDirection(Direction direction) public bool SupportsDirection(Direction direction)
{ {
return direction == Direction.Input || direction == Direction.Output; return direction is Direction.Input or Direction.Output;
} }
} }
} }
@@ -81,12 +81,12 @@ namespace Ryujinx.Audio.Backends.Dummy
public bool SupportsDirection(Direction direction) public bool SupportsDirection(Direction direction)
{ {
return direction == Direction.Output || direction == Direction.Input; return direction is Direction.Output or Direction.Input;
} }
public bool SupportsChannelCount(uint channelCount) public bool SupportsChannelCount(uint channelCount)
{ {
return channelCount == 1 || channelCount == 2 || channelCount == 6; return channelCount is 1 or 2 or 6;
} }
} }
} }
@@ -109,7 +109,7 @@ namespace Ryujinx.Audio.Common
/// <returns>The state of the session</returns> /// <returns>The state of the session</returns>
public AudioDeviceState GetState() public AudioDeviceState GetState()
{ {
Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); Debug.Assert(_state is AudioDeviceState.Started or AudioDeviceState.Stopped);
return _state; return _state;
} }
@@ -81,14 +81,14 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index) private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{ {
if ((uint)index >= (uint)coefficients.Length) if ((uint)index < (uint)coefficients.Length)
{ {
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); return coefficients[index];
return 0;
} }
return coefficients[index]; Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -26,12 +26,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
ReadOnlySpan<float> inputBuffer, ReadOnlySpan<float> inputBuffer,
uint sampleCount) uint sampleCount)
{ {
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); Span<short> numeratorSpan = parameter.Numerator.AsSpan();
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); Span<short> denominatorSpan = parameter.Denominator.AsSpan();
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); float a0 = FixedPointHelper.ToFloat(numeratorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(numeratorSpan[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(numeratorSpan[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(denominatorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(denominatorSpan[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@@ -64,12 +67,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
uint sampleCount, uint sampleCount,
float volume) float volume)
{ {
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); Span<short> numeratorSpan = parameter.Numerator.AsSpan();
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); Span<short> denominatorSpan = parameter.Denominator.AsSpan();
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); float a0 = FixedPointHelper.ToFloat(numeratorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(numeratorSpan[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(numeratorSpan[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(denominatorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(denominatorSpan[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@@ -107,12 +113,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
float volume, float volume,
float ramp) float ramp)
{ {
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); Span<short> numeratorSpan = parameter.Numerator.AsSpan();
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); Span<short> denominatorSpan = parameter.Denominator.AsSpan();
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); float a0 = FixedPointHelper.ToFloat(numeratorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(numeratorSpan[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(numeratorSpan[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(denominatorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(denominatorSpan[1], FixedPointPrecisionForParameter);
float mixState = 0f; float mixState = 0f;
@@ -158,12 +167,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
ref BiquadFilterState state = ref states[stageIndex]; ref BiquadFilterState state = ref states[stageIndex];
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); Span<short> numeratorSpan = parameter.Numerator.AsSpan();
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); Span<short> denominatorSpan = parameter.Denominator.AsSpan();
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); float a0 = FixedPointHelper.ToFloat(numeratorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(numeratorSpan[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(numeratorSpan[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(denominatorSpan[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(denominatorSpan[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@@ -201,19 +213,25 @@ namespace Ryujinx.Audio.Renderer.Dsp
uint sampleCount, uint sampleCount,
float volume) float volume)
{ {
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); Span<short> numerator0Span = parameter0.Numerator.AsSpan();
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); Span<short> numerator1Span = parameter1.Numerator.AsSpan();
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); Span<short> denominator0Span = parameter0.Denominator.AsSpan();
Span<short> denominator1Span = parameter1.Denominator.AsSpan();
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); float a00 = FixedPointHelper.ToFloat(numerator0Span[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); float a10 = FixedPointHelper.ToFloat(numerator0Span[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter); float a20 = FixedPointHelper.ToFloat(numerator0Span[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); float b10 = FixedPointHelper.ToFloat(denominator0Span[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); float b20 = FixedPointHelper.ToFloat(denominator0Span[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(numerator1Span[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(numerator1Span[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(numerator1Span[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(denominator1Span[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(denominator1Span[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@@ -261,19 +279,24 @@ namespace Ryujinx.Audio.Renderer.Dsp
float volume, float volume,
float ramp) float ramp)
{ {
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); Span<short> numerator0Span = parameter0.Numerator.AsSpan();
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); Span<short> numerator1Span = parameter1.Numerator.AsSpan();
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); Span<short> denominator0Span = parameter0.Denominator.AsSpan();
Span<short> denominator1Span = parameter1.Denominator.AsSpan();
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter); float a00 = FixedPointHelper.ToFloat(numerator0Span[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter); float a10 = FixedPointHelper.ToFloat(numerator0Span[1], FixedPointPrecisionForParameter);
float a20 = FixedPointHelper.ToFloat(numerator0Span[2], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); float b10 = FixedPointHelper.ToFloat(denominator0Span[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); float b20 = FixedPointHelper.ToFloat(denominator0Span[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); float a01 = FixedPointHelper.ToFloat(numerator1Span[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); float a11 = FixedPointHelper.ToFloat(numerator1Span[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(numerator1Span[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(denominator1Span[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(denominator1Span[1], FixedPointPrecisionForParameter);
float mixState = 0f; float mixState = 0f;
@@ -40,11 +40,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
SampleRate = serverState.SampleRate; SampleRate = serverState.SampleRate;
Pitch = serverState.Pitch; Pitch = serverState.Pitch;
Span<Server.Voice.WaveBuffer> waveBufferSpan = serverState.WaveBuffers.AsSpan();
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
for (int i = 0; i < WaveBuffers.Length; i++) for (int i = 0; i < WaveBuffers.Length; i++)
{ {
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; ref Server.Voice.WaveBuffer voiceWaveBuffer = ref waveBufferSpan[i];
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
} }
@@ -1,5 +1,6 @@
using Ryujinx.Audio.Renderer.Parameter.Sink; using Ryujinx.Audio.Renderer.Parameter.Sink;
using Ryujinx.Audio.Renderer.Server.MemoryPool; using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics; using System.Diagnostics;
namespace Ryujinx.Audio.Renderer.Dsp.Command namespace Ryujinx.Audio.Renderer.Dsp.Command
@@ -29,9 +30,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Input = new ushort[Constants.ChannelCountMax]; Input = new ushort[Constants.ChannelCountMax];
InputCount = parameter.InputCount; InputCount = parameter.InputCount;
Span<byte> inputSpan = parameter.Input.AsSpan();
for (int i = 0; i < InputCount; i++) for (int i = 0; i < InputCount; i++)
{ {
Input[i] = (ushort)(bufferOffset + parameter.Input[i]); Input[i] = (ushort)(bufferOffset + inputSpan[i]);
} }
CircularBuffer = circularBufferAddressInfo.GetReference(true); CircularBuffer = circularBufferAddressInfo.GetReference(true);
@@ -59,7 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
for (int y = 0; y < context.SampleCount; y++) for (int y = 0; y < context.SampleCount; y++)
{ {
context.MemoryManager.Write(targetOffset + (ulong)y * TargetChannelCount, PcmHelper.Saturate(inputBuffer[y])); if (inputBuffer != null)
{
context.MemoryManager.Write(targetOffset + (ulong)y * TargetChannelCount,
PcmHelper.Saturate(inputBuffer[y]));
}
} }
currentOffset += context.SampleCount * TargetChannelCount; currentOffset += context.SampleCount * TargetChannelCount;
@@ -43,10 +43,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = _parameter.Input.AsSpan();
Span<byte> outputSpan = _parameter.Output.AsSpan();
for (int i = 0; i < _parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
} }
@@ -172,9 +175,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain); statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean); statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);
Span<float> lastSamplesSpan = statistics.LastSamples.AsSpan();
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++) for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{ {
statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f)); lastSamplesSpan[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
} }
} }
} }
@@ -53,11 +53,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
SampleRate = serverState.SampleRate; SampleRate = serverState.SampleRate;
Pitch = serverState.Pitch; Pitch = serverState.Pitch;
Span<Server.Voice.WaveBuffer> waveBufferSpan = serverState.WaveBuffers.AsSpan();
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
for (int i = 0; i < WaveBuffers.Length; i++) for (int i = 0; i < WaveBuffers.Length; i++)
{ {
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; ref Server.Voice.WaveBuffer voiceWaveBuffer = ref waveBufferSpan[i];
WaveBuffers[i] = voiceWaveBuffer.ToCommon(2); WaveBuffers[i] = voiceWaveBuffer.ToCommon(2);
} }
@@ -43,10 +43,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = Parameter.Input.AsSpan();
Span<byte> outputSpan = Parameter.Output.AsSpan();
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < Parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices, Parameter.ChannelCount); DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices, Parameter.ChannelCount);
@@ -42,14 +42,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref VoiceUpdateState state = ref State.Span[0]; ref VoiceUpdateState state = ref State.Span[0];
Span<float> depopBuffer = DepopBuffer.Span; Span<float> depopBuffer = DepopBuffer.Span;
Span<float> lastSamplesSpan = state.LastSamples.AsSpan();
for (int i = 0; i < MixBufferCount; i++) for (int i = 0; i < MixBufferCount; i++)
{ {
if (state.LastSamples[i] != 0) if (lastSamplesSpan[i] != 0)
{ {
depopBuffer[OutputBufferIndices[i]] += state.LastSamples[i]; depopBuffer[OutputBufferIndices[i]] += lastSamplesSpan[i];
state.LastSamples[i] = 0; lastSamplesSpan[i] = 0;
} }
} }
} }
@@ -35,9 +35,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputCount = sink.Parameter.InputCount; InputCount = sink.Parameter.InputCount;
InputBufferIndices = new ushort[InputCount]; InputBufferIndices = new ushort[InputCount];
Span<byte> inputSpan = sink.Parameter.Input.AsSpan();
for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++) for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
} }
if (sink.UpsamplerState != null) if (sink.UpsamplerState != null)
@@ -38,10 +38,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = _parameter.Input.AsSpan();
Span<byte> outputSpan = _parameter.Output.AsSpan();
for (int i = 0; i < _parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
} }
@@ -49,10 +49,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = _parameter.Input.AsSpan();
Span<byte> outputSpan = _parameter.Output.AsSpan();
for (int i = 0; i < _parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
} }
@@ -150,8 +153,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
{ {
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0]; ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax); Span<float> inputMaxSpan = statistics.InputMax.AsSpan();
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain); Span<float> compressionGainMinSpan = statistics.CompressionGainMin.AsSpan();
inputMaxSpan[channelIndex] = Math.Max(inputMaxSpan[channelIndex], sampleInputMax);
compressionGainMinSpan[channelIndex] = Math.Min(compressionGainMinSpan[channelIndex], compressionGain);
} }
} }
} }
@@ -79,6 +79,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public void Process(CommandList context) public void Process(CommandList context)
{ {
ref VoiceUpdateState state = ref State.Span[0];
Span<float> lastSamplesSpan = state.LastSamples.AsSpan();
for (int i = 0; i < MixBufferCount; i++) for (int i = 0; i < MixBufferCount; i++)
{ {
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndices[i]); ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndices[i]);
@@ -87,15 +91,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float volume0 = Volume0[i]; float volume0 = Volume0[i];
float volume1 = Volume1[i]; float volume1 = Volume1[i];
ref VoiceUpdateState state = ref State.Span[0];
if (volume0 != 0 || volume1 != 0) if (volume0 != 0 || volume1 != 0)
{ {
state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount); lastSamplesSpan[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount);
} }
else else
{ {
state.LastSamples[i] = 0; lastSamplesSpan[i] = 0;
} }
} }
} }
@@ -43,9 +43,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
Span<Server.Voice.WaveBuffer> waveBufferSpan = serverState.WaveBuffers.AsSpan();
for (int i = 0; i < WaveBuffers.Length; i++) for (int i = 0; i < WaveBuffers.Length; i++)
{ {
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; ref Server.Voice.WaveBuffer voiceWaveBuffer = ref waveBufferSpan[i];
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
} }
@@ -43,9 +43,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
Span<Server.Voice.WaveBuffer> waveBufferSpan = serverState.WaveBuffers.AsSpan();
for (int i = 0; i < WaveBuffers.Length; i++) for (int i = 0; i < WaveBuffers.Length; i++)
{ {
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; ref Server.Voice.WaveBuffer voiceWaveBuffer = ref waveBufferSpan[i];
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
} }
@@ -66,10 +66,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = Parameter.Input.AsSpan();
Span<byte> outputSpan = Parameter.Output.AsSpan();
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < Parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
@@ -64,10 +64,13 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
Span<byte> inputSpan = Parameter.Input.AsSpan();
Span<byte> outputSpan = Parameter.Output.AsSpan();
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < Parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + inputSpan[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputSpan[i]);
} }
IsLongSizePreDelaySupported = isLongSizePreDelaySupported; IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
@@ -74,7 +74,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
{ {
int tempBufferIndex = 0; int tempBufferIndex = 0;
if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) if ((info.DecodingBehaviour & DecodingBehaviour.SkipPitchAndSampleRateConversion) != DecodingBehaviour.SkipPitchAndSampleRateConversion)
{ {
voiceState.Pitch.AsSpan()[..pitchMaxLength].CopyTo(tempBuffer); voiceState.Pitch.AsSpan()[..pitchMaxLength].CopyTo(tempBuffer);
tempBufferIndex += pitchMaxLength; tempBufferIndex += pitchMaxLength;
@@ -208,7 +208,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
break; break;
} }
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping)) if ((info.DecodingBehaviour & DecodingBehaviour.PlayedSampleCountResetWhenLooping) == DecodingBehaviour.PlayedSampleCountResetWhenLooping)
{ {
playedSampleCount = 0; playedSampleCount = 0;
} }
@@ -222,7 +222,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputBuffer[i..]); Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputBuffer[i..]);
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) if ((info.DecodingBehaviour & DecodingBehaviour.SkipPitchAndSampleRateConversion) == DecodingBehaviour.SkipPitchAndSampleRateConversion)
{ {
for (int j = 0; j < y; j++) for (int j = 0; j < y; j++)
{ {
@@ -75,7 +75,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// Map decibel to linear. /// Map decibel to linear.
/// </summary> /// </summary>
/// <param name="db">The decibel value to convert</param> /// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value/returns> /// <returns>Converted linear value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinear(float db) public static float DecibelToLinear(float db)
{ {
@@ -40,7 +40,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain; DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain;
if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6) if (parameter.ChannelCount is 4 or 6)
{ {
DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain; DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain;
} }
@@ -40,11 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
} }
Array20<float> result = new(); Array20<float> result = new();
Span<float> resultSpan = result.AsSpan();
for (int i = 0; i < FilterBankLength; i++) for (int i = 0; i < FilterBankLength; i++)
{ {
float x = (Bank0CenterIndex - i) + offset; float x = (Bank0CenterIndex - i) + offset;
result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f); resultSpan[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f);
} }
return result; return result;
@@ -78,6 +79,9 @@ namespace Ryujinx.Audio.Renderer.Dsp
Debug.Assert(state.History.Length == HistoryLength); Debug.Assert(state.History.Length == HistoryLength);
Debug.Assert(bank.Length == FilterBankLength); Debug.Assert(bank.Length == FilterBankLength);
Span<float> bankSpan = bank.AsSpan();
Span<float> historySpan = state.History.AsSpan();
int curIdx = 0; int curIdx = 0;
if (Vector.IsHardwareAccelerated) if (Vector.IsHardwareAccelerated)
{ {
@@ -87,15 +91,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
while (curIdx < stopIdx) while (curIdx < stopIdx)
{ {
result += Vector.Dot( result += Vector.Dot(
new Vector<float>(bank.AsSpan().Slice(curIdx, Vector<float>.Count)), new Vector<float>(bankSpan[curIdx..(curIdx + Vector<float>.Count)]),
new Vector<float>(state.History.AsSpan().Slice(curIdx, Vector<float>.Count))); new Vector<float>(historySpan[curIdx..(curIdx + Vector<float>.Count)]));
curIdx += Vector<float>.Count; curIdx += Vector<float>.Count;
} }
} }
while (curIdx < FilterBankLength) while (curIdx < FilterBankLength)
{ {
result += bank[curIdx] * state.History[curIdx]; result += bankSpan[curIdx] * historySpan[curIdx];
curIdx++; curIdx++;
} }
@@ -91,7 +91,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// <returns>Returns true if the channel count is valid.</returns> /// <returns>Returns true if the channel count is valid.</returns>
public static bool IsChannelCountValid(int channelCount) public static bool IsChannelCountValid(int channelCount)
{ {
return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; return channelCount is 1 or 2 or 4 or 6;
} }
} }
} }
@@ -91,7 +91,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// <returns>Returns true if the channel count is valid.</returns> /// <returns>Returns true if the channel count is valid.</returns>
public static bool IsChannelCountValid(int channelCount) public static bool IsChannelCountValid(int channelCount)
{ {
return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; return channelCount is 1 or 2 or 4 or 6;
} }
} }
} }
@@ -436,7 +436,7 @@ namespace Ryujinx.Audio.Renderer.Server
return result; return result;
} }
PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); PoolMapper poolMapper = new(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper); result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
@@ -532,13 +532,13 @@ namespace Ryujinx.Audio.Renderer.Server
CommandType commandType = command.CommandType; CommandType commandType = command.CommandType;
if (commandType == CommandType.AdpcmDataSourceVersion1 || if (commandType is CommandType.AdpcmDataSourceVersion1
commandType == CommandType.AdpcmDataSourceVersion2 || or CommandType.AdpcmDataSourceVersion2
commandType == CommandType.PcmInt16DataSourceVersion1 || or CommandType.PcmInt16DataSourceVersion1
commandType == CommandType.PcmInt16DataSourceVersion2 || or CommandType.PcmInt16DataSourceVersion2
commandType == CommandType.PcmFloatDataSourceVersion1 || or CommandType.PcmFloatDataSourceVersion1
commandType == CommandType.PcmFloatDataSourceVersion2 || or CommandType.PcmFloatDataSourceVersion2
commandType == CommandType.Performance) or CommandType.Performance)
{ {
break; break;
} }
@@ -141,18 +141,20 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing(); bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) Span<BiquadFilterParameter> biquadFiltersSpan = voiceState.BiquadFilters.AsSpan();
if (supportsOptimizedPath && biquadFiltersSpan[0].Enable && biquadFiltersSpan[1].Enable)
{ {
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)]; Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory); Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
_commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, biquadFiltersSpan, stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
} }
else else
{ {
for (int i = 0; i < voiceState.BiquadFilters.Length; i++) for (int i = 0; i < biquadFiltersSpan.Length; i++)
{ {
ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i]; ref BiquadFilterParameter filter = ref biquadFiltersSpan[i];
if (filter.Enable) if (filter.Enable)
{ {
@@ -312,11 +314,14 @@ namespace Ryujinx.Audio.Renderer.Server
int nodeId = voiceState.NodeId; int nodeId = voiceState.NodeId;
uint channelsCount = voiceState.ChannelsCount; uint channelsCount = voiceState.ChannelsCount;
Span<int> channelResourceIdsSpan = voiceState.ChannelResourceIds.AsSpan();
Span<BiquadFilterParameter> biquadFiltersSpan = voiceState.BiquadFilters.AsSpan();
for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++)
{ {
Memory<VoiceUpdateState> dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]); Memory<VoiceUpdateState> dspStateMemory = _voiceContext.GetUpdateStateForDsp(channelResourceIdsSpan[channelIndex]);
ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]); ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(channelResourceIdsSpan[channelIndex]);
PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm; PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm;
@@ -476,7 +481,7 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++) for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++)
{ {
voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable; voiceState.BiquadFilterNeedInitialization[i] = biquadFiltersSpan[i].Enable;
} }
} }
} }
@@ -526,15 +531,19 @@ namespace Ryujinx.Audio.Renderer.Server
if (effect.IsEnabled) if (effect.IsEnabled)
{ {
Span<float> volumesSpan = effect.Parameter.Volumes.AsSpan();
Span<byte> inputSpan = effect.Parameter.Input.AsSpan();
Span<byte> outputSpan = effect.Parameter.Output.AsSpan();
for (int i = 0; i < effect.Parameter.MixesCount; i++) for (int i = 0; i < effect.Parameter.MixesCount; i++)
{ {
if (effect.Parameter.Volumes[i] != 0.0f) if (volumesSpan[i] != 0.0f)
{ {
_commandBuffer.GenerateMix( _commandBuffer.GenerateMix(
(uint)bufferOffset + effect.Parameter.Input[i], (uint)bufferOffset + inputSpan[i],
(uint)bufferOffset + effect.Parameter.Output[i], (uint)bufferOffset + outputSpan[i],
nodeId, nodeId,
effect.Parameter.Volumes[i]); volumesSpan[i]);
} }
} }
} }
@@ -554,6 +563,10 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
int i = 0; int i = 0;
uint writeOffset = 0; uint writeOffset = 0;
Span<byte> inputSpan = effect.Parameter.Input.AsSpan();
Span<byte> outputSpan = effect.Parameter.Output.AsSpan();
for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
{ {
uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
@@ -571,8 +584,8 @@ namespace Ryujinx.Audio.Renderer.Server
_commandBuffer.GenerateAuxEffect( _commandBuffer.GenerateAuxEffect(
bufferOffset, bufferOffset,
effect.Parameter.Input[i], inputSpan[i],
effect.Parameter.Output[i], outputSpan[i],
ref effect.State, ref effect.State,
effect.IsEnabled, effect.IsEnabled,
effect.Parameter.BufferStorageSize, effect.Parameter.BufferStorageSize,
@@ -620,6 +633,9 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
Debug.Assert(effect.Type == EffectType.BiquadFilter); Debug.Assert(effect.Type == EffectType.BiquadFilter);
Span<byte> inputSpan = effect.Parameter.Input.AsSpan();
Span<byte> outputSpan = effect.Parameter.Output.AsSpan();
if (effect.IsEnabled) if (effect.IsEnabled)
{ {
bool needInitialization = effect.Parameter.Status == UsageState.Invalid || bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
@@ -639,8 +655,8 @@ namespace Ryujinx.Audio.Renderer.Server
(int)bufferOffset, (int)bufferOffset,
ref parameter, ref parameter,
effect.State.Slice(i, 1), effect.State.Slice(i, 1),
effect.Parameter.Input[i], inputSpan[i],
effect.Parameter.Output[i], outputSpan[i],
needInitialization, needInitialization,
nodeId); nodeId);
} }
@@ -649,8 +665,8 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
for (int i = 0; i < effect.Parameter.ChannelCount; i++) for (int i = 0; i < effect.Parameter.ChannelCount; i++)
{ {
uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i]; uint inputBufferIndex = bufferOffset + inputSpan[i];
uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i]; uint outputBufferIndex = bufferOffset + outputSpan[i];
// If the input and output isn't the same, generate a command. // If the input and output isn't the same, generate a command.
if (inputBufferIndex != outputBufferIndex) if (inputBufferIndex != outputBufferIndex)
@@ -702,6 +718,8 @@ namespace Ryujinx.Audio.Renderer.Server
int i = 0; int i = 0;
uint writeOffset = 0; uint writeOffset = 0;
Span<byte> inputSpan = effect.Parameter.Input.AsSpan();
for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
{ {
uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
@@ -719,7 +737,7 @@ namespace Ryujinx.Audio.Renderer.Server
_commandBuffer.GenerateCaptureEffect( _commandBuffer.GenerateCaptureEffect(
bufferOffset, bufferOffset,
effect.Parameter.Input[i], inputSpan[i],
effect.State.SendBufferInfo, effect.State.SendBufferInfo,
effect.IsEnabled, effect.IsEnabled,
effect.Parameter.BufferStorageSize, effect.Parameter.BufferStorageSize,
@@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PerformanceCommand command) public uint Estimate(PerformanceCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -32,7 +32,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(ClearMixBufferCommand command) public uint Estimate(ClearMixBufferCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
float costPerBuffer = 668.8f; float costPerBuffer = 668.8f;
float baseCost = 193.2f; float baseCost = 193.2f;
@@ -48,7 +48,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(BiquadFilterCommand command) public uint Estimate(BiquadFilterCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
const float CostPerSample = 7.245f; const float CostPerSample = 7.245f;
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
int volumeCount = 0; int volumeCount = 0;
@@ -79,7 +79,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(MixRampCommand command) public uint Estimate(MixRampCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -91,7 +91,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DepopPrepareCommand command) public uint Estimate(DepopPrepareCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(VolumeRampCommand command) public uint Estimate(VolumeRampCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -115,7 +115,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PcmInt16DataSourceCommandVersion1 command) public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
float costPerSample = 1195.5f; float costPerSample = 1195.5f;
float baseCost = 7797.0f; float baseCost = 7797.0f;
@@ -131,7 +131,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(AdpcmDataSourceCommandVersion1 command) public uint Estimate(AdpcmDataSourceCommandVersion1 command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
float costPerSample = 3564.1f; float costPerSample = 3564.1f;
float baseCost = 6225.5f; float baseCost = 6225.5f;
@@ -147,7 +147,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DepopForMixBuffersCommand command) public uint Estimate(DepopForMixBuffersCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -159,7 +159,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(CopyMixBufferCommand command) public uint Estimate(CopyMixBufferCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -171,7 +171,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(MixCommand command) public uint Estimate(MixCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -183,7 +183,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DelayCommand command) public uint Estimate(DelayCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -234,7 +234,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(ReverbCommand command) public uint Estimate(ReverbCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -285,7 +285,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(Reverb3dCommand command) public uint Estimate(Reverb3dCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -335,7 +335,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(AuxiliaryBufferCommand command) public uint Estimate(AuxiliaryBufferCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -357,7 +357,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(VolumeCommand command) public uint Estimate(VolumeCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -369,7 +369,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(CircularBufferSinkCommand command) public uint Estimate(CircularBufferSinkCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
float costPerBuffer = 1726.0f; float costPerBuffer = 1726.0f;
float baseCost = 1369.7f; float baseCost = 1369.7f;
@@ -385,7 +385,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DownMixSurroundToStereoCommand command) public uint Estimate(DownMixSurroundToStereoCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -397,7 +397,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(UpsampleCommand command) public uint Estimate(UpsampleCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
if (_sampleCount == 160) if (_sampleCount == 160)
{ {
@@ -409,8 +409,8 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DeviceSinkCommand command) public uint Estimate(DeviceSinkCommand command)
{ {
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
Debug.Assert(command.InputCount == 2 || command.InputCount == 6); Debug.Assert(command.InputCount is 2 or 6);
if (command.InputCount == 2) if (command.InputCount == 2)
{ {
@@ -433,7 +433,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PcmFloatDataSourceCommandVersion1 command) public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
{ {
// NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed... // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed...
Debug.Assert(_sampleCount == 160 || _sampleCount == 240); Debug.Assert(_sampleCount is 160 or 240);
float costPerSample = 3490.9f; float costPerSample = 3490.9f;
float baseCost = 10091.0f; float baseCost = 10091.0f;
@@ -23,7 +23,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PerformanceCommand command) public uint Estimate(PerformanceCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -35,7 +35,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(ClearMixBufferCommand command) public uint Estimate(ClearMixBufferCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
float costPerBuffer = 440.68f; float costPerBuffer = 440.68f;
float baseCost = 0; float baseCost = 0;
@@ -50,7 +50,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(BiquadFilterCommand command) public uint Estimate(BiquadFilterCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -64,7 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
float costPerSample = 6.4434f; float costPerSample = 6.4434f;
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -86,7 +86,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(MixRampCommand command) public uint Estimate(MixRampCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(VolumeRampCommand command) public uint Estimate(VolumeRampCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -115,7 +115,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PcmInt16DataSourceCommandVersion1 command) public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
float costPerSample = 710.143f; float costPerSample = 710.143f;
float baseCost = 7853.286f; float baseCost = 7853.286f;
@@ -131,7 +131,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(AdpcmDataSourceCommandVersion1 command) public uint Estimate(AdpcmDataSourceCommandVersion1 command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
float costPerSample = 3564.1f; float costPerSample = 3564.1f;
float baseCost = 9736.702f; float baseCost = 9736.702f;
@@ -147,7 +147,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DepopForMixBuffersCommand command) public uint Estimate(DepopForMixBuffersCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -159,7 +159,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(CopyMixBufferCommand command) public uint Estimate(CopyMixBufferCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -171,7 +171,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(MixCommand command) public uint Estimate(MixCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -183,7 +183,7 @@ namespace Ryujinx.Audio.Renderer.Server
public virtual uint Estimate(DelayCommand command) public virtual uint Estimate(DelayCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -233,7 +233,7 @@ namespace Ryujinx.Audio.Renderer.Server
public virtual uint Estimate(ReverbCommand command) public virtual uint Estimate(ReverbCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -283,7 +283,7 @@ namespace Ryujinx.Audio.Renderer.Server
public virtual uint Estimate(Reverb3dCommand command) public virtual uint Estimate(Reverb3dCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -333,7 +333,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(AuxiliaryBufferCommand command) public uint Estimate(AuxiliaryBufferCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -355,7 +355,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(VolumeCommand command) public uint Estimate(VolumeCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -367,7 +367,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(CircularBufferSinkCommand command) public uint Estimate(CircularBufferSinkCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
float costPerBuffer = 770.26f; float costPerBuffer = 770.26f;
float baseCost = 0f; float baseCost = 0f;
@@ -382,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DownMixSurroundToStereoCommand command) public uint Estimate(DownMixSurroundToStereoCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -394,7 +394,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(UpsampleCommand command) public uint Estimate(UpsampleCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -406,8 +406,8 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DeviceSinkCommand command) public uint Estimate(DeviceSinkCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
Debug.Assert(command.InputCount == 2 || command.InputCount == 6); Debug.Assert(command.InputCount is 2 or 6);
if (command.InputCount == 2) if (command.InputCount == 2)
{ {
@@ -429,7 +429,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(PcmFloatDataSourceCommandVersion1 command) public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
float costPerSample = 3490.9f; float costPerSample = 3490.9f;
float baseCost = 10090.9f; float baseCost = 10090.9f;
@@ -445,7 +445,7 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(DataSourceVersion2Command command) public uint Estimate(DataSourceVersion2Command command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
(float baseCost, float costPerSample) = GetCostByFormat(SampleCount, command.SampleFormat, command.SrcQuality); (float baseCost, float costPerSample) = GetCostByFormat(SampleCount, command.SampleFormat, command.SrcQuality);
@@ -454,7 +454,7 @@ namespace Ryujinx.Audio.Renderer.Server
private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality) private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality)
{ {
Debug.Assert(sampleCount == 160 || sampleCount == 240); Debug.Assert(sampleCount is 160 or 240);
switch (format) switch (format)
{ {
@@ -496,12 +496,6 @@ namespace Ryujinx.Audio.Renderer.Server
return (10090.9f, 3490.9f); return (10090.9f, 3490.9f);
case SampleRateConversionQuality.High: case SampleRateConversionQuality.High:
if (sampleCount == 160)
{
return (9446.36f, 2308.91f);
}
return (12520.85f, 3480.61f);
case SampleRateConversionQuality.Low: case SampleRateConversionQuality.Low:
if (sampleCount == 160) if (sampleCount == 160)
{ {
@@ -546,7 +540,7 @@ namespace Ryujinx.Audio.Renderer.Server
private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled) private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -596,14 +590,14 @@ namespace Ryujinx.Audio.Renderer.Server
public uint Estimate(LimiterCommandVersion1 command) public uint Estimate(LimiterCommandVersion1 command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled);
} }
public uint Estimate(LimiterCommandVersion2 command) public uint Estimate(LimiterCommandVersion2 command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled) if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled)
{ {
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(MultiTapBiquadFilterCommand command) public override uint Estimate(MultiTapBiquadFilterCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(CaptureBufferCommand command) public override uint Estimate(CaptureBufferCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(DelayCommand command) public override uint Estimate(DelayCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -63,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(ReverbCommand command) public override uint Estimate(ReverbCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -113,7 +113,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(Reverb3dCommand command) public override uint Estimate(Reverb3dCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -163,7 +163,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(CompressorCommand command) public override uint Estimate(CompressorCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (SampleCount == 160) if (SampleCount == 160)
{ {
@@ -241,7 +241,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(BiquadFilterAndMixCommand command) public override uint Estimate(BiquadFilterAndMixCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (command.HasVolumeRamp) if (command.HasVolumeRamp)
{ {
@@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
public override uint Estimate(MultiTapBiquadFilterAndMixCommand command) public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount is 160 or 240);
if (command.HasVolumeRamp) if (command.HasVolumeRamp)
{ {
@@ -262,12 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
return UpdateResult.Success; return UpdateResult.Success;
} }
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % PageSize) != 0) if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % PageSize) != 0 ||
{ inParameter.Size == 0 || (inParameter.Size % PageSize) != 0)
return UpdateResult.InvalidParameter;
}
if (inParameter.Size == 0 || (inParameter.Size % PageSize) != 0)
{ {
return UpdateResult.InvalidParameter; return UpdateResult.InvalidParameter;
} }
@@ -218,7 +218,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// <returns>True if any biquad filter is enabled.</returns> /// <returns>True if any biquad filter is enabled.</returns>
public bool IsBiquadFilterEnabled() public bool IsBiquadFilterEnabled()
{ {
return _biquadFilters[0].Enable || _biquadFilters[1].Enable; Span<BiquadFilterParameter> biquadFiltersSpan = _biquadFilters.AsSpan();
return biquadFiltersSpan[0].Enable || biquadFiltersSpan[1].Enable;
} }
/// <summary> /// <summary>
@@ -162,9 +162,11 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
ref VoiceState currentVoiceState = ref context.GetState(i); ref VoiceState currentVoiceState = ref context.GetState(i);
Span<int> channelResourceIdsSpan = parameter.ChannelResourceIds.AsSpan();
for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++) for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++)
{ {
int channelId = parameter.ChannelResourceIds[channelResourceIndex]; int channelId = channelResourceIdsSpan[channelResourceIndex];
Debug.Assert(channelId >= 0 && channelId < context.GetCount()); Debug.Assert(channelId >= 0 && channelId < context.GetCount());
@@ -126,9 +126,9 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
_sortedVoices.Span[i] = i; _sortedVoices.Span[i] = i;
} }
int[] sortedVoicesTemp = _sortedVoices[..(int)GetCount()].ToArray(); Span<int> sortedVoicesTemp = _sortedVoices[..(int)_voiceCount].Span;
Array.Sort(sortedVoicesTemp, (a, b) => sortedVoicesTemp.Sort((a, b) =>
{ {
ref VoiceState aState = ref GetState(a); ref VoiceState aState = ref GetState(a);
ref VoiceState bState = ref GetState(b); ref VoiceState bState = ref GetState(b);
@@ -143,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
return result; return result;
}); });
sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); // sortedVoicesTemp.CopyTo(_sortedVoices.Span);
} }
} }
} }
@@ -219,15 +219,17 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// </summary> /// </summary>
private void InitializeWaveBuffers() private void InitializeWaveBuffers()
{ {
for (int i = 0; i < WaveBuffers.Length; i++) Span<WaveBuffer> waveBuffersSpan = WaveBuffers.AsSpan();
for (int i = 0; i < waveBuffersSpan.Length; i++)
{ {
WaveBuffers[i].StartSampleOffset = 0; waveBuffersSpan[i].StartSampleOffset = 0;
WaveBuffers[i].EndSampleOffset = 0; waveBuffersSpan[i].EndSampleOffset = 0;
WaveBuffers[i].ShouldLoop = false; waveBuffersSpan[i].ShouldLoop = false;
WaveBuffers[i].IsEndOfStream = false; waveBuffersSpan[i].IsEndOfStream = false;
WaveBuffers[i].BufferAddressInfo.Setup(0, 0); waveBuffersSpan[i].BufferAddressInfo.Setup(0, 0);
WaveBuffers[i].ContextAddressInfo.Setup(0, 0); waveBuffersSpan[i].ContextAddressInfo.Setup(0, 0);
WaveBuffers[i].IsSendToAudioProcessor = true; waveBuffersSpan[i].IsSendToAudioProcessor = true;
} }
} }
@@ -447,9 +449,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0];
Span<WaveBuffer> waveBuffersSpan = WaveBuffers.AsSpan();
Span<WaveBufferInternal> pWaveBuffersSpan = parameter.WaveBuffers.AsSpan();
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{ {
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext); UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext);
} }
} }
@@ -534,9 +539,11 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="context">The voice context.</param> /// <param name="context">The voice context.</param>
private void ResetResources(VoiceContext context) private void ResetResources(VoiceContext context)
{ {
Span<int> channelResourceIdsSpan = ChannelResourceIds.AsSpan();
for (int i = 0; i < ChannelsCount; i++) for (int i = 0; i < ChannelsCount; i++)
{ {
int channelResourceId = ChannelResourceIds[i]; int channelResourceId = channelResourceIdsSpan[i];
ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId);
@@ -560,9 +567,11 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
{ {
uint waveBufferIndex = WaveBuffersIndex; uint waveBufferIndex = WaveBuffersIndex;
Span<WaveBuffer> waveBuffersSpan = WaveBuffers.AsSpan();
for (int i = 0; i < waveBufferCount; i++) for (int i = 0; i < waveBufferCount; i++)
{ {
WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true; waveBuffersSpan[(int)waveBufferIndex].IsSendToAudioProcessor = true;
for (int j = 0; j < channelCount; j++) for (int j = 0; j < channelCount; j++)
{ {
@@ -591,14 +600,18 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
FlushWaveBufferCount = 0; FlushWaveBufferCount = 0;
} }
Span<WaveBuffer> waveBuffersSpan;
switch (PlayState) switch (PlayState)
{ {
case PlayState.Started: case PlayState.Started:
for (int i = 0; i < WaveBuffers.Length; i++) waveBuffersSpan = WaveBuffers.AsSpan();
{
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
if (!wavebuffer.IsSendToAudioProcessor) for (int i = 0; i < waveBuffersSpan.Length; i++)
{
ref WaveBuffer waveBuffer = ref waveBuffersSpan[i];
if (!waveBuffer.IsSendToAudioProcessor)
{ {
for (int y = 0; y < ChannelsCount; y++) for (int y = 0; y < ChannelsCount; y++)
{ {
@@ -607,7 +620,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true; voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true;
} }
wavebuffer.IsSendToAudioProcessor = true; waveBuffer.IsSendToAudioProcessor = true;
} }
} }
@@ -626,11 +639,13 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
return false; return false;
case PlayState.Stopping: case PlayState.Stopping:
for (int i = 0; i < WaveBuffers.Length; i++) waveBuffersSpan = WaveBuffers.AsSpan();
{
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
wavebuffer.IsSendToAudioProcessor = true; for (int i = 0; i < waveBuffersSpan.Length; i++)
{
ref WaveBuffer waveBuffer = ref waveBuffersSpan[i];
waveBuffer.IsSendToAudioProcessor = true;
for (int j = 0; j < ChannelsCount; j++) for (int j = 0; j < ChannelsCount; j++)
{ {
@@ -702,9 +717,11 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax]; Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
Span<int> channelResourceIdsSpan = ChannelResourceIds.AsSpan();
for (int i = 0; i < ChannelsCount; i++) for (int i = 0; i < ChannelsCount; i++)
{ {
voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); voiceUpdateStates[i] = context.GetUpdateStateForDsp(channelResourceIdsSpan[i]);
} }
return UpdateParametersForCommandGeneration(voiceUpdateStates); return UpdateParametersForCommandGeneration(voiceUpdateStates);
-1
View File
@@ -6,7 +6,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" /> <ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup> </ItemGroup>
+226
View File
@@ -0,0 +1,226 @@
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Represents a collection that can store 1 bit values.
/// </summary>
public struct BitMap
{
/// <summary>
/// Size in bits of the integer used internally for the groups of bits.
/// </summary>
public const int IntSize = 64;
private const int IntShift = 6;
private const int IntMask = IntSize - 1;
private readonly long[] _masks;
/// <summary>
/// Gets or sets the value of a bit.
/// </summary>
/// <param name="bit">Bit to access</param>
/// <returns>Bit value</returns>
public bool this[int bit]
{
get => IsSet(bit);
set
{
if (value)
{
Set(bit);
}
else
{
Clear(bit);
}
}
}
/// <summary>
/// Creates a new bitmap.
/// </summary>
/// <param name="count">Total number of bits</param>
public BitMap(int count)
{
_masks = new long[(count + IntMask) / IntSize];
}
/// <summary>
/// Checks if any bit is set.
/// </summary>
/// <returns>True if any bit is set, false otherwise</returns>
public bool AnySet()
{
for (int i = 0; i < _masks.Length; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if a specific bit is set.
/// </summary>
/// <param name="bit">Bit to be checked</param>
/// <returns>True if set, false otherwise</returns>
public bool IsSet(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
return (_masks[wordIndex] & wordMask) != 0;
}
/// <summary>
/// Checks if any bit inside a given range of bits is set.
/// </summary>
/// <param name="start">Start bit of the range</param>
/// <param name="end">End bit of the range (inclusive)</param>
/// <returns>True if any bit is set, false otherwise</returns>
public bool IsSet(int start, int end)
{
if (start == end)
{
return IsSet(start);
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
return (_masks[startIndex] & startMask & endMask) != 0;
}
if ((_masks[startIndex] & startMask) != 0)
{
return true;
}
for (int i = startIndex + 1; i < endIndex; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
if ((_masks[endIndex] & endMask) != 0)
{
return true;
}
return false;
}
/// <summary>
/// Sets the value of a bit to 1.
/// </summary>
/// <param name="bit">Bit to be set</param>
/// <returns>True if the bit was 0 and then changed to 1, false if it was already 1</returns>
public bool Set(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
if ((_masks[wordIndex] & wordMask) != 0)
{
return false;
}
_masks[wordIndex] |= wordMask;
return true;
}
/// <summary>
/// Sets a given range of bits to 1.
/// </summary>
/// <param name="start">Start bit of the range</param>
/// <param name="end">End bit of the range (inclusive)</param>
public void SetRange(int start, int end)
{
if (start == end)
{
Set(start);
return;
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
_masks[startIndex] |= startMask & endMask;
}
else
{
_masks[startIndex] |= startMask;
for (int i = startIndex + 1; i < endIndex; i++)
{
_masks[i] |= -1;
}
_masks[endIndex] |= endMask;
}
}
/// <summary>
/// Sets a given bit to 0.
/// </summary>
/// <param name="bit">Bit to be cleared</param>
public void Clear(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
_masks[wordIndex] &= ~wordMask;
}
/// <summary>
/// Sets all bits to 0.
/// </summary>
public void Clear()
{
for (int i = 0; i < _masks.Length; i++)
{
_masks[i] = 0;
}
}
/// <summary>
/// Sets one or more groups of bits to 0.
/// See <see cref="IntSize"/> for how many bits are inside each group.
/// </summary>
/// <param name="start">Start index of the group</param>
/// <param name="end">End index of the group (inclusive)</param>
public void ClearInt(int start, int end)
{
for (int i = start; i <= end; i++)
{
_masks[i] = 0;
}
}
}
}
@@ -318,13 +318,16 @@ namespace Ryujinx.Common.Collections
{ {
Root = newNode; Root = newNode;
} }
else if (start.CompareTo(parent.Start) < 0) else if (parent != null && start.CompareTo(parent.Start) < 0)
{ {
parent.Left = newNode; parent.Left = newNode;
} }
else else
{ {
parent.Right = newNode; if (parent != null)
{
parent.Right = newNode;
}
} }
PropagateIncrease(newNode); PropagateIncrease(newNode);
@@ -14,12 +14,13 @@ namespace Ryujinx.Common.Collections
/// Adds a new node into the tree. /// Adds a new node into the tree.
/// </summary> /// </summary>
/// <param name="node">Node to be added</param> /// <param name="node">Node to be added</param>
/// <param name="parent">Node to be added under</param>
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
public void Add(T node) public void Add(T node, T parent = null)
{ {
ArgumentNullException.ThrowIfNull(node); ArgumentNullException.ThrowIfNull(node);
Insert(node); Insert(node, parent);
} }
/// <summary> /// <summary>
@@ -75,9 +76,11 @@ namespace Ryujinx.Common.Collections
/// Inserts a new node into the tree. /// Inserts a new node into the tree.
/// </summary> /// </summary>
/// <param name="node">Node to be inserted</param> /// <param name="node">Node to be inserted</param>
private void Insert(T node) /// <param name="parent">Node to be inserted under</param>
private void Insert(T node, T parent = null)
{ {
T newNode = BSTInsert(node); T newNode = parent != null ? InsertWithParent(node, parent) : BSTInsert(node);
RestoreBalanceAfterInsertion(newNode); RestoreBalanceAfterInsertion(newNode);
} }
@@ -120,10 +123,77 @@ namespace Ryujinx.Common.Collections
else if (newNode.CompareTo(parent) < 0) else if (newNode.CompareTo(parent) < 0)
{ {
parent.Left = newNode; parent.Left = newNode;
newNode.Successor = parent;
if (parent.Predecessor != null)
{
newNode.Predecessor = parent.Predecessor;
newNode.Predecessor.Successor = newNode;
}
parent.Predecessor = newNode;
} }
else else
{ {
parent.Right = newNode; parent.Right = newNode;
newNode.Predecessor = parent;
if (parent.Successor != null)
{
newNode.Successor = parent.Successor;
newNode.Successor.Predecessor = newNode;
}
parent.Successor = newNode;
}
Count++;
return newNode;
}
/// <summary>
/// Insertion Mechanism for a Binary Search Tree (BST).
/// <br></br>
/// Inserts a new node directly under a parent node
/// where all children in the left subtree are less than <paramref name="newNode"/>,
/// and all children in the right subtree are greater than <paramref name="newNode"/>.
/// </summary>
/// <param name="newNode">Node to be inserted</param>
/// <param name="parent">Node to be inserted under</param>
/// <returns>The inserted Node</returns>
private T InsertWithParent(T newNode, T parent)
{
newNode.Parent = parent;
if (newNode.CompareTo(parent) < 0)
{
parent.Left = newNode;
newNode.Successor = parent;
if (parent.Predecessor != null)
{
newNode.Predecessor = parent.Predecessor;
parent.Predecessor = newNode;
newNode.Predecessor.Successor = newNode;
}
parent.Predecessor = newNode;
}
else
{
parent.Right = newNode;
newNode.Predecessor = parent;
if (parent.Successor != null)
{
newNode.Successor = parent.Successor;
newNode.Successor.Predecessor = newNode;
}
parent.Successor = newNode;
} }
Count++; Count++;
return newNode; return newNode;
@@ -156,7 +226,7 @@ namespace Ryujinx.Common.Collections
} }
else else
{ {
T element = Minimum(RightOf(nodeToDelete)); T element = nodeToDelete.Successor;
child = RightOf(element); child = RightOf(element);
parent = ParentOf(element); parent = ParentOf(element);
@@ -184,6 +254,9 @@ namespace Ryujinx.Common.Collections
element.Left = old.Left; element.Left = old.Left;
element.Right = old.Right; element.Right = old.Right;
element.Parent = old.Parent; element.Parent = old.Parent;
element.Predecessor = old.Predecessor;
if (element.Predecessor != null)
element.Predecessor.Successor = element;
if (ParentOf(old) == null) if (ParentOf(old) == null)
{ {
@@ -239,6 +312,11 @@ namespace Ryujinx.Common.Collections
RestoreBalanceAfterRemoval(child); RestoreBalanceAfterRemoval(child);
} }
if (old.Successor != null)
old.Successor.Predecessor = old.Predecessor;
if (old.Predecessor != null)
old.Predecessor.Successor = old.Successor;
return old; return old;
} }
@@ -9,8 +9,7 @@ namespace Ryujinx.Common.Collections
public T Left; public T Left;
public T Right; public T Right;
public T Parent; public T Parent;
public T Predecessor;
public T Predecessor => IntrusiveRedBlackTreeImpl<T>.PredecessorOf((T)this); public T Successor;
public T Successor => IntrusiveRedBlackTreeImpl<T>.SuccessorOf((T)this);
} }
} }
@@ -107,7 +107,7 @@ namespace Ryujinx.Common.Collections
Node<TKey, TValue> node = GetNode(key); Node<TKey, TValue> node = GetNode(key);
if (node != null) if (node != null)
{ {
Node<TKey, TValue> successor = SuccessorOf(node); Node<TKey, TValue> successor = node.Successor;
return successor != null ? successor.Key : default; return successor != null ? successor.Key : default;
} }
@@ -124,7 +124,7 @@ namespace Ryujinx.Common.Collections
Node<TKey, TValue> node = GetNode(key); Node<TKey, TValue> node = GetNode(key);
if (node != null) if (node != null)
{ {
Node<TKey, TValue> predecessor = PredecessorOf(node); Node<TKey, TValue> predecessor = node.Predecessor;
return predecessor != null ? predecessor.Key : default; return predecessor != null ? predecessor.Key : default;
} }
@@ -132,11 +132,10 @@ namespace Ryujinx.Common.Collections
} }
/// <summary> /// <summary>
/// Adds all the nodes in the dictionary as key/value pairs into <paramref name="list"/>. /// Adds all the nodes in the dictionary as key/value pairs into a list.
/// <br></br> /// <br></br>
/// The key/value pairs will be added in Level Order. /// The key/value pairs will be added in Level Order.
/// </summary> /// </summary>
/// <param name="list">List to add the tree pairs into</param>
public List<KeyValuePair<TKey, TValue>> AsLevelOrderList() public List<KeyValuePair<TKey, TValue>> AsLevelOrderList()
{ {
List<KeyValuePair<TKey, TValue>> list = []; List<KeyValuePair<TKey, TValue>> list = [];
@@ -163,7 +162,7 @@ namespace Ryujinx.Common.Collections
} }
/// <summary> /// <summary>
/// Adds all the nodes in the dictionary into <paramref name="list"/>. /// Adds all the nodes in the dictionary into a list.
/// </summary> /// </summary>
/// <returns>A list of all KeyValuePairs sorted by Key Order</returns> /// <returns>A list of all KeyValuePairs sorted by Key Order</returns>
public List<KeyValuePair<TKey, TValue>> AsList() public List<KeyValuePair<TKey, TValue>> AsList()
@@ -275,17 +274,20 @@ namespace Ryujinx.Common.Collections
} }
} }
Node<TKey, TValue> newNode = new(key, value, parent); Node<TKey, TValue> newNode = new(key, value, parent);
if (newNode.Parent == null) if (parent == null)
{ {
Root = newNode; Root = newNode;
} }
else if (key.CompareTo(parent.Key) < 0) else if (parent != null && key.CompareTo(parent.Key) < 0)
{ {
parent.Left = newNode; parent.Left = newNode;
} }
else else
{ {
parent.Right = newNode; if (parent != null)
{
parent.Right = newNode;
}
} }
Count++; Count++;
return newNode; return newNode;
@@ -273,7 +273,7 @@ namespace Ryujinx.Common.Configuration
{ {
FileSystemInfo resolvedDirectoryInfo = FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName; string resolvedPath = resolvedDirectoryInfo?.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
} }
catch (Exception symlinkException) catch (Exception symlinkException)
@@ -297,7 +297,7 @@ namespace Ryujinx.Common.Configuration
{ {
FileSystemInfo resolvedDirectoryInfo = FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName; string resolvedPath = resolvedDirectoryInfo?.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
} }
catch (Exception symlinkException) catch (Exception symlinkException)
@@ -46,10 +46,11 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
// PS5 touchpad button // PS5 touchpad button
Touchpad, Touchpad,
// Virtual buttons for single joycon // Virtual buttons for single joycon (left)
SingleLeftTrigger0, SingleLeftTrigger0,
SingleRightTrigger0, SingleRightTrigger0,
// Virtual buttons for single joycon (right)
SingleLeftTrigger1, SingleLeftTrigger1,
SingleRightTrigger1, SingleRightTrigger1,

Some files were not shown because too many files have changed in this diff Show More