Compare commits

...

53 Commits

Author SHA1 Message Date
9e50dd99d7 nuget: bump System.IdentityModel.Tokens.Jwt from 6.28.1 to 6.29.0 (#4694)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.28.1 to 6.29.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.28.1...6.29.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 21:31:12 +02:00
0dec91bb42 nuget: bump System.Management from 7.0.0 to 7.0.1 (#4695)
Bumps [System.Management](https://github.com/dotnet/runtime) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v7.0.1)

---
updated-dependencies:
- dependency-name: System.Management
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 21:30:40 +02:00
d9b63353b0 Support copy between multisample and non-multisample depth textures (#4676)
* Support copy between multisample and non-multisample depth textures

* PR feedback
2023-04-17 08:13:53 +00:00
eabd0ec93f Revert "chore: Update Silk.NET to 2.17.1 (#4686)" (#4690)
This reverts commit 79d1c190db.
2023-04-16 20:56:27 -03:00
138d5dc64a Vulkan: HashTableSlim lookup optimization (#4688) 2023-04-16 14:57:01 -03:00
3e68a87d63 Change SMAA filter texture clear method (#4685)
* Change SMAA filter texture clear method

* Alpha should be 1

* Delete more unnecessary code
2023-04-16 14:26:22 -03:00
69b6ef7a4a [GUI] Add network interface dropdown (#4597)
* Add network adapter dropdown from LDN build

* Ava: Add NetworkInterfaces to SettingsNetworkTab

* Add headless network interface option

* Add network interface dropdown to Avalonia

* Fix handling network interfaces without a gateway address

* gtk: Actually save selected network interface to config

* Increment config version
2023-04-16 15:25:20 +00:00
40e87c634e Fix a crash in Ryujinx.Headless.SDL2 when loading an app (#4687)
Caused by the recent application loader changes.
2023-04-16 16:50:30 +02:00
79d1c190db chore: Update Silk.NET to 2.17.1 (#4686) 2023-04-16 09:38:07 +00:00
2bc88467eb Update README.md 2023-04-16 09:37:31 +00:00
baf8752e74 Ensure the updater doesn't delete hidden or system files (#4626)
* Copy desktop.ini to update directory if it exists in HomeDir

* EnumerateFilesToDelete() exclude files with "Hidden" and "System" attributes
2023-04-16 09:19:33 +00:00
d5e4378aea nuget: bump DynamicData from 7.13.1 to 7.13.5 (#4654)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.1 to 7.13.5.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.13.1...7.13.5)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-16 09:02:06 +00:00
6dbcdfea47 Ava: Fix nca extraction window never closing & minor cleanup (#4569)
* ava: Remove unused doWhileDeferred parameters

* ava: Minimally improve swkbd dialog

It's currently impossible to get the dialog to redirect focus to the InputBox.

* ava: Fix nca extraction dialog never closing

Also contains some minor cleanup
2023-04-16 07:09:02 +00:00
c5258cf082 Ability to hide file types in Game List (#4555)
* Added HiddenFileTypes to config state, and check to file enumeration

* Added hiddenfiletypes checkboxes to the UI

* Added Ava version of HiddenFileTypes

* Inverted Hide to Show with file types, minor formatting

* all variables with a reference to 'hidden' is now 'shown'

* one more variable name changed

* review feedback

* added FileTypes extension methof to get the correlating config value

* moved extension method to new folder and file in Ryujinx.Ui.Common

* added default case for ToggleFileType

* changed exception type to OutOfRangeException
2023-04-16 01:03:35 +00:00
5c89e22bb9 Added check for eventual symlink when displaying game files. (#4526)
* Added check for eventual symlink when displaying game files.

* Moved symlink check logic

* Moved symlink check logic

* Fixed prev commit

---------

Co-authored-by: Daniel Shala <danielshala00@gmail.com>
2023-04-15 16:11:24 +00:00
11ecff2ff0 Rename Hipc to Cmif where appropriate (#3880) 2023-04-14 20:00:34 -03:00
4c3f09644a Move swkbd message null check into constructor (#4671) 2023-04-12 21:18:40 +02:00
e187a8870a HLE: Deal with empty title names properly (#4643)
* hle: Deal with empty titleNames in some languages

* gui: Fix displaying the wrong title name

* Remove unnecessary bounds check

* Fix a NRE when getting the version string

* Restore empty string logic
2023-04-12 01:09:47 +00:00
a64fee29dc Vulkan: add situational "Fast Flush" mode (#4667)
* Flush in the middle of long command buffers.

* Vulkan: add situational "Fast Flush" mode

The AutoFlushCounter class was added to periodically flush Vulkan command buffers throughout a frame, which reduces latency to the GPU as commands are submitted and processed much sooner. This was done by allowing command buffers to flush when framebuffer attachments changed.

However, some games have incredibly long render passes with a large number of draws, and really aggressive data access that forces GPU sync.

The Vulkan backend could potentially end up building a single command buffer for 4-5ms if a pass has enough draws, such as in BOTW. In the scenario where sync is waited on immediately after submission, this would have to wait for the completion of a much longer command buffer than usual.

The solution is to force command buffer submission periodically in a "fast flush" mode. This will end up splitting render passes, but it will only enable if sync is aggressive enough.

This should improve performance in GPU limited scenarios, or in games that aggressively wait on synchronization. In some games, it may only kick in when res scaling. It won't trigger in games like SMO where sync is not an issue.

Improves performance in Pokemon Scarlet/Violet (res scaled) and BOTW (in general).

* Add conversions in milliseconds next to flush timers.
2023-04-11 09:23:41 +02:00
9ef94c8292 ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext (#4661)
* ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext

Some games access these system registers several tens of thousands of times in a second from many different threads. While this isn't really crippling, it is a lot of wasted time spent in a reverse pinvoke transition.

Example games are Pokemon Scarlet/Violet and BOTW. These games have a lot of different potential bottlenecks so it's unlikely you will see a consistent improvement, but it definitely disappears from the cpu profile.

* Remove unreachable code.

* Add ulong conversion for offsets

* Nit
2023-04-11 08:55:04 +02:00
915d6d044c OpenGL: Fix OBS/Overlays again by binding FB before present (#4668)
This seems to have been removed by the Post-Processing PR, but it is required for the display in OBS to be the right way up and properly scaled.

I've tested this with AA and FSR on MK8D and it seems to behave properly. Testing is welcome.
2023-04-11 08:32:31 +02:00
a4780ab33b Force activate parent window before dialog is shown (#4663) 2023-04-11 00:04:31 +02:00
a947a45d81 gtk: Fix a NRE when disposing OpenGL (#4648) 2023-04-10 17:00:23 +02:00
9db73f74cf ARMeilleure: Respect FZ/RM flags for all floating point operations (#4618)
* ARMeilleure: Respect Fz flag for all floating point operations.

This is a change in strategy for emulating the Fz FPCR flag. Before, it was set before instructions that "needed it" and reset after. However, this missed a few hot instructions like the multiplication instruction, and the entirety of A32.

The new strategy is to set the Fz flag only in the following circumstances:

- Set to match FPCR before translated functions/loop are executed.
- Reset when calling SoftFloat methods, set when returning.
- Reset when exiting execution.

This allows us to remove the code around the existing Fz aware instructions, and get the accuracy benefits on all floating point instructions executed while in translated code.

Single step executions now need to be called with a context wrapper - right now it just contains the Fz flag initialization, and won't actually do anything on ARM.

This fixes a bug in Breath of the Wild where some physics interactions could randomly crash the game due to subnormal values not flushing to zero.

This is draft right now because I need to answer the questions:
- Does dotnet avoid changing the value of Mxcsr?
- Is it a good idea to assume that? Or should the flag set/restore be done on every managed method call, not just softfloat?
- If we assume that, do we want a unit test to verify the behaviour?

I recommend testing a bunch of games, especially games affected when this was originally added, such as #1611.

* Remove unused method

* Use FMA for Fmadd, Fmsub, Fnmadd, Fnmsub, Fmla, Fmls

...when available.

Similar implementation to A32

* Use FMA for Frecps, Frsqrts

* Don't set DAZ.

* Add round mode to ARM FP mode

* Fix mistakes

* Add test for FP state when calling managed methods

* Add explanatory comment to test.

* Cleanup

* Add A64 FPCR flags

* Vrintx_S A32 fast path on A64 backend

* Address feedback 1, re-enable DAZ

* Fix FMA instructions By Elem

* Address feedback
2023-04-10 12:22:58 +02:00
a1efd87c45 Implement remaining Arm64 HINT instructions as NOP (#4658)
* Implement remaining HINT instructions as NOP

* Split HINT encodings more to account for CSDB
2023-04-09 13:21:16 -03:00
49be977588 Eliminate boxing allocations caused by ISampledData structs (#4556)
* Redesign use of ISampledData for accessing the SamplingNumber value on input data structs.

* Always read SamplingNumber as little-endian

* Restored field order for SixAxisSensorState. Rework to allow possibility of non-zero offsets for the SamplingNumber field. Set StructLayout Pack=8 - the KeyboardState struct is 4 bytes shorter with any other value.

* fix spelling

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

* set Pack = 1 for ISampledDataStruct types, added Unknown field to KeyboardState

* extend size of KeyboardModifier

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2023-04-05 17:42:32 -03:00
c95be55091 vulkan: Cleanup PhysicalDevice and Instance querying (#4632)
* vulkan: Move most of the properties enumeration to VulkanPhysicalDevice

That clean up a bit of duplicate logic.
Also move to use an hashset for device extensions.

* vulkan: Move instance querying to VulkanInstance

Also cleanup code to use span when possible instead of unsafe pointers.

* Address gdkchan's comments
2023-04-05 14:48:38 -03:00
63dedbda86 nuget: bump System.IdentityModel.Tokens.Jwt from 6.27.0 to 6.28.1 (#4639)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.27.0 to 6.28.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.27.0...6.28.1)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 07:55:57 +02:00
c532118d94 Use index fragment shader output when dual source blend is enabled (#4404)
* Use index fragment shader output when dual source blend is enabled

* Shader cache version bump

* Actually set DualSourceBlendEnabled to true

* Fix XML doc

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-04-05 05:25:19 +02:00
52d6f2e656 hle: Set ProcessResult name from NACP (#4633)
* Extract titleName from nacp

* Address formatting feedback

* Check if the desired language is actually available
2023-04-05 03:34:21 +02:00
c9bc4eaf58 Fix missing string enum converters for the config (#4634)
* Fix missing string enum converters for the config

* Revert changing KeyboardHotkeys to struct

This needs to be done because
Avalonia's TwoWay Binding breaks otherwise.
2023-04-03 15:37:27 +02:00
3249f8ff41 Source generated json serializers (#4582)
* Use source generated json serializers in order to improve code trimming

* Use strongly typed github releases model to fetch updates instead of raw Newtonsoft.Json parsing

* Use separate model for LogEventArgs serialization

* Make dynamic object formatter static. Fix string builder pooling.

* Do not inherit json version of LogEventArgs from EventArgs

* Fix extra space in object formatting

* Write log json directly to stream instead of using buffer writer

* Rebase fixes

* Rebase fixes

* Rebase fixes

* Enforce block-scoped namespaces in the solution. Convert style for existing code

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Rebase indent fix

* Fix indent

* Delete unnecessary json properties

* Rebase fix

* Remove overridden json property names as they are handled in the options

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Use default json options in github api calls

* Indentation and spacing fixes

* Fix json serialization

* Fix missing JsonConverter for config enums

* Add double \n\n after the whole string, not inside join

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-04-03 10:14:19 +00:00
1b41b285ac nuget: bump DynamicData from 7.12.11 to 7.13.1 (#4490)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.12.11 to 7.13.1.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.12.11...7.13.1)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-01 08:23:09 +00:00
f5a6f45b27 vulkan: Separate debug utils logic from VulkanInitialization (#4609)
* vulkan: Separate debug utils logic from VulkanInitialization

Also checks for VK_EXT_debug_utils existence instead of force enabling it and allow possible error during messenger init

* Address gdkchan's comment

* Use CreateDebugUtilsMessenger Span variant
2023-04-01 08:05:02 +00:00
210557951b nuget: bump Avalonia dependencies from 0.10.18 to 0.10.19 (#4602)
* infra: Update Avalonia to 0.10.19

* infra: Update XamlNameReferenceGenerator to 1.6.1
2023-04-01 07:27:34 +00:00
4c2d9ff3ff HLE: Refactoring of ApplicationLoader (#4480)
* HLE: Refactoring of ApplicationLoader

* Fix SDL2 Headless

* Addresses gdkchan feedback

* Fixes LoadUnpackedNca RomFS loading

* remove useless casting

* Cleanup and fixe empty application name

* Remove ProcessInfo

* Fixes typo

* ActiveProcess to ActiveApplication

* Update check

* Clean using.

* Use the correct filepath when loading Homebrew.npdm

* Fix NRE in ProcessResult if MetaLoader is null

* Add more checks for valid processId & return success

* Add missing logging statement for npdm error

* Return result for LoadKip()

* Move error logging out of PFS load extension method

This avoids logging "Could not find Main NCA"
followed by "Loading main..." when trying to start hbl.

* Fix GUIs not checking load results

* Fix style and formatting issues

* Fix formatting and wording

* gtk: Refactor LoadApplication()

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-03-31 21:16:46 +02:00
8198b99935 Fix Linux hang on shutdown (#4617)
* Rework StdErr-to-log redirection to use built-in FileStream, and do reads asynchronously to avoid hanging the process shutdown.

* set _disposable to false ASAP
2023-03-30 22:07:07 +02:00
460f96967d Slight Code Refactoring (#4373)
* Simplify return statements by using ternary expressions

* Remove a redundant type conversion

* Reduce nesting by inverting "if" statements

* Try to improve code readability by using LINQ and inverting "if" statements

* Try to improve code readability by using LINQ, using ternary expressions, and inverting "if" statements

* Add line breaks to long LINQ

* Add line breaks to long LINQ
2023-03-28 14:59:43 +02:00
7ca779a26d audout: Fix a possible crash with SDL2 when the SDL2 audio backend is dummy (#4605)
This change makes audio device error not fatal.
In case of error, the SDL2 audio backend will behave like the dummy
backend.
2023-03-27 20:56:36 +02:00
b5032b3c91 vulkan: Fix access level of extensions fields and make them readonly (#4608) 2023-03-27 08:40:27 +02:00
f0a3dff136 vulkan: Remove CreateCommandBufferPool from VulkanInitialization (#4606)
It was only called in one place, that can be simplified.
2023-03-27 02:16:31 +00:00
f659dcb9d8 vulkan: fix broken "VK_EXT_subgroup_size_control" support check (#4607)
Not sure since when it was broken...
2023-03-26 19:01:30 +02:00
a34fb0e939 Vulkan: Insert barriers before clears (#4596)
* Vulkan: Insert barriers before clears

Newer NVIDIA GPUs seem to be able to start clearing render targets before the last rasterization task is completed, which can cause it to clear a texture while it is being sampled.

This change adds a barrier from read to write when doing a clear, assuming it has been sampled in the past. It could be possible for this to be needed for sample into draw by some GPU, but it's not right now afaik.

This should fix visual artifacts on newer NVIDIA GPUs and driver combos. Contrary to popular belief, Tetris® Effect: Connected is not affected. Testing welcome, hopefully should fix most cases of this and not cost too much performance.

* Visual Studio Moment

* Address feedback

* Address Feedback 2
2023-03-26 12:51:02 +02:00
21ce8a9b80 chore: Update Ryujinx.SDL2-CS to 2.26.3 (#4479) 2023-03-24 22:42:24 +01:00
9ecbee8032 Batch inline index buffer update (#4587) 2023-03-24 14:19:54 +01:00
80519af67d Update short cache textures if modified (#4586) 2023-03-24 12:54:58 +01:00
26e30faff3 Fix handle leak on IShopServiceAccessServerInterface.CreateServerInterface (#4591) 2023-03-24 11:56:54 +01:00
0992310b76 ARMeilleure: Check for XSAVE cpuid flag for AVX{2,512} (#4584)
Protection for the `xgetbv` instruction for systems that do not support
`xcr0` such as nehalem processors.

The `XSAVE` cpuid indicates support for `XSAVE`, `XRESTOR`, `XSETBV`,
`XGETBV` while `OSXSAVE` indicates if the operating system itself has
`XSAVE` turned on. Both must be checked at the same time.
2023-03-22 14:51:21 -03:00
009c1101d2 CI: add a version tag to correlate release versions with commits (#4572)
* add step to tag commit with release version

* add step to tag commit with release version

* Rename step to “Create Tag”

* Fix name
2023-03-22 13:17:28 +01:00
ba95ee54ab Revert "Use source generated json serializers in order to improve code trimming (#4094)" (#4576)
This reverts commit 4ce4299ca2.
2023-03-21 20:14:46 -03:00
4ce4299ca2 Use source generated json serializers in order to improve code trimming (#4094)
* Use source generated json serializers in order to improve code trimming

* Use strongly typed github releases model to fetch updates instead of raw Newtonsoft.Json parsing

* Use separate model for LogEventArgs serialization

* Make dynamic object formatter static. Fix string builder pooling.

* Do not inherit json version of LogEventArgs from EventArgs

* Fix extra space in object formatting

* Write log json directly to stream instead of using buffer writer

* Rebase fixes

* Rebase fixes

* Rebase fixes

* Enforce block-scoped namespaces in the solution. Convert style for existing code

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Rebase indent fix

* Fix indent

* Delete unnecessary json properties

* Rebase fix

* Remove overridden json property names as they are handled in the options

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Use default json options in github api calls

* Indentation and spacing fixes

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-03-21 19:41:19 -03:00
17620d18db ARMeilleure: Add initial support for AVX512 (EVEX encoding) (cont) (#4147)
* ARMeilleure: Add AVX512{F,VL,DQ,BW} detection

Add `UseAvx512Ortho` and `UseAvx512OrthoFloat` optimization flags as
short-hands for `F+VL` and `F+VL+DQ`.

* ARMeilleure: Add initial support for EVEX instruction encoding

Does not implement rounding, or exception controls.

* ARMeilleure: Add `X86Vpternlogd`

Accelerates the vector-`Not` instruction.

* ARMeilleure: Add check for `OSXSAVE` for AVX{2,512}

* ARMeilleure: Add check for `XCR0` flags

Add XCR0 register checks for AVX and AVX512F, following the guidelines
from section 14.3 and 15.2 from the Intel Architecture Software
Developer's Manual.

* ARMeilleure: Remove redundant `ReProtect` and `Dispose`, formatting

* ARMeilleure: Move XCR0 procedure to GetXcr0Eax

* ARMeilleure: Add `XCR0` to `FeatureInfo` structure

* ARMeilleure: Utilize `ReadOnlySpan` for Xcr0 assembly

Avoids an additional allocation

* ARMeilleure: Formatting fixes

* ARMeilleure: Fix EVEX encoding src2 register index

> Just like in VEX prefix, vvvv is provided in inverted form.

* ARMeilleure: Add `X86Vpternlogd` acceleration to `Vmvn_I`

Passes unit tests, verified instruction utilization

* ARMeilleure: Fix EVEX register operand designations

Operand 2 was being sourced improperly.

EVEX encoded instructions source their operands like so:
Operand 1: ModRM:reg
Operand 2: EVEX.vvvvv
Operand 3: ModRM:r/m
Operand 4: Imm

This fixes the improper register designations when emitting vpternlog.
Now "dest", "src1", "src2" arguments emit in the proper order in EVEX instructions.

* ARMeilleure: Add `X86Vpternlogd` acceleration to `Orn_V`

* ARMeilleure: PTC version bump

* ARMeilleure: Update EVEX encoding Debug.Assert to Debug.Fail

* ARMeilleure: Update EVEX encoding comment capitalization
2023-03-20 16:09:24 -03:00
9f1cf6458c Vulkan: Migrate buffers between memory types to improve GPU performance (#4540)
* Initial implementation of migration between memory heaps

- Missing OOM handling
- Missing `_map` data safety when remapping
  - Copy may not have completed yet (needs some kind of fence)
  - Map may be unmapped before it is done being used. (needs scoped access)
- SSBO accesses are all "writes" - maybe pass info in another way.
- Missing keeping map type when resizing buffers (should this be done?)

* Ensure migrated data is in place before flushing.

* Fix issue where old waitable would be signalled.

- There is a real issue where existing Auto<> references need to be replaced.

* Swap bound Auto<> instances when swapping buffer backing

* Fix conversion buffers

* Don't try move buffers if the host has shared memory.

* Make GPU methods return PinnedSpan with scope

* Storage Hint

* Fix stupidity

* Fix rebase

* Tweak rules

Attempt to sidestep BOTW slowdown

* Remove line

* Migrate only when command buffers flush

* Change backing swap log to debug

* Address some feedback

* Disallow backing swap when the flush lock is held by the current thread

* Make PinnedSpan from ReadOnlySpan explicitly unsafe

* Fix some small issues

- Index buffer swap fixed
- Allocate DeviceLocal buffers using a separate block list to images.

* Remove alternative flags

* Address feedback
2023-03-19 17:56:48 -03:00
402 changed files with 8268 additions and 4744 deletions

View File

@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
#### C# Coding Conventions #### #### C# Coding Conventions ####
# Namespace preferences
csharp_style_namespace_declarations = block_scoped:warning
resharper_csharp_namespace_body = block_scoped
# var preferences # var preferences
csharp_style_var_elsewhere = false:silent csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent csharp_style_var_for_built_in_types = false:silent

View File

@ -112,6 +112,17 @@ jobs:
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }} token: ${{ secrets.RELEASE_TOKEN }}
- name: Create tag
uses: actions/github-script@v5
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
sha: context.sha
})
flatpak_release: flatpak_release:
uses: ./.github/workflows/flatpak.yml uses: ./.github/workflows/flatpak.yml
needs: release needs: release

View File

@ -7,6 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem)); Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd)); Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi)); Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi));
Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister));
Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister));
Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister)); Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister));
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister)); Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem)); Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));

View File

@ -1034,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(opCode != BadOp, "Invalid opcode value."); Debug.Assert(opCode != BadOp, "Invalid opcode value.");
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding) if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
{
WriteEvexInst(dest, src1, src2, type, flags, opCode);
opCode &= 0xff;
}
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
{ {
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits. // In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
@ -1153,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
} }
} }
private void WriteEvexInst(
Operand dest,
Operand src1,
Operand src2,
OperandType type,
InstructionFlags flags,
int opCode,
bool broadcast = false,
int registerWidth = 128,
int maskRegisterIdx = 0,
bool zeroElements = false)
{
int op1Idx = dest.GetRegister().Index;
int op2Idx = src1.GetRegister().Index;
int op3Idx = src2.GetRegister().Index;
WriteByte(0x62);
// P0
// Extend operand 1 register
bool r = (op1Idx & 8) == 0;
// Extend operand 3 register
bool x = (op3Idx & 16) == 0;
// Extend operand 3 register
bool b = (op3Idx & 8) == 0;
// Extend operand 1 register
bool rp = (op1Idx & 16) == 0;
// Escape code index
byte mm = 0b00;
switch ((ushort)(opCode >> 8))
{
case 0xf00: mm = 0b01; break;
case 0xf38: mm = 0b10; break;
case 0xf3a: mm = 0b11; break;
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
}
WriteByte(
(byte)(
(r ? 0x80 : 0) |
(x ? 0x40 : 0) |
(b ? 0x20 : 0) |
(rp ? 0x10 : 0) |
mm));
// P1
// Specify 64-bit lane mode
bool w = Is64Bits(type);
// Operand 2 register index
byte vvvv = (byte)(~op2Idx & 0b1111);
// Opcode prefix
byte pp = (flags & InstructionFlags.PrefixMask) switch
{
InstructionFlags.Prefix66 => 0b01,
InstructionFlags.PrefixF3 => 0b10,
InstructionFlags.PrefixF2 => 0b11,
_ => 0
};
WriteByte(
(byte)(
(w ? 0x80 : 0) |
(vvvv << 3) |
0b100 |
pp));
// P2
// Mask register determines what elements to zero, rather than what elements to merge
bool z = zeroElements;
// Specifies register-width
byte ll = 0b00;
switch (registerWidth)
{
case 128: ll = 0b00; break;
case 256: ll = 0b01; break;
case 512: ll = 0b10; break;
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
}
// Embedded broadcast in the case of a memory operand
bool bcast = broadcast;
// Extend operand 2 register
bool vp = (op2Idx & 16) == 0;
// Mask register index
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
byte aaa = (byte)(maskRegisterIdx & 0b111);
WriteByte(
(byte)(
(z ? 0x80 : 0) |
(ll << 5) |
(bcast ? 0x10 : 0) |
(vp ? 8 : 0) |
aaa));
}
private void WriteCompactInst(Operand operand, int opCode) private void WriteCompactInst(Operand operand, int opCode)
{ {
int regIndex = operand.GetRegister().Index; int regIndex = operand.GetRegister().Index;

View File

@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
Reg8Dest = 1 << 2, Reg8Dest = 1 << 2,
RexW = 1 << 3, RexW = 1 << 3,
Vex = 1 << 4, Vex = 1 << 4,
Evex = 1 << 5,
PrefixBit = 16, PrefixBit = 16,
PrefixMask = 7 << PrefixBit, PrefixMask = 7 << PrefixBit,
@ -267,17 +268,20 @@ namespace ARMeilleure.CodeGen.X86
Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None)); Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex)); Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));

View File

@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
case IntrinsicType.Mxcsr: case IntrinsicType.Mxcsr:
{ {
Operand offset = operation.GetSource(0); Operand offset = operation.GetSource(0);
Operand bits = operation.GetSource(1);
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant); Debug.Assert(offset.Kind == OperandKind.Constant);
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32); Debug.Assert(offset.Type == OperandType.I32);
int offs = offset.AsInt32() + context.CallArgsRegionSize; int offs = offset.AsInt32() + context.CallArgsRegionSize;
@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding); Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
context.Assembler.Stmxcsr(memOp); if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
{ {
context.Assembler.Or(memOp, bits, OperandType.I32); Operand bits = operation.GetSource(1);
Debug.Assert(bits.Type == OperandType.I32);
context.Assembler.Mov(memOp, bits, OperandType.I32);
context.Assembler.Ldmxcsr(memOp);
} }
else /* if (intrinOp.Intrinsic == Intrinsic.X86Mxcsrub) */ else if (operation.Intrinsic == Intrinsic.X86Stmxcsr)
{ {
Operand notBits = Const(~bits.AsInt32()); Operand dest = operation.Destination;
Debug.Assert(dest.Type == OperandType.I32);
context.Assembler.And(memOp, notBits, OperandType.I32); context.Assembler.Stmxcsr(memOp);
context.Assembler.Mov(dest, memOp, OperandType.I32);
} }
context.Assembler.Ldmxcsr(memOp);
break; break;
} }

View File

@ -1,10 +1,14 @@
using Ryujinx.Memory;
using System; using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
namespace ARMeilleure.CodeGen.X86 namespace ARMeilleure.CodeGen.X86
{ {
static class HardwareCapabilities static class HardwareCapabilities
{ {
private delegate uint GetXcr0();
static HardwareCapabilities() static HardwareCapabilities()
{ {
if (!X86Base.IsSupported) if (!X86Base.IsSupported)
@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7; FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7; FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
} }
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
}
private static uint GetXcr0Eax()
{
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
{
// XSAVE feature required for xgetbv
return 0;
}
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
{
0x31, 0xc9, // xor ecx, ecx
0xf, 0x01, 0xd0, // xgetbv
0xc3, // ret
};
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
memGetXcr0.Write(0, asmGetXcr0);
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
return fGetXcr0();
} }
[Flags] [Flags]
@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
Sse42 = 1 << 20, Sse42 = 1 << 20,
Popcnt = 1 << 23, Popcnt = 1 << 23,
Aes = 1 << 25, Aes = 1 << 25,
Xsave = 1 << 26,
Osxsave = 1 << 27,
Avx = 1 << 28, Avx = 1 << 28,
F16c = 1 << 29 F16c = 1 << 29
} }
@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
public enum FeatureFlags7Ebx public enum FeatureFlags7Ebx
{ {
Avx2 = 1 << 5, Avx2 = 1 << 5,
Sha = 1 << 29 Avx512f = 1 << 16,
Avx512dq = 1 << 17,
Sha = 1 << 29,
Avx512bw = 1 << 30,
Avx512vl = 1 << 31
} }
[Flags] [Flags]
@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
Gfni = 1 << 8, Gfni = 1 << 8,
} }
[Flags]
public enum Xcr0FlagsEax
{
Sse = 1 << 1,
YmmHi128 = 1 << 2,
Opmask = 1 << 5,
ZmmHi256 = 1 << 6,
Hi16Zmm = 1 << 7
}
public static FeatureFlags1Edx FeatureInfo1Edx { get; } public static FeatureFlags1Edx FeatureInfo1Edx { get; }
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; } public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0; public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0; public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse); public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2); public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
@ -76,8 +125,13 @@ namespace ARMeilleure.CodeGen.X86
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42); public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt); public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes); public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx); public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx; public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c); public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha); public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni); public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
public static bool ForceLegacySse { get; set; } public static bool ForceLegacySse { get; set; }
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse; public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
} }
} }

View File

@ -60,6 +60,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary)); Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary)); Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm)); Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
Add(Intrinsic.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary)); Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary)); Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary)); Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary));
@ -75,8 +76,6 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary)); Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary)); Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary)); Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary));
Add(Intrinsic.X86Mxcsrmb, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Mask bits.
Add(Intrinsic.X86Mxcsrub, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Unmask bits.
Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary)); Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary)); Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary)); Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary));
@ -160,6 +159,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary)); Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary)); Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary)); Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary));
Add(Intrinsic.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary)); Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary)); Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary)); Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
@ -170,16 +170,19 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary)); Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary)); Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm)); Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
Add(Intrinsic.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma)); Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma)); Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma)); Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary)); Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary)); Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
} }

View File

@ -0,0 +1,15 @@
using System;
namespace ARMeilleure.CodeGen.X86
{
[Flags]
enum Mxcsr
{
Ftz = 1 << 15, // Flush To Zero.
Rhi = 1 << 14, // Round Mode high bit.
Rlo = 1 << 13, // Round Mode low bit.
Um = 1 << 11, // Underflow Mask.
Dm = 1 << 8, // Denormal Mask.
Daz = 1 << 6 // Denormals Are Zero.
}
}

View File

@ -120,12 +120,18 @@ namespace ARMeilleure.CodeGen.X86
break; break;
case Instruction.Extended: case Instruction.Extended:
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub) if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
{ {
int stackOffset = stackAlloc.Allocate(OperandType.I32); int stackOffset = stackAlloc.Allocate(OperandType.I32);
node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) }); node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) });
} }
else if (node.Intrinsic == Intrinsic.X86Stmxcsr)
{
int stackOffset = stackAlloc.Allocate(OperandType.I32);
node.SetSources(new Operand[] { Const(stackOffset) });
}
break; break;
} }
} }

View File

@ -208,17 +208,20 @@ namespace ARMeilleure.CodeGen.X86
Vblendvps, Vblendvps,
Vcvtph2ps, Vcvtph2ps,
Vcvtps2ph, Vcvtps2ph,
Vfmadd231pd,
Vfmadd231ps, Vfmadd231ps,
Vfmadd231sd, Vfmadd231sd,
Vfmadd231ss, Vfmadd231ss,
Vfmsub231sd, Vfmsub231sd,
Vfmsub231ss, Vfmsub231ss,
Vfnmadd231pd,
Vfnmadd231ps, Vfnmadd231ps,
Vfnmadd231sd, Vfnmadd231sd,
Vfnmadd231ss, Vfnmadd231ss,
Vfnmsub231sd, Vfnmsub231sd,
Vfnmsub231ss, Vfnmsub231ss,
Vpblendvb, Vpblendvb,
Vpternlogd,
Xor, Xor,
Xorpd, Xorpd,
Xorps, Xorps,

View File

@ -1,6 +1,7 @@
namespace ARMeilleure.Decoders; namespace ARMeilleure.Decoders
interface IOpCode32Exception
{ {
int Id { get; } interface IOpCode32Exception
{
int Id { get; }
}
} }

View File

@ -108,6 +108,13 @@ namespace ARMeilleure.Decoders
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create); SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create);
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create); SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create); SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);

View File

@ -615,14 +615,11 @@ namespace ARMeilleure.Instructions
{ {
return EmitSse41ProcessNaNsOpF(context, (op1, op2) => return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
{
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd; Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
return context.AddIntrinsic(addInst, op1, op2); return context.AddIntrinsic(addInst, op1, op2);
}, scalar: false, op1, op2);
}, scalar: false, op1, op2); }, scalar: false, op1, op2);
}); });
} }
@ -696,17 +693,33 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn); Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm); Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0) if (op.Size == 0)
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
}
context.Copy(d, context.VectorZeroUpper96(res)); context.Copy(d, context.VectorZeroUpper96(res));
} }
else /* if (op.Size == 1) */ else /* if (op.Size == 1) */
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
}
context.Copy(d, context.VectorZeroUpper64(res)); context.Copy(d, context.VectorZeroUpper64(res));
} }
@ -730,10 +743,7 @@ namespace ARMeilleure.Instructions
{ {
EmitSse41ProcessNaNsOpF(context, (op1, op2) => EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: true, op1, op2);
}, scalar: true); }, scalar: true);
} }
else else
@ -755,10 +765,7 @@ namespace ARMeilleure.Instructions
{ {
EmitSse41ProcessNaNsOpF(context, (op1, op2) => EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
}, scalar: false); }, scalar: false);
} }
else else
@ -886,10 +893,7 @@ namespace ARMeilleure.Instructions
{ {
return EmitSse41ProcessNaNsOpF(context, (op1, op2) => return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
}, scalar: false, op1, op2); }, scalar: false, op1, op2);
}); });
} }
@ -914,10 +918,7 @@ namespace ARMeilleure.Instructions
{ {
return EmitSse41ProcessNaNsOpF(context, (op1, op2) => return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
}, scalar: false, op1, op2); }, scalar: false, op1, op2);
}); });
} }
@ -940,10 +941,7 @@ namespace ARMeilleure.Instructions
{ {
EmitSse41ProcessNaNsOpF(context, (op1, op2) => EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: true, op1, op2);
}, scalar: true); }, scalar: true);
} }
else else
@ -965,10 +963,7 @@ namespace ARMeilleure.Instructions
{ {
EmitSse41ProcessNaNsOpF(context, (op1, op2) => EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
}, scalar: false); }, scalar: false);
} }
else else
@ -1096,10 +1091,7 @@ namespace ARMeilleure.Instructions
{ {
return EmitSse41ProcessNaNsOpF(context, (op1, op2) => return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
}, scalar: false, op1, op2); }, scalar: false, op1, op2);
}); });
} }
@ -1124,10 +1116,7 @@ namespace ARMeilleure.Instructions
{ {
return EmitSse41ProcessNaNsOpF(context, (op1, op2) => return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
}, scalar: false, op1, op2); }, scalar: false, op1, op2);
}); });
} }
@ -1146,6 +1135,37 @@ namespace ARMeilleure.Instructions
{ {
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe); InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe);
} }
else if (Optimizations.UseFma)
{
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
Operand d = GetVec(op.Rd);
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
int sizeF = op.Size & 1;
if (sizeF == 0)
{
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res);
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (sizeF == 1) */
{
int shuffleMask = op.Index | op.Index << 1;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res);
context.Copy(d, context.VectorZeroUpper64(res));
}
}
else else
{ {
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
@ -1171,11 +1191,19 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {
@ -1186,9 +1214,15 @@ namespace ARMeilleure.Instructions
} }
else /* if (sizeF == 1) */ else /* if (sizeF == 1) */
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
}
context.Copy(d, res); context.Copy(d, res);
} }
@ -1224,8 +1258,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {
@ -1240,8 +1281,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
}
context.Copy(d, res); context.Copy(d, res);
} }
@ -1261,6 +1309,37 @@ namespace ARMeilleure.Instructions
{ {
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe); InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe);
} }
else if (Optimizations.UseFma)
{
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
Operand d = GetVec(op.Rd);
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
int sizeF = op.Size & 1;
if (sizeF == 0)
{
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res);
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (sizeF == 1) */
{
int shuffleMask = op.Index | op.Index << 1;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res);
context.Copy(d, context.VectorZeroUpper64(res));
}
}
else else
{ {
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
@ -1286,11 +1365,19 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {
@ -1301,9 +1388,15 @@ namespace ARMeilleure.Instructions
} }
else /* if (sizeF == 1) */ else /* if (sizeF == 1) */
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
}
context.Copy(d, res); context.Copy(d, res);
} }
@ -1339,8 +1432,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {
@ -1355,8 +1455,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
}
context.Copy(d, res); context.Copy(d, res);
} }
@ -1385,17 +1492,33 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn); Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm); Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0) if (op.Size == 0)
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
}
context.Copy(d, context.VectorZeroUpper96(res)); context.Copy(d, context.VectorZeroUpper96(res));
} }
else /* if (op.Size == 1) */ else /* if (op.Size == 1) */
{ {
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); if (Optimizations.UseFma)
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
}
context.Copy(d, context.VectorZeroUpper64(res)); context.Copy(d, context.VectorZeroUpper64(res));
} }
@ -1669,25 +1792,39 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn); Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm); Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0) if (op.Size == 0)
{ {
Operand mask = X86GetScalar(context, -0f); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0f);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); }
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
context.Copy(d, context.VectorZeroUpper96(res)); context.Copy(d, context.VectorZeroUpper96(res));
} }
else /* if (op.Size == 1) */ else /* if (op.Size == 1) */
{ {
Operand mask = X86GetScalar(context, -0d); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0d);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); }
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
context.Copy(d, context.VectorZeroUpper64(res)); context.Copy(d, context.VectorZeroUpper64(res));
} }
@ -1716,25 +1853,39 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn); Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm); Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0) if (op.Size == 0)
{ {
Operand mask = X86GetScalar(context, -0f); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0f);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); }
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
context.Copy(d, context.VectorZeroUpper96(res)); context.Copy(d, context.VectorZeroUpper96(res));
} }
else /* if (op.Size == 1) */ else /* if (op.Size == 1) */
{ {
Operand mask = X86GetScalar(context, -0d); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0d);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); }
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
context.Copy(d, context.VectorZeroUpper64(res)); context.Copy(d, context.VectorZeroUpper64(res));
} }
@ -1830,13 +1981,22 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand mask = X86GetScalar(context, 2f); Operand mask = X86GetScalar(context, 2f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
@ -1845,9 +2005,16 @@ namespace ARMeilleure.Instructions
{ {
Operand mask = X86GetScalar(context, 2d); Operand mask = X86GetScalar(context, 2d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
@ -1877,14 +2044,23 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand mask = X86GetAllElements(context, 2f); Operand mask = X86GetAllElements(context, 2f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); if (Optimizations.UseFma)
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res); res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
if (op.RegisterSize == RegisterSize.Simd64) if (op.RegisterSize == RegisterSize.Simd64)
{ {
@ -1897,10 +2073,17 @@ namespace ARMeilleure.Instructions
{ {
Operand mask = X86GetAllElements(context, 2d); Operand mask = X86GetAllElements(context, 2d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); if (Optimizations.UseFma)
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); {
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res); res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
context.Copy(GetVec(op.Rd), res); context.Copy(GetVec(op.Rd), res);
} }
@ -2113,20 +2296,32 @@ namespace ARMeilleure.Instructions
public static void Frintx_S(ArmEmitterContext context) public static void Frintx_S(ArmEmitterContext context)
{ {
// TODO Arm64: Fast path. Should we set host FPCR? if (Optimizations.UseAdvSimd)
EmitScalarUnaryOpF(context, (op1) =>
{ {
return EmitRoundByRMode(context, op1); InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS);
}); }
else
{
EmitScalarUnaryOpF(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
} }
public static void Frintx_V(ArmEmitterContext context) public static void Frintx_V(ArmEmitterContext context)
{ {
// TODO Arm64: Fast path. Should we set host FPCR? if (Optimizations.UseAdvSimd)
EmitVectorUnaryOpF(context, (op1) =>
{ {
return EmitRoundByRMode(context, op1); InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV);
}); }
else
{
EmitVectorUnaryOpF(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
} }
public static void Frintz_S(ArmEmitterContext context) public static void Frintz_S(ArmEmitterContext context)
@ -2237,16 +2432,25 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand maskHalf = X86GetScalar(context, 0.5f); Operand maskHalf = X86GetScalar(context, 0.5f);
Operand maskThree = X86GetScalar(context, 3f); Operand maskThree = X86GetScalar(context, 3f);
Operand maskOneHalf = X86GetScalar(context, 1.5f); Operand maskOneHalf = X86GetScalar(context, 1.5f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res); res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
@ -2257,10 +2461,17 @@ namespace ARMeilleure.Instructions
Operand maskThree = X86GetScalar(context, 3d); Operand maskThree = X86GetScalar(context, 3d);
Operand maskOneHalf = X86GetScalar(context, 1.5d); Operand maskOneHalf = X86GetScalar(context, 1.5d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res); res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
@ -2290,15 +2501,24 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1; int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0) if (sizeF == 0)
{ {
Operand maskHalf = X86GetAllElements(context, 0.5f); Operand maskHalf = X86GetAllElements(context, 0.5f);
Operand maskThree = X86GetAllElements(context, 3f); Operand maskThree = X86GetAllElements(context, 3f);
Operand maskOneHalf = X86GetAllElements(context, 1.5f); Operand maskOneHalf = X86GetAllElements(context, 1.5f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
@ -2315,9 +2535,16 @@ namespace ARMeilleure.Instructions
Operand maskThree = X86GetAllElements(context, 3d); Operand maskThree = X86GetAllElements(context, 3d);
Operand maskOneHalf = X86GetAllElements(context, 1.5d); Operand maskOneHalf = X86GetAllElements(context, 1.5d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
@ -4728,53 +4955,6 @@ namespace ARMeilleure.Instructions
} }
} }
public static Operand EmitSseOrAvxHandleFzModeOpF(
ArmEmitterContext context,
Func2I emit,
bool scalar,
Operand n = default,
Operand m = default)
{
Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n;
Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m;
EmitSseOrAvxEnterFtzAndDazModesOpF(context, out Operand isTrue);
Operand res = emit(nCopy, mCopy);
EmitSseOrAvxExitFtzAndDazModesOpF(context, isTrue);
if (n != default || m != default)
{
return res;
}
int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1;
if (sizeF == 0)
{
if (scalar)
{
res = context.VectorZeroUpper96(res);
}
else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
}
else /* if (sizeF == 1) */
{
if (scalar)
{
res = context.VectorZeroUpper64(res);
}
}
context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res);
return default;
}
private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax) private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax)
{ {
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
@ -4834,10 +5014,7 @@ namespace ARMeilleure.Instructions
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, op1, op2);
}, scalar: scalar, nCopy, mCopy); }, scalar: scalar, nCopy, mCopy);
if (n != default || m != default) if (n != default || m != default)
@ -4872,10 +5049,7 @@ namespace ARMeilleure.Instructions
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{ {
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) => return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, op1, op2);
}, scalar: scalar, nCopy, mCopy); }, scalar: scalar, nCopy, mCopy);
if (n != default || m != default) if (n != default || m != default)

View File

@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)) ? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)); : typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
context.ExitArmFpMode();
context.StoreToContext(); context.StoreToContext();
Operand res = context.Call(method, src); Operand res = context.Call(method, src);
context.LoadFromContext(); context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar16(context, op.Vd, op.T, res); InsertScalar16(context, op.Vd, op.T, res);
} }
@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)) ? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)); : typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
context.ExitArmFpMode();
context.StoreToContext(); context.StoreToContext();
Operand res = context.Call(method, src); Operand res = context.Call(method, src);
context.LoadFromContext(); context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar(context, op.Vd, res); InsertScalar(context, op.Vd, res);
} }
@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
// VRINTX (floating-point). // VRINTX (floating-point).
public static void Vrintx_S(ArmEmitterContext context) public static void Vrintx_S(ArmEmitterContext context)
{ {
EmitScalarUnaryOpF32(context, (op1) => if (Optimizations.UseAdvSimd)
{ {
return EmitRoundByRMode(context, op1); InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS);
}); }
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
} }
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed) private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)

View File

@ -1,3 +1,4 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.Decoders; using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State; using ARMeilleure.State;
@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
}; };
#endregion #endregion
public static void EnterArmFpMode(EmitterContext context, Func<FPState, Operand> getFpFlag)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
Operand fzTrue = getFpFlag(FPState.FzFlag);
Operand r0True = getFpFlag(FPState.RMode0Flag);
Operand r1True = getFpFlag(FPState.RMode1Flag);
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0)));
// X86 round modes in order: nearest, negative, positive, zero
// ARM round modes in order: nearest, positive, negative, zero
// Read the bits backwards to correct this.
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0)));
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0)));
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
Operand fzTrue = getFpFlag(FPState.FzFlag);
Operand r0True = getFpFlag(FPState.RMode0Flag);
Operand r1True = getFpFlag(FPState.RMode1Flag);
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0)));
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
// TODO: Restore FPSR
}
}
public static void ExitArmFpMode(EmitterContext context, Action<FPState, Operand> setFpFlag)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
// Unset round mode (to nearest) and ftz.
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
// Status flags would be stored here if they were used.
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
// Unset round mode (to nearest) and fz.
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
// TODO: Store FPSR
}
}
public static int GetImmShl(OpCodeSimdShImm op) public static int GetImmShl(OpCodeSimdShImm op)
{ {
return op.Imm - (8 << op.Size); return op.Imm - (8 << op.Size);
@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat32).GetMethod(name) ? typeof(SoftFloat32).GetMethod(name)
: typeof(SoftFloat64).GetMethod(name); : typeof(SoftFloat64).GetMethod(name);
context.ExitArmFpMode();
context.StoreToContext(); context.StoreToContext();
Operand res = context.Call(info, callArgs); Operand res = context.Call(info, callArgs);
context.LoadFromContext(); context.LoadFromContext();
context.EnterArmFpMode();
return res; return res;
} }
@ -1358,39 +1430,6 @@ namespace ARMeilleure.Instructions
} }
} }
[Flags]
public enum Mxcsr
{
Ftz = 1 << 15, // Flush To Zero.
Um = 1 << 11, // Underflow Mask.
Dm = 1 << 8, // Denormal Mask.
Daz = 1 << 6 // Denormals Are Zero.
}
public static void EmitSseOrAvxEnterFtzAndDazModesOpF(ArmEmitterContext context, out Operand isTrue)
{
isTrue = GetFpFlag(FPState.FzFlag);
Operand lblTrue = Label();
context.BranchIfFalse(lblTrue, isTrue);
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrmb, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm | Mxcsr.Daz)));
context.MarkLabel(lblTrue);
}
public static void EmitSseOrAvxExitFtzAndDazModesOpF(ArmEmitterContext context, Operand isTrue = default)
{
isTrue = isTrue == default ? GetFpFlag(FPState.FzFlag) : isTrue;
Operand lblTrue = Label();
context.BranchIfFalse(lblTrue, isTrue);
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrub, Const((int)(Mxcsr.Ftz | Mxcsr.Daz)));
context.MarkLabel(lblTrue);
}
public enum CmpCondition public enum CmpCondition
{ {
// Legacy Sse. // Legacy Sse.

View File

@ -1197,9 +1197,11 @@ namespace ARMeilleure.Instructions
Array.Resize(ref callArgs, callArgs.Length + 1); Array.Resize(ref callArgs, callArgs.Length + 1);
callArgs[callArgs.Length - 1] = Const(1); callArgs[callArgs.Length - 1] = Const(1);
context.ExitArmFpMode();
context.StoreToContext(); context.StoreToContext();
Operand res = context.Call(info, callArgs); Operand res = context.Call(info, callArgs);
context.LoadFromContext(); context.LoadFromContext();
context.EnterArmFpMode();
return res; return res;
} }

View File

@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
public static void Not_V(ArmEmitterContext context) public static void Not_V(ArmEmitterContext context)
{ {
if (Optimizations.UseSse2) if (Optimizations.UseAvx512Ortho)
{
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
Operand n = GetVec(op.Rn);
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
if (op.RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
context.Copy(GetVec(op.Rd), res);
}
else if (Optimizations.UseSse2)
{ {
OpCodeSimd op = (OpCodeSimd)context.CurrOp; OpCodeSimd op = (OpCodeSimd)context.CurrOp;
@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
{ {
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV); InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
} }
else if (Optimizations.UseAvx512Ortho)
{
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
if (op.RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
context.Copy(GetVec(op.Rd), res);
}
else if (Optimizations.UseSse2) else if (Optimizations.UseSse2)
{ {
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;

View File

@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
{ {
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m)); InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
} }
else if (Optimizations.UseAvx512Ortho)
{
EmitVectorBinaryOpSimd32(context, (n, m) =>
{
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
});
}
else if (Optimizations.UseSse2) else if (Optimizations.UseSse2)
{ {
Operand mask = context.VectorOne(); Operand mask = context.VectorOne();

View File

@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
public static void Vmvn_I(ArmEmitterContext context) public static void Vmvn_I(ArmEmitterContext context)
{ {
if (Optimizations.UseSse2) if (Optimizations.UseAvx512Ortho)
{
EmitVectorUnaryOpSimd32(context, (op1) =>
{
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
});
}
else if (Optimizations.UseSse2)
{ {
EmitVectorUnaryOpSimd32(context, (op1) => EmitVectorUnaryOpSimd32(context, (op1) =>
{ {

View File

@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return; case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return; case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
case 0b11_011_0100_0100_001: EmitGetFpsr(context); return; case 0b11_011_0100_0100_001: EmitGetFpsr(context); return;
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break; case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return;
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break; case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return;
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break; case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break; case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break; case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
@ -49,19 +49,15 @@ namespace ARMeilleure.Instructions
{ {
OpCodeSystem op = (OpCodeSystem)context.CurrOp; OpCodeSystem op = (OpCodeSystem)context.CurrOp;
MethodInfo info;
switch (GetPackedId(op)) switch (GetPackedId(op))
{ {
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return; case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return; case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
case 0b11_011_0100_0100_001: EmitSetFpsr(context); return; case 0b11_011_0100_0100_001: EmitSetFpsr(context); return;
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)); break; case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return;
default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
} }
context.Call(info, GetIntOrZR(context, op.Rt));
} }
public static void Nop(ArmEmitterContext context) public static void Nop(ArmEmitterContext context)
@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
SetIntOrZR(context, op.Rt, fpsr); SetIntOrZR(context, op.Rt, fpsr);
} }
private static void EmitGetTpidrEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
SetIntOrZR(context, op.Rt, result);
}
private static void EmitGetTpidrroEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
SetIntOrZR(context, op.Rt, result);
}
private static void EmitSetNzcv(ArmEmitterContext context) private static void EmitSetNzcv(ArmEmitterContext context)
{ {
OpCodeSystem op = (OpCodeSystem)context.CurrOp; OpCodeSystem op = (OpCodeSystem)context.CurrOp;
@ -192,6 +210,8 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1))); SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
} }
} }
context.UpdateArmFpMode();
} }
private static void EmitSetFpsr(ArmEmitterContext context) private static void EmitSetFpsr(ArmEmitterContext context)
@ -210,6 +230,19 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1))); SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1)));
} }
} }
context.UpdateArmFpMode();
}
private static void EmitSetTpidrEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand value = GetIntOrZR(context, op.Rt);
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
} }
} }
} }

View File

@ -23,8 +23,6 @@ namespace ARMeilleure.Instructions
return; return;
} }
MethodInfo info;
switch (op.CRn) switch (op.CRn)
{ {
case 13: // Process and Thread Info. case 13: // Process and Thread Info.
@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
switch (op.Opc2) switch (op.Opc2)
{ {
case 2: case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break; EmitSetTpidrEl0(context); return;
default: default:
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
} }
break;
case 7: case 7:
switch (op.CRm) // Cache and Memory barrier. switch (op.CRm) // Cache and Memory barrier.
{ {
@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
default: default:
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
} }
context.Call(info, GetIntA32(context, op.Rt));
} }
public static void Mrc(ArmEmitterContext context) public static void Mrc(ArmEmitterContext context)
@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
return; return;
} }
MethodInfo info; Operand result;
switch (op.CRn) switch (op.CRn)
{ {
@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
switch (op.Opc2) switch (op.Opc2)
{ {
case 2: case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break; result = EmitGetTpidrEl0(context); break;
case 3: case 3:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break; result = EmitGetTpidrroEl0(context); break;
default: default:
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
@ -110,13 +104,13 @@ namespace ARMeilleure.Instructions
if (op.Rt == RegisterAlias.Aarch32Pc) if (op.Rt == RegisterAlias.Aarch32Pc)
{ {
// Special behavior: copy NZCV flags into APSR. // Special behavior: copy NZCV flags into APSR.
EmitSetNzcv(context, context.Call(info)); EmitSetNzcv(context, result);
return; return;
} }
else else
{ {
SetIntA32(context, op.Rt, context.Call(info)); SetIntA32(context, op.Rt, result);
} }
} }
@ -321,6 +315,37 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1))); SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1)));
} }
} }
context.UpdateArmFpMode();
}
private static Operand EmitGetTpidrEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
}
private static Operand EmitGetTpidrroEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
}
private static void EmitSetTpidrEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand value = GetIntA32(context, op.Rt);
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value));
} }
} }
} }

View File

@ -72,26 +72,6 @@ namespace ARMeilleure.Instructions
return (ulong)GetContext().DczidEl0; return (ulong)GetContext().DczidEl0;
} }
public static ulong GetTpidrEl0()
{
return (ulong)GetContext().TpidrEl0;
}
public static uint GetTpidrEl032()
{
return (uint)GetContext().TpidrEl0;
}
public static ulong GetTpidrroEl0()
{
return (ulong)GetContext().TpidrroEl0;
}
public static uint GetTpidr32()
{
return (uint)GetContext().TpidrroEl0;
}
public static ulong GetCntfrqEl0() public static ulong GetCntfrqEl0()
{ {
return GetContext().CntfrqEl0; return GetContext().CntfrqEl0;
@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
{ {
return GetContext().CntvctEl0; return GetContext().CntvctEl0;
} }
public static void SetTpidrEl0(ulong value)
{
GetContext().TpidrEl0 = (long)value;
}
public static void SetTpidrEl032(uint value)
{
GetContext().TpidrEl0 = (long)value;
}
#endregion #endregion
#region "Read" #region "Read"

View File

@ -53,6 +53,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Haddpd, X86Haddpd,
X86Haddps, X86Haddps,
X86Insertps, X86Insertps,
X86Ldmxcsr,
X86Maxpd, X86Maxpd,
X86Maxps, X86Maxps,
X86Maxsd, X86Maxsd,
@ -68,8 +69,6 @@ namespace ARMeilleure.IntermediateRepresentation
X86Mulps, X86Mulps,
X86Mulsd, X86Mulsd,
X86Mulss, X86Mulss,
X86Mxcsrmb,
X86Mxcsrub,
X86Paddb, X86Paddb,
X86Paddd, X86Paddd,
X86Paddq, X86Paddq,
@ -153,6 +152,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Sqrtps, X86Sqrtps,
X86Sqrtsd, X86Sqrtsd,
X86Sqrtss, X86Sqrtss,
X86Stmxcsr,
X86Subpd, X86Subpd,
X86Subps, X86Subps,
X86Subsd, X86Subsd,
@ -163,16 +163,19 @@ namespace ARMeilleure.IntermediateRepresentation
X86Unpcklps, X86Unpcklps,
X86Vcvtph2ps, X86Vcvtph2ps,
X86Vcvtps2ph, X86Vcvtps2ph,
X86Vfmadd231pd,
X86Vfmadd231ps, X86Vfmadd231ps,
X86Vfmadd231sd, X86Vfmadd231sd,
X86Vfmadd231ss, X86Vfmadd231ss,
X86Vfmsub231sd, X86Vfmsub231sd,
X86Vfmsub231ss, X86Vfmsub231ss,
X86Vfnmadd231pd,
X86Vfnmadd231ps, X86Vfnmadd231ps,
X86Vfnmadd231sd, X86Vfnmadd231sd,
X86Vfnmadd231ss, X86Vfnmadd231ss,
X86Vfnmsub231sd, X86Vfnmsub231sd,
X86Vfnmsub231ss, X86Vfnmsub231ss,
X86Vpternlogd,
X86Xorpd, X86Xorpd,
X86Xorps, X86Xorps,
@ -393,6 +396,8 @@ namespace ARMeilleure.IntermediateRepresentation
Arm64MlsVe, Arm64MlsVe,
Arm64MlsV, Arm64MlsV,
Arm64MoviV, Arm64MoviV,
Arm64MrsFpcr,
Arm64MsrFpcr,
Arm64MrsFpsr, Arm64MrsFpsr,
Arm64MsrFpsr, Arm64MsrFpsr,
Arm64MulVe, Arm64MulVe,

View File

@ -23,6 +23,10 @@ namespace ARMeilleure
public static bool UseSse42IfAvailable { get; set; } = true; public static bool UseSse42IfAvailable { get; set; } = true;
public static bool UsePopCntIfAvailable { get; set; } = true; public static bool UsePopCntIfAvailable { get; set; } = true;
public static bool UseAvxIfAvailable { get; set; } = true; public static bool UseAvxIfAvailable { get; set; } = true;
public static bool UseAvx512FIfAvailable { get; set; } = true;
public static bool UseAvx512VlIfAvailable { get; set; } = true;
public static bool UseAvx512BwIfAvailable { get; set; } = true;
public static bool UseAvx512DqIfAvailable { get; set; } = true;
public static bool UseF16cIfAvailable { get; set; } = true; public static bool UseF16cIfAvailable { get; set; } = true;
public static bool UseFmaIfAvailable { get; set; } = true; public static bool UseFmaIfAvailable { get; set; } = true;
public static bool UseAesniIfAvailable { get; set; } = true; public static bool UseAesniIfAvailable { get; set; } = true;
@ -47,11 +51,18 @@ namespace ARMeilleure
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42; internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt; internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse; internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c; internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma; internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni; internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq; internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha; internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni; internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
} }
} }

View File

@ -27,8 +27,17 @@ namespace ARMeilleure.State
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0 // Since EL2 isn't implemented, CNTVOFF_EL2 = 0
public ulong CntvctEl0 => CntpctEl0; public ulong CntvctEl0 => CntpctEl0;
public long TpidrEl0 { get; set; } public long TpidrEl0
public long TpidrroEl0 { get; set; } {
get => _nativeContext.GetTpidrEl0();
set => _nativeContext.SetTpidrEl0(value);
}
public long TpidrroEl0
{
get => _nativeContext.GetTpidrroEl0();
set => _nativeContext.SetTpidrroEl0(value);
}
public uint Pstate public uint Pstate
{ {

View File

@ -13,6 +13,8 @@ namespace ARMeilleure.State
public fixed ulong V[RegisterConsts.VecRegsCount * 2]; public fixed ulong V[RegisterConsts.VecRegsCount * 2];
public fixed uint Flags[RegisterConsts.FlagsCount]; public fixed uint Flags[RegisterConsts.FlagsCount];
public fixed uint FpFlags[RegisterConsts.FpFlagsCount]; public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
public long TpidrEl0;
public long TpidrroEl0;
public int Counter; public int Counter;
public ulong DispatchAddress; public ulong DispatchAddress;
public ulong ExclusiveAddress; public ulong ExclusiveAddress;
@ -168,6 +170,12 @@ namespace ARMeilleure.State
} }
} }
public long GetTpidrEl0() => GetStorage().TpidrEl0;
public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value;
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
public int GetCounter() => GetStorage().Counter; public int GetCounter() => GetStorage().Counter;
public void SetCounter(int value) => GetStorage().Counter = value; public void SetCounter(int value) => GetStorage().Counter = value;
@ -214,6 +222,16 @@ namespace ARMeilleure.State
} }
} }
public static int GetTpidrEl0Offset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0);
}
public static int GetTpidrroEl0Offset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
}
public static int GetCounterOffset() public static int GetCounterOffset()
{ {
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter); return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);

View File

@ -188,6 +188,21 @@ namespace ARMeilleure.Translation
} }
} }
public void EnterArmFpMode()
{
InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag);
}
public void UpdateArmFpMode()
{
EnterArmFpMode();
}
public void ExitArmFpMode()
{
InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value));
}
public Operand TryGetComparisonResult(Condition condition) public Operand TryGetComparisonResult(Condition condition)
{ {
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet) if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)

View File

@ -105,17 +105,11 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));

View File

@ -3,4 +3,5 @@
namespace ARMeilleure.Translation namespace ARMeilleure.Translation
{ {
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress);
} }

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 = 4484; //! To be incremented manually for each change to the ARMeilleure project. private const uint InternalVersion = 4661; //! 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";
@ -969,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap, (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2, (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo, (ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
0,
0); 0);
} }
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
@ -977,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx, (ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
(ulong)X86HardwareCapabilities.FeatureInfo1Edx, (ulong)X86HardwareCapabilities.FeatureInfo1Edx,
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx, (ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx); (ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
} }
else else
{ {
return new FeatureInfo(0, 0, 0, 0); return new FeatureInfo(0, 0, 0, 0, 0);
} }
} }
@ -1002,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform; return osPlatform;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)] [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
private struct OuterHeader private struct OuterHeader
{ {
public ulong Magic; public ulong Magic;
@ -1034,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
} }
} }
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)] [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3); private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)] [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
private struct InnerHeader private struct InnerHeader

View File

@ -25,5 +25,10 @@ namespace ARMeilleure.Translation
{ {
return _func(context.NativeContextPtr); return _func(context.NativeContextPtr);
} }
public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context)
{
return dispatcher(context.NativeContextPtr, (ulong)FuncPointer);
}
} }
} }

View File

@ -183,7 +183,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer(); Statistics.StartTimer();
ulong nextAddr = func.Execute(context); ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address); Statistics.StopTimer(address);
@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
{ {
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context); address = func.Execute(Stubs.ContextWrapper, context);
EnqueueForDeletion(address, func); EnqueueForDeletion(address, func);

View File

@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
private readonly Translator _translator; private readonly Translator _translator;
private readonly Lazy<IntPtr> _dispatchStub; private readonly Lazy<IntPtr> _dispatchStub;
private readonly Lazy<DispatcherFunction> _dispatchLoop; private readonly Lazy<DispatcherFunction> _dispatchLoop;
private readonly Lazy<WrapperFunction> _contextWrapper;
/// <summary> /// <summary>
/// Gets the dispatch stub. /// Gets the dispatch stub.
@ -64,6 +65,20 @@ namespace ARMeilleure.Translation
} }
} }
/// <summary>
/// Gets the context wrapper function.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
public WrapperFunction ContextWrapper
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _contextWrapper.Value;
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified /// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance. /// <see cref="Translator"/> instance.
@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
_translator = translator; _translator = translator;
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
} }
/// <summary> /// <summary>
@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
return Marshal.GetFunctionPointerForDelegate(func); return Marshal.GetFunctionPointerForDelegate(func);
} }
/// <summary>
/// Emits code that syncs FP state before executing guest code, or returns it to normal.
/// </summary>
/// <param name="context">Emitter context for the method</param>
/// <param name="nativeContext">Pointer to the native context</param>
/// <param name="enter">True if entering guest code, false otherwise</param>
private void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
{
if (enter)
{
InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
return context.Load(OperandType.I32, flagAddress);
});
}
else
{
InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
context.Store(flagAddress, value);
});
}
}
/// <summary> /// <summary>
/// Generates a <see cref="DispatchLoop"/> function. /// Generates a <see cref="DispatchLoop"/> function.
/// </summary> /// </summary>
@ -221,6 +263,8 @@ 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()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl); context.MarkLabel(beginLbl);
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));
@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
context.Branch(beginLbl); context.Branch(beginLbl);
context.MarkLabel(endLbl); context.MarkLabel(endLbl);
EmitSyncFpContext(context, nativeContext, false);
context.Return(); context.Return();
var cfg = context.GetControlFlowGraph(); var cfg = context.GetControlFlowGraph();
@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(); return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
} }
/// <summary>
/// Generates a <see cref="ContextWrapper"/> function.
/// </summary>
/// <returns><see cref="ContextWrapper"/> function</returns>
private WrapperFunction GenerateContextWrapper()
{
var context = new EmitterContext();
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
EmitSyncFpContext(context, nativeContext, true);
Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
EmitSyncFpContext(context, nativeContext, false);
context.Return(returnValue);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
}
} }
} }

View File

@ -0,0 +1,148 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Translation
{
public static class TranslatorTestMethods
{
public delegate int FpFlagsPInvokeTest(IntPtr managedMethod);
private static bool SetPlatformFtz(EmitterContext context, bool ftz)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
if (ftz)
{
mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm)));
}
else
{
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz));
}
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
return true;
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
if (ftz)
{
fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz));
}
else
{
fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz));
}
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
return true;
}
else
{
return false;
}
}
private static Operand FpBitsToInt(EmitterContext context, Operand fp)
{
Operand vec = context.VectorInsert(context.VectorZero(), fp, 0);
return context.VectorExtract(OperandType.I32, vec, 0);
}
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
{
EmitterContext context = new EmitterContext();
Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0));
// Verify that default dotnet fp state does not flush to zero.
// This is required for SoftFloat to function.
// Denormal + zero != 0
Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45
Operand zeroF = ConstF(0f);
Operand zero = Const(0);
Operand result = context.Add(zeroF, denormal);
// Must not be zero.
Operand correct1Label = Label();
context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero));
context.Return(Const(1));
context.MarkLabel(correct1Label);
// Set flush to zero flag. If unsupported by the backend, just return true.
if (!SetPlatformFtz(context, true))
{
context.Return(Const(0));
}
// Denormal + zero == 0
Operand resultFz = context.Add(zeroF, denormal);
// Must equal zero.
Operand correct2Label = Label();
context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero));
SetPlatformFtz(context, false);
context.Return(Const(2));
context.MarkLabel(correct2Label);
// Call a managed method. This method should not change Fz state.
context.Call(methodAddress, OperandType.None);
// Denormal + zero == 0
Operand resultFz2 = context.Add(zeroF, denormal);
// Must equal zero.
Operand correct3Label = Label();
context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero));
SetPlatformFtz(context, false);
context.Return(Const(3));
context.MarkLabel(correct3Label);
// Success.
SetPlatformFtz(context, false);
context.Return(Const(0));
// Compile and return the function.
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
}
}
}

View File

@ -3,17 +3,17 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.18" /> <PackageVersion Include="Avalonia" Version="0.10.19" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" /> <PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" /> <PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" /> <PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" /> <PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.12.11" /> <PackageVersion Include="DynamicData" Version="7.13.5" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
@ -34,7 +34,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<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.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
<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" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
@ -44,10 +44,10 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" /> <PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" /> <PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.0" /> <PackageVersion Include="System.Management" Version="7.0.1" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" /> <PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -40,7 +40,7 @@
## Compatibility ## Compatibility
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable. As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already! You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
## Usage ## Usage

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private ulong _playedSampleCount; private ulong _playedSampleCount;
private ManualResetEvent _updateRequiredEvent; private ManualResetEvent _updateRequiredEvent;
private uint _outputStream; private uint _outputStream;
private bool _hasSetupError;
private SDL_AudioCallback _callbackDelegate; private SDL_AudioCallback _callbackDelegate;
private int _bytesPerFrame; private int _bytesPerFrame;
private uint _sampleCount; private uint _sampleCount;
@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private void EnsureAudioStreamSetup(AudioBuffer buffer) private void EnsureAudioStreamSetup(AudioBuffer buffer)
{ {
uint bufferSampleCount = (uint)GetSampleCount(buffer); uint bufferSampleCount = (uint)GetSampleCount(buffer);
bool needAudioSetup = _outputStream == 0 || bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
if (needAudioSetup) if (needAudioSetup)
@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
if (newOutputStream == 0) _hasSetupError = newOutputStream == 0;
{
// No stream in place, this is unexpected. if (!_hasSetupError)
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
}
else
{ {
if (_outputStream != 0) if (_outputStream != 0)
{ {
@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
EnsureAudioStreamSetup(buffer); EnsureAudioStreamSetup(buffer);
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer)); if (_outputStream != 0)
{
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_queuedBuffers.Enqueue(driverBuffer); _queuedBuffers.Enqueue(driverBuffer);
}
else
{
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
_updateRequiredEvent.Set();
}
} }
public override void SetVolume(float volume) public override void SetVolume(float volume)

View File

@ -177,6 +177,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
} }
@ -320,14 +322,16 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true; _viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}"; var activeProcess = Device.Processes.ActiveApplication;
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})"; string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; _viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
}); });
_viewModel.SetUIProgressHandlers(Device); _viewModel.SetUIProgressHandlers(Device);
@ -381,6 +385,11 @@ namespace Ryujinx.Ava
}); });
} }
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
}
public void Stop() public void Stop()
{ {
_isActive = false; _isActive = false;
@ -423,9 +432,9 @@ namespace Ryujinx.Ava
private void Dispose() private void Dispose()
{ {
if (Device.Application != null) if (Device.Processes != null)
{ {
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText); _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
} }
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
@ -539,7 +548,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
Device.LoadNca(ApplicationPath); if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
else if (Directory.Exists(ApplicationPath)) else if (Directory.Exists(ApplicationPath))
{ {
@ -554,13 +568,23 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
Device.LoadCart(ApplicationPath, romFsFiles[0]); if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
{
Device.Dispose();
return false;
}
} }
else else
{ {
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
Device.LoadCart(ApplicationPath); if (!Device.LoadCart(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
} }
else if (File.Exists(ApplicationPath)) else if (File.Exists(ApplicationPath))
@ -571,7 +595,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as XCI."); Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
Device.LoadXci(ApplicationPath); if (!Device.LoadXci(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@ -579,7 +608,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as NCA."); Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
Device.LoadNca(ApplicationPath); if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@ -588,7 +622,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as NSP."); Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
Device.LoadNsp(ApplicationPath); if (!Device.LoadNsp(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@ -598,13 +637,18 @@ namespace Ryujinx.Ava
try try
{ {
Device.LoadProgram(ApplicationPath); if (!Device.LoadProgram(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
{ {
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
Dispose(); Device.Dispose();
return false; return false;
} }
@ -617,14 +661,14 @@ namespace Ryujinx.Ava
{ {
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Dispose(); Device.Dispose();
return false; return false;
} }
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{ {
appMetadata.LastPlayed = DateTime.UtcNow.ToString(); appMetadata.LastPlayed = DateTime.UtcNow.ToString();
}); });
@ -702,7 +746,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.IgnoreMissingServices, ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor); ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Device = new Switch(configuration); Device = new Switch(configuration);
} }
@ -950,7 +995,7 @@ namespace Ryujinx.Ava
{ {
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{ {
Device.Application.DiskCacheLoadState?.Cancel(); Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
} }
}); });
@ -1088,4 +1133,4 @@ namespace Ryujinx.Ava
return state; return state;
} }
} }
} }

View File

@ -429,6 +429,7 @@
"DlcManagerEnableAllButton": "Enable All", "DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All", "DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language", "MenuBarOptionsChangeLanguage": "Change Language",
"MenuBarShowFileTypes": "Show File Types",
"CommonSort": "Sort", "CommonSort": "Sort",
"CommonShowNames": "Show Names", "CommonShowNames": "Show Names",
"CommonFavorite": "Favorite", "CommonFavorite": "Favorite",
@ -637,5 +638,8 @@
"SmaaHigh": "SMAA High", "SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra", "SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User", "UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User" "UserEditorTitleCreate" : "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default"
} }

View File

@ -13,12 +13,14 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Buffers; using System.Buffers;
@ -151,25 +153,17 @@ namespace Ryujinx.Ava.Common
string destination = await folderDialog.ShowAsync(_owner); string destination = await folderDialog.ShowAsync(_owner);
var cancellationToken = new CancellationTokenSource(); var cancellationToken = new CancellationTokenSource();
UpdateWaitWindow waitingDialog = new(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
cancellationToken);
if (!string.IsNullOrWhiteSpace(destination)) if (!string.IsNullOrWhiteSpace(destination))
{ {
Thread extractorThread = new(() => Thread extractorThread = new(() =>
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(waitingDialog.Show);
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
if (result == UserResult.Cancel)
{
cancellationToken.Cancel();
}
});
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null; Nca mainNca = null;
@ -221,13 +215,15 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
}); });
return; return;
} }
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null) if (updatePatchNca != null)
{ {
patchNca = updatePatchNca; patchNca = updatePatchNca;
@ -262,11 +258,15 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
}); });
} }
else if (resultCode.Value.IsSuccess()) else if (resultCode.Value.IsSuccess())
{ {
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show( NotificationHelper.Show(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle], LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}", $"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
@ -283,6 +283,8 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(ex.Message); await ContentDialogHelper.CreateErrorDialog(ex.Message);
}); });
} }

View File

@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
{ {
var localeStrings = new Dictionary<LocaleKeys, string>(); var localeStrings = new Dictionary<LocaleKeys, string>();
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson); var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
foreach (var item in strings) foreach (var item in strings)
{ {

View File

@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Ava; using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.Models.Github;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -31,6 +32,7 @@ namespace Ryujinx.Modules
internal static class Updater internal static class Updater
{ {
private const string GitHubApiURL = "https://api.github.com"; private const string GitHubApiURL = "https://api.github.com";
private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
@ -99,22 +101,16 @@ namespace Ryujinx.Modules
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson); var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
JToken assets = jsonRoot["assets"]; _buildVer = fetched.Name;
_buildVer = (string)jsonRoot["name"]; foreach (var asset in fetched.Assets)
foreach (JToken asset in assets)
{ {
string assetName = (string)asset["name"]; if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
string assetState = (string)asset["state"];
string downloadURL = (string)asset["browser_download_url"];
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
{ {
_buildUrl = downloadURL; _buildUrl = asset.BrowserDownloadUrl;
if (assetState != "uploaded") if (asset.State != "uploaded")
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
@ -734,7 +730,7 @@ namespace Ryujinx.Modules
} }
} }
return files; return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
} }
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)

View File

@ -53,6 +53,8 @@ namespace Ryujinx.Ava.UI.Applet
bool opened = false; bool opened = false;
_parent.Activate();
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent, UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title, title,
message, message,

View File

@ -48,6 +48,7 @@
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
Focusable="True"
KeyUp="Message_KeyUp" KeyUp="Message_KeyUp"
Text="{Binding Message}" Text="{Binding Message}"
TextInput="Message_TextInput" TextInput="Message_TextInput"
@ -61,4 +62,4 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
private ContentDialog _host; private ContentDialog _host;
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder) public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
{ {
MainText = mainText; MainText = mainText;
SecondaryText = secondaryText; SecondaryText = secondaryText;
Message = message ?? "";
DataContext = this; DataContext = this;
_placeholder = placeholder; _placeholder = placeholder;
InitializeComponent(); InitializeComponent();
@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
InitializeComponent(); InitializeComponent();
} }
protected override void OnGotFocus(GotFocusEventArgs e)
{
// FIXME: This does not work. Might be a bug in Avalonia with DialogHost
// Currently focus will be redirected to the overlay window instead.
Input.Focus();
}
public string Message { get; set; } = ""; public string Message { get; set; } = "";
public string MainText { get; set; } = ""; public string MainText { get; set; } = "";
public string SecondaryText { get; set; } = ""; public string SecondaryText { get; set; } = "";
@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
UserResult result = UserResult.Cancel; UserResult result = UserResult.Cancel;
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText) SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
{
Message = args.InitialText ?? ""
};
string input = string.Empty; string input = string.Empty;
var overlay = new ContentDialogOverlayWindow()
{
Height = window.Bounds.Height,
Width = window.Bounds.Width,
Position = window.PointToScreen(new Point())
};
window.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
{
overlay.Position = window.PointToScreen(new Point());
}
contentDialog = overlay.ContentDialog;
bool opened = false;
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
content._host = contentDialog; content._host = contentDialog;
@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
}; };
contentDialog.Closed += handler; contentDialog.Closed += handler;
overlay.Opened += OverlayOnActivated; await ContentDialogHelper.ShowAsync(contentDialog);
async void OverlayOnActivated(object sender, EventArgs e)
{
if (opened)
{
return;
}
opened = true;
overlay.Position = window.PointToScreen(new Point());
await contentDialog.ShowAsync(overlay);
contentDialog.Closed -= handler;
overlay.Close();
};
await overlay.ShowDialog(window);
return (result, input); return (result, input);
} }

View File

@ -1,32 +0,0 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.InputDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="5,10,5,5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
<TextBox
Grid.Row="1"
Width="300"
Margin="10"
HorizontalAlignment="Center"
MaxLength="{Binding MaxLength}"
Text="{Binding Input, Mode=TwoWay}" />
<TextBlock
Grid.Row="2"
Margin="5,5,5,10"
HorizontalAlignment="Center"
Text="{Binding SubMessage}" />
</Grid>
</UserControl>

View File

@ -1,57 +0,0 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
public partial class InputDialog : UserControl
{
public string Message { get; set; }
public string Input { get; set; }
public string SubMessage { get; set; }
public uint MaxLength { get; }
public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
Message = message;
Input = input;
SubMessage = subMessage;
MaxLength = maxLength;
DataContext = this;
}
public InputDialog()
{
InitializeComponent();
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
UserResult result = UserResult.Cancel;
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
ContentDialog contentDialog = new ContentDialog
{
Title = title,
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.InputDialogOk],
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel],
Content = content,
PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Ok;
input = content.Input;
})
};
await contentDialog.ShowAsync();
return (result, input);
}
}
}

View File

@ -1,15 +1,26 @@
using Avalonia.Controls; using Avalonia.Controls;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using System.Threading;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
{ {
public partial class UpdateWaitWindow : StyleableWindow public partial class UpdateWaitWindow : StyleableWindow
{ {
public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText)
{
SystemDecorations = SystemDecorations.Full;
ShowInTaskbar = true;
Closing += (_, _) => cancellationToken.Cancel();
}
public UpdateWaitWindow(string primaryText, string secondaryText) : this() public UpdateWaitWindow(string primaryText, string secondaryText) : this()
{ {
PrimaryText.Text = primaryText; PrimaryText.Text = primaryText;
SecondaryText.Text = secondaryText; SecondaryText.Text = secondaryText;
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;
SystemDecorations = SystemDecorations.BorderOnly;
ShowInTaskbar = false;
} }
public UpdateWaitWindow() public UpdateWaitWindow()

View File

@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
string closeButton, string closeButton,
UserResult primaryButtonResult = UserResult.Ok, UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null, ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{ {
UserResult result = UserResult.None; UserResult result = UserResult.None;
@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
int iconSymbol, int iconSymbol,
UserResult primaryButtonResult = UserResult.Ok, UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null, ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{ {
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol); Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction); return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
} }
public async static Task<UserResult> ShowDeferredContentDialog( public async static Task<UserResult> ShowDeferredContentDialog(
@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
iconSymbol, iconSymbol,
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok, primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
deferResetEvent, deferResetEvent,
doWhileDeferred,
DeferClose); DeferClose);
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args) async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
primaryButtonResult); primaryButtonResult);
} }
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
{
return new(mainText, secondaryText);
}
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{ {
await ShowTextDialog( await ShowTextDialog(
@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
} }
internal static async Task<string> CreateInputDialog(
string title,
string mainText,
string subText,
uint maxLength = int.MaxValue,
string input = "")
{
var result = await InputDialog.ShowInputDialog(
title,
mainText,
input,
subText,
maxLength);
if (result.Result == UserResult.Ok)
{
return result.Input;
}
return string.Empty;
}
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog) public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
{ {
ContentDialogResult result; ContentDialogResult result;

View File

@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.UI.Models
{
public class Amiibo
{
public struct AmiiboJson
{
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
}
public struct AmiiboApi
{
[JsonPropertyName("name")] public string Name { get; set; }
[JsonPropertyName("head")] public string Head { get; set; }
[JsonPropertyName("tail")] public string Tail { get; set; }
[JsonPropertyName("image")] public string Image { get; set; }
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
[JsonPropertyName("character")] public string Character { get; set; }
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
public override string ToString()
{
return Name;
}
public string GetId()
{
return Head + Tail;
}
public override bool Equals(object obj)
{
if (obj is AmiiboApi amiibo)
{
return amiibo.Head + amiibo.Tail == Head + Tail;
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
public class AmiiboApiGamesSwitch
{
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
[JsonPropertyName("gameName")] public string GameName { get; set; }
}
public class AmiiboApiUsage
{
[JsonPropertyName("Usage")] public string Usage { get; set; }
[JsonPropertyName("write")] public bool Write { get; set; }
}
}
}

View File

@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n"; Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
} }
catch catch
{ {

View File

@ -4,11 +4,11 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -17,6 +17,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@ -31,8 +32,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly StyleableWindow _owner; private readonly StyleableWindow _owner;
private Bitmap _amiiboImage; private Bitmap _amiiboImage;
private List<Amiibo.AmiiboApi> _amiiboList; private List<AmiiboApi> _amiiboList;
private AvaloniaList<Amiibo.AmiiboApi> _amiibos; private AvaloniaList<AmiiboApi> _amiibos;
private ObservableCollection<string> _amiiboSeries; private ObservableCollection<string> _amiiboSeries;
private int _amiiboSelectedIndex; private int _amiiboSelectedIndex;
@ -41,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _showAllAmiibo; private bool _showAllAmiibo;
private bool _useRandomUuid; private bool _useRandomUuid;
private string _usage; private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId) public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
{ {
@ -52,9 +55,9 @@ namespace Ryujinx.Ava.UI.ViewModels
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
_amiiboList = new List<Amiibo.AmiiboApi>(); _amiiboList = new List<AmiiboApi>();
_amiiboSeries = new ObservableCollection<string>(); _amiiboSeries = new ObservableCollection<string>();
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>(); _amiibos = new AvaloniaList<AmiiboApi>();
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
@ -94,7 +97,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList public AvaloniaList<AmiiboApi> AmiiboList
{ {
get => _amiibos; get => _amiibos;
set set
@ -187,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
if (File.Exists(_amiiboJsonPath)) if (File.Exists(_amiiboJsonPath))
{ {
amiiboJsonString = File.ReadAllText(_amiiboJsonPath); amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated)) if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
{ {
amiiboJsonString = await DownloadAmiiboJson(); amiiboJsonString = await DownloadAmiiboJson();
} }
@ -206,7 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo; _amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData(); ParseAmiiboData();
@ -223,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (!ShowAllAmiibo) if (!ShowAllAmiibo)
{ {
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
{ {
if (game != null) if (game != null)
{ {
@ -255,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void SelectLastScannedAmiibo() private void SelectLastScannedAmiibo()
{ {
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId); AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries); SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
@ -270,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return; return;
} }
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList List<AmiiboApi> amiiboSortedList = _amiiboList
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
.OrderBy(amiibo => amiibo.Name).ToList(); .OrderBy(amiibo => amiibo.Name).ToList();
@ -280,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (!_showAllAmiibo) if (!_showAllAmiibo)
{ {
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
{ {
if (game != null) if (game != null)
{ {
@ -314,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return; return;
} }
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image; string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
@ -326,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
bool writable = false; bool writable = false;
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
{ {
if (item.GameId.Contains(TitleId)) if (item.GameId.Contains(TitleId))
{ {
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage) foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
{ {
usageString += Environment.NewLine + usageString += Environment.NewLine +
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"; $"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";

View File

@ -51,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isLoaded; private bool _isLoaded;
private readonly UserControl _owner; private readonly UserControl _owner;
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepadDriver AvaloniaKeyboardDriver { get; }
public IGamepad SelectedGamepad { get; private set; } public IGamepad SelectedGamepad { get; private set; }
@ -706,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
using (Stream stream = File.OpenRead(path)) config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
{
config = JsonHelper.Deserialize<InputConfig>(stream);
}
} }
catch (JsonException) { } catch (JsonException) { }
catch (InvalidOperationException) catch (InvalidOperationException)
@ -775,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ControllerType = Controllers[_controller].Type; config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, true); string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString); await File.WriteAllTextAsync(path, jsonString);

View File

@ -21,7 +21,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
@ -41,6 +40,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private ulong _titleId; private ulong _titleId;
private string _titleName; private string _titleName;
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<DownloadableContentModel> DownloadableContents public AvaloniaList<DownloadableContentModel> DownloadableContents
{ {
get => _downloadableContents; get => _downloadableContents;
@ -100,7 +101,7 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath); _downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
} }
catch catch
{ {
@ -330,10 +331,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_downloadableContentContainerList.Add(container); _downloadableContentContainerList.Add(container);
} }
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough)) JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
{
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
}
} }
} }

View File

@ -972,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void SetUIProgressHandlers(Switch emulationContext) public void SetUIProgressHandlers(Switch emulationContext)
{ {
if (emulationContext.Application.DiskCacheLoadState != null) if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
{ {
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
} }
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@ -1336,6 +1336,23 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public void ToggleFileType(string fileType)
{
_ = fileType switch
{
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
};
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
LoadApplications();
}
public async void ManageProfiles() public async void ManageProfiles()
{ {
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
@ -1705,8 +1722,8 @@ namespace Ryujinx.Ava.UI.ViewModels
if (string.IsNullOrWhiteSpace(titleName)) if (string.IsNullOrWhiteSpace(titleName))
{ {
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Application.TitleName; TitleName = AppHost.Device.Processes.ActiveApplication.Name;
} }
SwitchToRenderer(startFullscreen); SwitchToRenderer(startFullscreen);

View File

@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly List<string> _validTzRegions; private readonly List<string> _validTzRegions;
private readonly Dictionary<string, string> _networkInterfaces;
private float _customResolutionScale; private float _customResolutionScale;
private int _resolutionScale; private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex; private int _graphicsBackendMultithreadingIndex;
@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow; public event Action CloseWindow;
public event Action SaveSettingsEvent; public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
public int ResolutionScale public int ResolutionScale
{ {
@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public AvaloniaList<string> GameDirectories { get; set; } public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; } public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
public AvaloniaList<string> NetworkInterfaceList
{
get => new AvaloniaList<string>(_networkInterfaces.Keys);
}
public KeyboardHotkeys KeyboardHotkeys public KeyboardHotkeys KeyboardHotkeys
{ {
get => _keyboardHotkeys; get => _keyboardHotkeys;
@ -251,6 +260,16 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public int NetworkInterfaceIndex
{
get => _networkInterfaceIndex;
set
{
_networkInterfaceIndex = value != -1 ? value : 0;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
TimeZones = new AvaloniaList<TimeZone>(); TimeZones = new AvaloniaList<TimeZone>();
AvailableGpus = new ObservableCollection<ComboBoxItem>(); AvailableGpus = new ObservableCollection<ComboBoxItem>();
_validTzRegions = new List<string>(); _validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends(); CheckSoundBackends();
PopulateNetworkInterfaces();
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@ -327,6 +348,17 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
private void PopulateNetworkInterfaces()
{
_networkInterfaces.Clear();
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
}
}
public void ValidateAndSetTimeZone(string location) public void ValidateAndSetTimeZone(string location)
{ {
if (_validTzRegions.Contains(location)) if (_validTzRegions.Contains(location))
@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog; EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
} }
public void SaveSettings() public void SaveSettings()
@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.ToFileFormat().SaveConfig(Program.ConfigurationPath); config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();

View File

@ -17,234 +17,236 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers; using SpanHelpers = LibHac.Common.SpanHelpers;
namespace Ryujinx.Ava.UI.ViewModels; namespace Ryujinx.Ava.UI.ViewModels
public class TitleUpdateViewModel : BaseModel
{ {
public TitleUpdateMetadata _titleUpdateWindowData; public class TitleUpdateViewModel : BaseModel
public readonly string _titleUpdateJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private ulong _titleId { get; }
private string _titleName { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
public AvaloniaList<TitleUpdateModel> TitleUpdates
{ {
get => _titleUpdates; public TitleUpdateMetadata _titleUpdateWindowData;
set public readonly string _titleUpdateJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private ulong _titleId { get; }
private string _titleName { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<TitleUpdateModel> TitleUpdates
{ {
_titleUpdates = value; get => _titleUpdates;
OnPropertyChanged(); set
}
}
public AvaloniaList<object> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public object SelectedUpdate
{
get => _selectedUpdate;
set
{
_selectedUpdate = value;
OnPropertyChanged();
}
}
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
_titleUpdateWindowData = new TitleUpdateMetadata
{ {
Selected = "", _titleUpdates = value;
Paths = new List<string>() OnPropertyChanged();
};
Save();
}
LoadUpdates();
}
private void LoadUpdates()
{
foreach (string path in _titleUpdateWindowData.Paths)
{
AddUpdate(path);
}
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
SelectedUpdate = selected;
// NOTE: Save the list again to remove leftovers.
Save();
SortUpdates();
}
public void SortUpdates()
{
var list = TitleUpdates.ToList();
list.Sort((first, second) =>
{
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
{
return -1;
}
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
{
return 1;
}
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
});
Views.Clear();
Views.Add(new BaseModel());
Views.AddRange(list);
if (SelectedUpdate == null)
{
SelectedUpdate = Views[0];
}
else if (!TitleUpdates.Contains(SelectedUpdate))
{
if (Views.Count > 1)
{
SelectedUpdate = Views[1];
}
else
{
SelectedUpdate = Views[0];
} }
} }
}
private void AddUpdate(string path) public AvaloniaList<object> Views
{
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
{ {
using FileStream file = new(path, FileMode.Open, FileAccess.Read); get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public object SelectedUpdate
{
get => _selectedUpdate;
set
{
_selectedUpdate = value;
OnPropertyChanged();
}
}
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try try
{ {
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
if (controlNca != null && patchNca != null) _titleUpdateWindowData = new TitleUpdateMetadata
{ {
ApplicationControlProperty controlData = new(); Selected = "",
Paths = new List<string>()
};
using UniqueRef<IFile> nacpFile = new(); Save();
}
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); LoadUpdates();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); }
TitleUpdates.Add(new TitleUpdateModel(controlData, path)); private void LoadUpdates()
{
foreach (string path in _titleUpdateWindowData.Paths)
{
AddUpdate(path);
}
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
SelectedUpdate = selected;
// NOTE: Save the list again to remove leftovers.
Save();
SortUpdates();
}
public void SortUpdates()
{
var list = TitleUpdates.ToList();
list.Sort((first, second) =>
{
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
{
return -1;
}
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
{
return 1;
}
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
});
Views.Clear();
Views.Add(new BaseModel());
Views.AddRange(list);
if (SelectedUpdate == null)
{
SelectedUpdate = Views[0];
}
else if (!TitleUpdates.Contains(SelectedUpdate))
{
if (Views.Count > 1)
{
SelectedUpdate = Views[1];
} }
else else
{
SelectedUpdate = Views[0];
}
}
}
private void AddUpdate(string path)
{
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
{
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
try
{
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
}
}
catch (Exception ex)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
}); });
} }
} }
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
});
}
} }
}
public void RemoveUpdate(TitleUpdateModel update) public void RemoveUpdate(TitleUpdateModel update)
{
TitleUpdates.Remove(update);
SortUpdates();
}
public async void Add()
{
OpenFileDialog dialog = new()
{ {
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], TitleUpdates.Remove(update);
AllowMultiple = true
};
dialog.Filters.Add(new FileDialogFilter SortUpdates();
}
public async void Add()
{ {
Name = "NSP", OpenFileDialog dialog = new()
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
{ {
foreach (string file in files) Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
{ {
AddUpdate(file); foreach (string file in files)
{
AddUpdate(file);
}
} }
} }
SortUpdates();
} }
SortUpdates(); public void Save()
}
public void Save()
{
_titleUpdateWindowData.Paths.Clear();
_titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates)
{ {
_titleUpdateWindowData.Paths.Add(update.Path); _titleUpdateWindowData.Paths.Clear();
_titleUpdateWindowData.Selected = "";
if (update == SelectedUpdate) foreach (TitleUpdateModel update in TitleUpdates)
{ {
_titleUpdateWindowData.Selected = update.Path; _titleUpdateWindowData.Paths.Add(update.Path);
}
}
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); if (update == SelectedUpdate)
{
_titleUpdateWindowData.Selected = update.Path;
}
}
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
}
} }
} }

View File

@ -78,6 +78,7 @@
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" /> <MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenSettings" Click="OpenSettings"

View File

@ -11,6 +11,8 @@ using Ryujinx.Common;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
InitializeComponent(); InitializeComponent();
ToggleFileTypesMenuItem.Items = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.Items = GenerateLanguageMenuItems();
}
private CheckBox[] GenerateToggleFileTypeItems()
{
List<CheckBox> checkBoxes = new();
foreach (var item in Enum.GetValues(typeof (FileTypes)))
{
string fileName = Enum.GetName(typeof (FileTypes), item);
checkBoxes.Add(new CheckBox()
{
Content = $".{fileName}",
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName))
});
}
return checkBoxes.ToArray();
}
private MenuItem[] GenerateLanguageMenuItems()
{
List<MenuItem> menuItems = new(); List<MenuItem> menuItems = new();
string localePath = "Ryujinx.Ava/Assets/Locales"; string localePath = "Ryujinx.Ava/Assets/Locales";
@ -42,7 +68,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson); var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
if (!strings.TryGetValue("Language", out string languageName)) if (!strings.TryGetValue("Language", out string languageName))
{ {
@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
menuItems.Add(menuItem); menuItems.Add(menuItem);
} }
ChangeLanguageMenuItem.Items = menuItems.ToArray(); return menuItems.ToArray();
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
@ -126,7 +152,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{ {
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper(); string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
await window.ShowDialog(Window); await window.ShowDialog(Window);
@ -148,13 +174,11 @@ namespace Ryujinx.Ava.UI.Views.Main
return; return;
} }
ApplicationLoader application = ViewModel.AppHost.Device.Application; string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
if (application != null)
{
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats(); await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
}
ViewModel.AppHost.Device.EnableCheats();
} }
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)

View File

@ -1,4 +1,4 @@
<UserControl <UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView" x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -29,7 +29,18 @@
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}" <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox> </CheckBox>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
Items="{Binding NetworkInterfaceList}"
Width="250" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View File

@ -1,7 +1,7 @@
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Models.Amiibo;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
@ -35,14 +35,14 @@ namespace Ryujinx.Ava.UI.Windows
} }
public bool IsScanned { get; set; } public bool IsScanned { get; set; }
public Amiibo.AmiiboApi ScannedAmiibo { get; set; } public AmiiboApi ScannedAmiibo { get; set; }
public AmiiboWindowViewModel ViewModel { get; set; } public AmiiboWindowViewModel ViewModel { get; set; }
private void ScanButton_Click(object sender, RoutedEventArgs e) private void ScanButton_Click(object sender, RoutedEventArgs e)
{ {
if (ViewModel.AmiiboSelectedIndex > -1) if (ViewModel.AmiiboSelectedIndex > -1)
{ {
Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
ScannedAmiibo = amiibo; ScannedAmiibo = amiibo;
IsScanned = true; IsScanned = true;
Close(); Close();

View File

@ -1,4 +1,4 @@
<window:StyleableWindow <window:StyleableWindow
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -10,20 +10,16 @@
d:DesignHeight="450" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow" x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow"
Title="ContentDialogOverlayWindow" Title="ContentDialogOverlayWindow"
Focusable="True"> Focusable="False">
<window:StyleableWindow.Styles> <window:StyleableWindow.Styles>
<Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot"> <Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
<Setter Property="Background" <Setter Property="Background"
Value="Transparent" /> Value="Transparent" />
</Style> </Style>
</window:StyleableWindow.Styles> </window:StyleableWindow.Styles>
<ContentControl <ui:ContentDialog Name="ContentDialog"
Focusable="False" IsPrimaryButtonEnabled="True"
IsVisible="False" IsSecondaryButtonEnabled="True"
KeyboardNavigation.IsTabStop="False"> IsVisible="False"
<ui:ContentDialog Name="ContentDialog" Focusable="True"/>
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
</window:StyleableWindow> </window:StyleableWindow>

View File

@ -6,11 +6,8 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Button = Avalonia.Controls.Button; using Button = Avalonia.Controls.Button;

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
public enum AntiAliasing public enum AntiAliasing
{ {
None, None,
@ -9,4 +13,4 @@
SmaaHigh, SmaaHigh,
SmaaUltra SmaaUltra
} }
} }

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
public enum AspectRatio public enum AspectRatio
{ {
Fixed4x3, Fixed4x3,

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
public enum BackendThreading public enum BackendThreading
{ {
Auto, Auto,

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend public enum GraphicsBackend
{ {
Vulkan, Vulkan,

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
public enum GraphicsDebugLevel public enum GraphicsDebugLevel
{ {
None, None,

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration.Hid.Controller using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{ {
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
public enum GamepadInputId : byte public enum GamepadInputId : byte
{ {
Unbound, Unbound,
@ -51,4 +55,4 @@
Count Count
} }
} }

View File

@ -1,4 +1,5 @@
using System; using Ryujinx.Common.Utilities;
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -6,6 +7,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{ {
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController> class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
{ {
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader) private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
{ {
// Temporary reader to get the backend type // Temporary reader to get the backend type
@ -52,8 +55,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
return motionBackendType switch return motionBackendType switch
{ {
MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options), MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options), MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"), _ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
}; };
} }
@ -63,10 +66,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
switch (value.MotionBackend) switch (value.MotionBackend)
{ {
case MotionInputBackendType.GamepadDriver: case MotionInputBackendType.GamepadDriver:
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options); JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
break; break;
case MotionInputBackendType.CemuHook: case MotionInputBackendType.CemuHook:
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options); JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
break; break;
default: default:
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}"); throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");

View File

@ -1,5 +1,8 @@
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{ {
[JsonConverter(typeof(JsonMotionConfigControllerConverter))]
public class MotionConfigController public class MotionConfigController
{ {
public MotionInputBackendType MotionBackend { get; set; } public MotionInputBackendType MotionBackend { get; set; }

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(MotionConfigController))]
[JsonSerializable(typeof(CemuHookMotionConfigController))]
[JsonSerializable(typeof(StandardMotionConfigController))]
public partial class MotionConfigJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{ {
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
public enum MotionInputBackendType : byte public enum MotionInputBackendType : byte
{ {
Invalid, Invalid,

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration.Hid.Controller using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{ {
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
public enum StickInputId : byte public enum StickInputId : byte
{ {
Unbound, Unbound,
@ -8,4 +12,4 @@
Count Count
} }
} }

View File

@ -1,9 +1,12 @@
using Ryujinx.Common.Utilities;
using System; using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
[Flags]
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[Flags]
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
public enum ControllerType : int public enum ControllerType : int
{ {
None, None,

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration.Hid using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{ {
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
public enum InputBackendType public enum InputBackendType
{ {
Invalid, Invalid,

View File

@ -1,8 +1,10 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
[JsonConverter(typeof(JsonInputConfigConverter))]
public class InputConfig : INotifyPropertyChanged public class InputConfig : INotifyPropertyChanged
{ {
/// <summary> /// <summary>

View File

@ -0,0 +1,14 @@
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(InputConfig))]
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
[JsonSerializable(typeof(StandardControllerInputConfig))]
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -1,13 +1,16 @@
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
class JsonInputConfigConverter : JsonConverter<InputConfig> public class JsonInputConfigConverter : JsonConverter<InputConfig>
{ {
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader) private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
{ {
// Temporary reader to get the backend type // Temporary reader to get the backend type
@ -54,8 +57,8 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch return backendType switch
{ {
InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options), InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options), InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"), _ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
}; };
} }
@ -65,10 +68,10 @@ namespace Ryujinx.Common.Configuration.Hid
switch (value.Backend) switch (value.Backend)
{ {
case InputBackendType.WindowKeyboard: case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options); JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
break; break;
case InputBackendType.GamepadSDL2: case InputBackendType.GamepadSDL2:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options); JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
break; break;
default: default:
throw new ArgumentException($"Unknown backend type {value.Backend}"); throw new ArgumentException($"Unknown backend type {value.Backend}");

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration.Hid using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{ {
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
public enum Key public enum Key
{ {
Unknown, Unknown,
@ -136,4 +140,4 @@
Count Count
} }
} }

View File

@ -1,5 +1,7 @@
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// NOTE: Please don't change this to struct.
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
public class KeyboardHotkeys public class KeyboardHotkeys
{ {
public Key ToggleVsync { get; set; } public Key ToggleVsync { get; set; }
@ -12,4 +14,4 @@
public Key VolumeUp { get; set; } public Key VolumeUp { get; set; }
public Key VolumeDown { get; set; } public Key VolumeDown { get; set; }
} }
} }

View File

@ -1,6 +1,10 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
public enum PlayerIndex : int public enum PlayerIndex : int
{ {
Player1 = 0, Player1 = 0,

View File

@ -1,5 +1,9 @@
namespace Ryujinx.Common.Configuration using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
public enum MemoryManagerMode : byte public enum MemoryManagerMode : byte
{ {
SoftwarePageTable, SoftwarePageTable,

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
public enum ScalingFilter public enum ScalingFilter
{ {
Bilinear, Bilinear,

View File

@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(TitleUpdateMetadata))]
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -1,22 +1,20 @@
using System; using System.Text;
using System.Reflection;
using System.Text;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
internal class DefaultLogFormatter : ILogFormatter internal class DefaultLogFormatter : ILogFormatter
{ {
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>(); private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
public string Format(LogEventArgs args) public string Format(LogEventArgs args)
{ {
StringBuilder sb = _stringBuilderPool.Allocate(); StringBuilder sb = StringBuilderPool.Allocate();
try try
{ {
sb.Clear(); sb.Clear();
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time); sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
sb.Append($" |{args.Level.ToString()[0]}| "); sb.Append($" |{args.Level.ToString()[0]}| ");
if (args.ThreadName != null) if (args.ThreadName != null)
@ -27,53 +25,17 @@ namespace Ryujinx.Common.Logging
sb.Append(args.Message); sb.Append(args.Message);
if (args.Data != null) if (args.Data is not null)
{ {
PropertyInfo[] props = args.Data.GetType().GetProperties(); sb.Append(' ');
DynamicObjectFormatter.Format(sb, args.Data);
sb.Append(" {");
foreach (var prop in props)
{
sb.Append(prop.Name);
sb.Append(": ");
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
{
Array array = (Array)prop.GetValue(args.Data);
foreach (var item in array)
{
sb.Append(item.ToString());
sb.Append(", ");
}
if (array.Length > 0)
{
sb.Remove(sb.Length - 2, 2);
}
}
else
{
sb.Append(prop.GetValue(args.Data));
}
sb.Append(" ; ");
}
// We remove the final ';' from the string
if (props.Length > 0)
{
sb.Remove(sb.Length - 3, 3);
}
sb.Append('}');
} }
return sb.ToString(); return sb.ToString();
} }
finally finally
{ {
_stringBuilderPool.Release(sb); StringBuilderPool.Release(sb);
} }
} }
} }

View File

@ -0,0 +1,84 @@
#nullable enable
using System;
using System.Reflection;
using System.Text;
namespace Ryujinx.Common.Logging
{
internal class DynamicObjectFormatter
{
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
public static string? Format(object? dynamicObject)
{
if (dynamicObject is null)
{
return null;
}
StringBuilder sb = StringBuilderPool.Allocate();
try
{
Format(sb, dynamicObject);
return sb.ToString();
}
finally
{
StringBuilderPool.Release(sb);
}
}
public static void Format(StringBuilder sb, object? dynamicObject)
{
if (dynamicObject is null)
{
return;
}
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
sb.Append('{');
foreach (var prop in props)
{
sb.Append(prop.Name);
sb.Append(": ");
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
{
Array? array = (Array?) prop.GetValue(dynamicObject);
if (array is not null)
{
foreach (var item in array)
{
sb.Append(item);
sb.Append(", ");
}
if (array.Length > 0)
{
sb.Remove(sb.Length - 2, 2);
}
}
}
else
{
sb.Append(prop.GetValue(dynamicObject));
}
sb.Append(" ; ");
}
// We remove the final ';' from the string
if (props.Length > 0)
{
sb.Remove(sb.Length - 3, 3);
}
sb.Append('}');
}
}
}

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
public enum LogClass public enum LogClass
{ {
Application, Application,

View File

@ -11,15 +11,7 @@ namespace Ryujinx.Common.Logging
public readonly string Message; public readonly string Message;
public readonly object Data; public readonly object Data;
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message) public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
{
Level = level;
Time = time;
ThreadName = threadName;
Message = message;
}
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data)
{ {
Level = level; Level = level;
Time = time; Time = time;

View File

@ -0,0 +1,30 @@
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
internal class LogEventArgsJson
{
public LogLevel Level { get; }
public TimeSpan Time { get; }
public string ThreadName { get; }
public string Message { get; }
public string Data { get; }
[JsonConstructor]
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
{
Level = level;
Time = time;
ThreadName = threadName;
Message = message;
Data = data;
}
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
{
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
}
}
}

View File

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonSerializable(typeof(LogEventArgsJson))]
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
public enum LogLevel public enum LogLevel
{ {
Debug, Debug,

View File

@ -1,5 +1,5 @@
using System.IO; using Ryujinx.Common.Utilities;
using System.Text.Json; using System.IO;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
@ -25,12 +25,8 @@ namespace Ryujinx.Common.Logging
public void Log(object sender, LogEventArgs e) public void Log(object sender, LogEventArgs e)
{ {
string text = JsonSerializer.Serialize(e); var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
using (BinaryWriter writer = new BinaryWriter(_stream))
{
writer.Write(text);
}
} }
public void Dispose() public void Dispose()

View File

@ -1,18 +1,21 @@
using Microsoft.Win32.SafeHandles;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.SystemInterop namespace Ryujinx.Common.SystemInterop
{ {
public partial class StdErrAdapter : IDisposable public partial class StdErrAdapter : IDisposable
{ {
private bool _disposable = false; private bool _disposable = false;
private UnixStream _pipeReader; private Stream _pipeReader;
private UnixStream _pipeWriter; private Stream _pipeWriter;
private Thread _worker; private CancellationTokenSource _cancellationTokenSource;
private Task _worker;
public StdErrAdapter() public StdErrAdapter()
{ {
@ -31,37 +34,39 @@ namespace Ryujinx.Common.SystemInterop
(int readFd, int writeFd) = MakePipe(); (int readFd, int writeFd) = MakePipe();
dup2(writeFd, stdErrFileno); dup2(writeFd, stdErrFileno);
_pipeReader = new UnixStream(readFd); _pipeReader = CreateFileDescriptorStream(readFd);
_pipeWriter = new UnixStream(writeFd); _pipeWriter = CreateFileDescriptorStream(writeFd);
_worker = new Thread(EventWorker); _cancellationTokenSource = new CancellationTokenSource();
_worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
_disposable = true; _disposable = true;
_worker.Start();
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
private void EventWorker() private async Task EventWorkerAsync(CancellationToken cancellationToken)
{ {
TextReader reader = new StreamReader(_pipeReader); using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
string line; string line;
while ((line = reader.ReadLine()) != null) while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
{ {
Logger.Error?.PrintRawMsg(line); Logger.Error?.PrintRawMsg(line);
} }
} }
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (_disposable) if (_disposable)
{ {
_disposable = false;
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{ {
_cancellationTokenSource.Cancel();
_worker.Wait(0);
_pipeReader?.Close(); _pipeReader?.Close();
_pipeWriter?.Close(); _pipeWriter?.Close();
} }
_disposable = false;
} }
} }
@ -74,11 +79,11 @@ namespace Ryujinx.Common.SystemInterop
private static partial int dup2(int fd, int fd2); private static partial int dup2(int fd, int fd2);
[LibraryImport("libc", SetLastError = true)] [LibraryImport("libc", SetLastError = true)]
private static unsafe partial int pipe(int* pipefd); private static partial int pipe(Span<int> pipefd);
private static unsafe (int, int) MakePipe() private static (int, int) MakePipe()
{ {
int *pipefd = stackalloc int[2]; Span<int> pipefd = stackalloc int[2];
if (pipe(pipefd) == 0) if (pipe(pipefd) == 0)
{ {
@ -89,5 +94,16 @@ namespace Ryujinx.Common.SystemInterop
throw new(); throw new();
} }
} }
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private static Stream CreateFileDescriptorStream(int fd)
{
return new FileStream(
new SafeFileHandle((IntPtr)fd, ownsHandle: true),
FileAccess.ReadWrite
);
}
} }
} }

View File

@ -1,155 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Common.SystemInterop
{
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
public partial class UnixStream : Stream, IDisposable
{
private const int InvalidFd = -1;
private int _fd;
[LibraryImport("libc", SetLastError = true)]
private static partial long read(int fd, IntPtr buf, ulong count);
[LibraryImport("libc", SetLastError = true)]
private static partial long write(int fd, IntPtr buf, ulong count);
[LibraryImport("libc", SetLastError = true)]
private static partial int close(int fd);
public UnixStream(int fd)
{
if (InvalidFd == fd)
{
throw new ArgumentException("Invalid file descriptor");
}
_fd = fd;
CanRead = read(fd, IntPtr.Zero, 0) != -1;
CanWrite = write(fd, IntPtr.Zero, 0) != -1;
}
~UnixStream()
{
Close();
}
public override bool CanRead { get; }
public override bool CanWrite { get; }
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override unsafe int Read([In, Out] byte[] buffer, int offset, int count)
{
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
{
throw new ArgumentOutOfRangeException();
}
if (buffer.Length == 0)
{
return 0;
}
long r = 0;
fixed (byte* buf = &buffer[offset])
{
do
{
r = read(_fd, (IntPtr)buf, (ulong)count);
} while (ShouldRetry(r));
}
return (int)r;
}
public override unsafe void Write(byte[] buffer, int offset, int count)
{
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
{
throw new ArgumentOutOfRangeException();
}
if (buffer.Length == 0)
{
return;
}
fixed (byte* buf = &buffer[offset])
{
long r = 0;
do {
r = write(_fd, (IntPtr)buf, (ulong)count);
} while (ShouldRetry(r));
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Close()
{
if (_fd == InvalidFd)
{
return;
}
Flush();
int r;
do {
r = close(_fd);
} while (ShouldRetry(r));
_fd = InvalidFd;
}
void IDisposable.Dispose()
{
Close();
}
private bool ShouldRetry(long r)
{
if (r == -1)
{
const int eintr = 4;
int errno = Marshal.GetLastPInvokeError();
if (errno == eintr)
{
return true;
}
throw new SystemException($"Operation failed with error 0x{errno:X}");
}
return false;
}
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Utilities
{
[JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")]
[JsonSerializable(typeof(Dictionary<string, string>), TypeInfoPropertyName = "StringDictionary")]
public partial class CommonJsonContext : JsonSerializerContext
{
}
}

View File

@ -1,15 +1,62 @@
using Ryujinx.Common.Configuration.Hid; using System.IO;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
public class JsonHelper public class JsonHelper
{ {
public static JsonNamingPolicy SnakeCase { get; } private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy();
private const int DefaultFileWriteBufferSize = 4096;
/// <summary>
/// Creates new serializer options with default settings.
/// </summary>
/// <remarks>
/// It is REQUIRED for you to save returned options statically or as a part of static serializer context
/// in order to avoid performance issues. You can safely modify returned options for your case before storing.
/// </remarks>
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true)
{
JsonSerializerOptions options = new()
{
DictionaryKeyPolicy = SnakeCasePolicy,
PropertyNamingPolicy = SnakeCasePolicy,
WriteIndented = indented,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
return options;
}
public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo)
{
return JsonSerializer.Serialize(value, typeInfo);
}
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
{
return JsonSerializer.Deserialize(value, typeInfo);
}
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
{
using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough);
JsonSerializer.Serialize(file, value, typeInfo);
}
public static T DeserializeFromFile<T>(string filePath, JsonTypeInfo<T> typeInfo)
{
using FileStream file = File.OpenRead(filePath);
return JsonSerializer.Deserialize(file, typeInfo);
}
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
{
JsonSerializer.Serialize(stream, value, typeInfo);
}
private class SnakeCaseNamingPolicy : JsonNamingPolicy private class SnakeCaseNamingPolicy : JsonNamingPolicy
{ {
@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities
return name; return name;
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new();
for (int i = 0; i < name.Length; i++) for (int i = 0; i < name.Length; i++)
{ {
@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities
} }
else else
{ {
builder.Append("_"); builder.Append('_');
builder.Append(char.ToLowerInvariant(c)); builder.Append(char.ToLowerInvariant(c));
} }
} }
@ -47,64 +94,5 @@ namespace Ryujinx.Common.Utilities
return builder.ToString(); return builder.ToString();
} }
} }
static JsonHelper()
{
SnakeCase = new SnakeCaseNamingPolicy();
}
public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false)
{
JsonSerializerOptions options = new JsonSerializerOptions
{
DictionaryKeyPolicy = SnakeCase,
PropertyNamingPolicy = SnakeCase,
WriteIndented = prettyPrint,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new JsonInputConfigConverter());
options.Converters.Add(new JsonMotionConfigControllerConverter());
return options;
}
public static T Deserialize<T>(Stream stream)
{
using (BinaryReader reader = new BinaryReader(stream))
{
return JsonSerializer.Deserialize<T>(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions());
}
}
public static T DeserializeFromFile<T>(string path)
{
return Deserialize<T>(File.ReadAllText(path));
}
public static T Deserialize<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, GetDefaultSerializerOptions());
}
public static void Serialize<TValue>(Stream stream, TValue obj, bool prettyPrint = false)
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(SerializeToUtf8Bytes(obj, prettyPrint));
}
}
public static string Serialize<TValue>(TValue obj, bool prettyPrint = false)
{
return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint));
}
public static byte[] SerializeToUtf8Bytes<T>(T obj, bool prettyPrint = false)
{
return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint));
}
} }
} }

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