Compare commits

...

38 Commits

Author SHA1 Message Date
8d9d508dc7 Shader: Bias textureGather instructions on AMD/Intel (#4703)
* Experimental (GLSL, forced)

* SPIR-V attempt

* Add capability

* Fix pCount == 1 on glsl

* Fix typo
2023-04-22 18:02:39 -03:00
e27f5522e2 Removed MotionInput Calibration (#4705)
Don't know why this is here.
It just seems to set the filter to an identity. Which then quickly returns to where its supposed to be anyways.
2023-04-22 15:31:28 +02:00
add2a9d151 Avoid LM service crashes by not reading more than the buffer size (#4701) 2023-04-20 17:10:17 +02:00
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
346 changed files with 5678 additions and 2979 deletions

View File

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

View File

@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
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.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));

View File

@ -268,11 +268,13 @@ namespace ARMeilleure.CodeGen.X86
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.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.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.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.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.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));

View File

@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
case IntrinsicType.Mxcsr:
{
Operand offset = operation.GetSource(0);
Operand bits = operation.GetSource(1);
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant);
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32);
Debug.Assert(offset.Kind == OperandKind.Constant);
Debug.Assert(offset.Type == OperandType.I32);
int offs = offset.AsInt32() + context.CallArgsRegionSize;
@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
context.Assembler.Stmxcsr(memOp);
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
{
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;
}

View File

@ -60,6 +60,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
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.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, 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.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, 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.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, 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.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, 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.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
@ -170,11 +170,13 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
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.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, 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.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));

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;
case Instruction.Extended:
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub)
if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
{
int stackOffset = stackAlloc.Allocate(OperandType.I32);
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;
}
}

View File

@ -208,11 +208,13 @@ namespace ARMeilleure.CodeGen.X86
Vblendvps,
Vcvtph2ps,
Vcvtps2ph,
Vfmadd231pd,
Vfmadd231ps,
Vfmadd231sd,
Vfmadd231ss,
Vfmsub231sd,
Vfmsub231ss,
Vfnmadd231pd,
Vfnmadd231ps,
Vfnmadd231sd,
Vfnmadd231ss,

View File

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

View File

@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(method, src);
context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar16(context, op.Vd, op.T, res);
}
@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(method, src);
context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar(context, op.Vd, res);
}
@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
// VRINTX (floating-point).
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)

View File

@ -1,3 +1,4 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
};
#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)
{
return op.Imm - (8 << op.Size);
@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat32).GetMethod(name)
: typeof(SoftFloat64).GetMethod(name);
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(info, callArgs);
context.LoadFromContext();
context.EnterArmFpMode();
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
{
// Legacy Sse.

View File

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

View File

@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitGetFpcr(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_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return;
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_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); 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;
MethodInfo info;
switch (GetPackedId(op))
{
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitSetFpcr(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}.");
}
context.Call(info, GetIntOrZR(context, op.Rt));
}
public static void Nop(ArmEmitterContext context)
@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
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)
{
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)));
}
}
context.UpdateArmFpMode();
}
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)));
}
}
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;
}
MethodInfo info;
switch (op.CRn)
{
case 13: // Process and Thread Info.
@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
switch (op.Opc2)
{
case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
EmitSetTpidrEl0(context); return;
default:
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
}
break;
case 7:
switch (op.CRm) // Cache and Memory barrier.
{
@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
default:
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)
@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
return;
}
MethodInfo info;
Operand result;
switch (op.CRn)
{
@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
switch (op.Opc2)
{
case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
result = EmitGetTpidrEl0(context); break;
case 3:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
result = EmitGetTpidrroEl0(context); break;
default:
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)
{
// Special behavior: copy NZCV flags into APSR.
EmitSetNzcv(context, context.Call(info));
EmitSetNzcv(context, result);
return;
}
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)));
}
}
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;
}
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()
{
return GetContext().CntfrqEl0;
@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
{
return GetContext().CntvctEl0;
}
public static void SetTpidrEl0(ulong value)
{
GetContext().TpidrEl0 = (long)value;
}
public static void SetTpidrEl032(uint value)
{
GetContext().TpidrEl0 = (long)value;
}
#endregion
#region "Read"

View File

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

View File

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

View File

@ -13,6 +13,8 @@ namespace ARMeilleure.State
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
public fixed uint Flags[RegisterConsts.FlagsCount];
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
public long TpidrEl0;
public long TpidrroEl0;
public int Counter;
public ulong DispatchAddress;
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 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()
{
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)
{
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.GetFunctionAddress)));
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.ReadUInt16)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
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.SupervisorCall)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));

View File

@ -3,4 +3,5 @@
namespace ARMeilleure.Translation
{
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 InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 4485; //! 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 BackupDir = "1";

View File

@ -25,5 +25,10 @@ namespace ARMeilleure.Translation
{
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();
ulong nextAddr = func.Execute(context);
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address);
@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context);
address = func.Execute(Stubs.ContextWrapper, context);
EnqueueForDeletion(address, func);

View File

@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
private readonly Translator _translator;
private readonly Lazy<IntPtr> _dispatchStub;
private readonly Lazy<DispatcherFunction> _dispatchLoop;
private readonly Lazy<WrapperFunction> _contextWrapper;
/// <summary>
/// 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>
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance.
@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
_translator = translator;
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
}
/// <summary>
@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
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>
/// Generates a <see cref="DispatchLoop"/> function.
/// </summary>
@ -221,6 +263,8 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
context.Branch(beginLbl);
context.MarkLabel(endLbl);
EmitSyncFpContext(context, nativeContext, false);
context.Return();
var cfg = context.GetControlFlowGraph();
@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
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>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.18" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageVersion Include="Avalonia" Version="0.10.19" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<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="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
@ -44,10 +44,10 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<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.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="XamlNameReferenceGenerator" Version="1.5.1" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>
</Project>

View File

@ -40,7 +40,7 @@
## 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!
## Usage

View File

@ -177,6 +177,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource();
}
@ -321,17 +323,15 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true;
var activeProcess = Device.Processes.ActiveApplication;
var nacp = activeProcess.ApplicationControlProperties;
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
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(() =>
{
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
});
_viewModel.SetUIProgressHandlers(Device);
@ -385,6 +385,11 @@ namespace Ryujinx.Ava
});
}
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
}
public void Stop()
{
_isActive = false;
@ -741,7 +746,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor);
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Device = new Switch(configuration);
}

View File

@ -429,6 +429,7 @@
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language",
"MenuBarShowFileTypes": "Show File Types",
"CommonSort": "Sort",
"CommonShowNames": "Show Names",
"CommonFavorite": "Favorite",
@ -637,5 +638,8 @@
"SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra",
"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,6 +13,7 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
@ -152,25 +153,17 @@ namespace Ryujinx.Ava.Common
string destination = await folderDialog.ShowAsync(_owner);
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))
{
Thread extractorThread = new(() =>
{
Dispatcher.UIThread.Post(async () =>
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
Dispatcher.UIThread.Post(waitingDialog.Show);
if (result == UserResult.Cancel)
{
cancellationToken.Cancel();
}
});
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
@ -222,6 +215,8 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
});
@ -263,11 +258,15 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
});
}
else if (resultCode.Value.IsSuccess())
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
@ -284,6 +283,8 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}

View File

@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
{
var localeStrings = new Dictionary<LocaleKeys, string>();
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)
{

View File

@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.Models.Github;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -31,6 +32,7 @@ namespace Ryujinx.Modules
internal static class Updater
{
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 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 fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson);
JToken assets = jsonRoot["assets"];
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
_buildVer = fetched.Name;
_buildVer = (string)jsonRoot["name"];
foreach (JToken asset in assets)
foreach (var asset in fetched.Assets)
{
string assetName = (string)asset["name"];
string assetState = (string)asset["state"];
string downloadURL = (string)asset["browser_download_url"];
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
{
_buildUrl = downloadURL;
_buildUrl = asset.BrowserDownloadUrl;
if (assetState != "uploaded")
if (asset.State != "uploaded")
{
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)

View File

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

View File

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

View File

@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
private ContentDialog _host;
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder)
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
{
MainText = mainText;
SecondaryText = secondaryText;
Message = message ?? "";
DataContext = this;
_placeholder = placeholder;
InitializeComponent();
@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
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 MainText { get; set; } = "";
public string SecondaryText { get; set; } = "";
@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
UserResult result = UserResult.Cancel;
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText)
{
Message = args.InitialText ?? ""
};
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
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._host = contentDialog;
@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
};
contentDialog.Closed += handler;
overlay.Opened += OverlayOnActivated;
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);
await ContentDialogHelper.ShowAsync(contentDialog);
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 Ryujinx.Ava.UI.Windows;
using System.Threading;
namespace Ryujinx.Ava.UI.Controls
{
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()
{
PrimaryText.Text = primaryText;
SecondaryText.Text = secondaryText;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
SystemDecorations = SystemDecorations.BorderOnly;
ShowInTaskbar = false;
}
public UpdateWaitWindow()

View File

@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
string closeButton,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
UserResult result = UserResult.None;
@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
int iconSymbol,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
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(
@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
iconSymbol,
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
deferResetEvent,
doWhileDeferred,
DeferClose);
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
primaryButtonResult);
}
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
{
return new(mainText, secondaryText);
}
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowTextDialog(
@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
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)
{
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/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
}
catch
{

View File

@ -4,11 +4,11 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -17,6 +17,7 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -31,8 +32,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly StyleableWindow _owner;
private Bitmap _amiiboImage;
private List<Amiibo.AmiiboApi> _amiiboList;
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
private List<AmiiboApi> _amiiboList;
private AvaloniaList<AmiiboApi> _amiibos;
private ObservableCollection<string> _amiiboSeries;
private int _amiiboSelectedIndex;
@ -41,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _showAllAmiibo;
private bool _useRandomUuid;
private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
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"));
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
_amiiboList = new List<Amiibo.AmiiboApi>();
_amiiboList = new List<AmiiboApi>();
_amiiboSeries = new ObservableCollection<string>();
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
_amiibos = new AvaloniaList<AmiiboApi>();
_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;
set
@ -187,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
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();
}
@ -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();
ParseAmiiboData();
@ -223,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (!ShowAllAmiibo)
{
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
{
if (game != null)
{
@ -255,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
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);
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
@ -270,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
List<AmiiboApi> amiiboSortedList = _amiiboList
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
.OrderBy(amiibo => amiibo.Name).ToList();
@ -280,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (!_showAllAmiibo)
{
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
{
if (game != null)
{
@ -314,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
@ -326,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
{
bool writable = false;
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
{
if (item.GameId.Contains(TitleId))
{
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
{
usageString += Environment.NewLine +
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";

View File

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

View File

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

View File

@ -972,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.InputDialogNo],
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)
{
@ -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()
{
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);

View File

@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly List<string> _validTzRegions;
private readonly Dictionary<string, string> _networkInterfaces;
private float _customResolutionScale;
private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex;
@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow;
public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
public int ResolutionScale
{
@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
public AvaloniaList<string> NetworkInterfaceList
{
get => new AvaloniaList<string>(_networkInterfaces.Keys);
}
public KeyboardHotkeys 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()
{
_virtualFileSystem = virtualFileSystem;
@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
TimeZones = new AvaloniaList<TimeZone>();
AvailableGpus = new ObservableCollection<ComboBoxItem>();
_validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends();
PopulateNetworkInterfaces();
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)
{
if (_validTzRegions.Contains(location))
@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
}
public void SaveSettings()
@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();

View File

@ -22,230 +22,231 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
namespace Ryujinx.Ava.UI.ViewModels;
public class TitleUpdateViewModel : BaseModel
namespace Ryujinx.Ava.UI.ViewModels
{
public TitleUpdateMetadata _titleUpdateWindowData;
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
public class TitleUpdateViewModel : BaseModel
{
get => _titleUpdates;
set
public TitleUpdateMetadata _titleUpdateWindowData;
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;
OnPropertyChanged();
}
}
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
get => _titleUpdates;
set
{
Selected = "",
Paths = new List<string>()
};
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];
_titleUpdates = value;
OnPropertyChanged();
}
}
}
private void AddUpdate(string path)
{
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
public AvaloniaList<object> Views
{
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
{
(Nca patchNca, Nca controlNca) = ApplicationLibrary.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();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
LoadUpdates();
}
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
{
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 () =>
{
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)
{
TitleUpdates.Remove(update);
SortUpdates();
}
public async void Add()
{
OpenFileDialog dialog = new()
public void RemoveUpdate(TitleUpdateModel update)
{
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true
};
TitleUpdates.Remove(update);
dialog.Filters.Add(new FileDialogFilter
SortUpdates();
}
public async void Add()
{
Name = "NSP",
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
OpenFileDialog dialog = new()
{
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()
{
_titleUpdateWindowData.Paths.Clear();
_titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates)
public void Save()
{
_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>
<Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
<Separator />
<MenuItem
Click="OpenSettings"

View File

@ -11,6 +11,8 @@ using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
{
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();
string localePath = "Ryujinx.Ava/Assets/Locales";
@ -42,7 +68,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
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))
{
@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
menuItems.Add(menuItem);
}
ChangeLanguageMenuItem.Items = menuItems.ToArray();
return menuItems.ToArray();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

View File

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -29,7 +29,18 @@
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</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>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

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

View File

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

View File

@ -6,11 +6,8 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Helper;
using System.IO;
using System.Text;
using System.Threading.Tasks;
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
{
None,
@ -9,4 +13,4 @@
SmaaHigh,
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
{
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
{
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
{
Vulkan,

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
public enum GraphicsDebugLevel
{
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
{
Unbound,
@ -51,4 +55,4 @@
Count
}
}
}

View File

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

View File

@ -1,9 +1,12 @@
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[Flags]
// 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
{
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
{
Invalid,

View File

@ -1,8 +1,10 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonInputConfigConverter))]
public class InputConfig : INotifyPropertyChanged
{
/// <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.Keyboard;
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
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)
{
// Temporary reader to get the backend type
@ -54,8 +57,8 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch
{
InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
};
}
@ -65,10 +68,10 @@ namespace Ryujinx.Common.Configuration.Hid
switch (value.Backend)
{
case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
break;
case InputBackendType.GamepadSDL2:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
break;
default:
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
{
Unknown,
@ -136,4 +140,4 @@
Count
}
}
}

View File

@ -1,5 +1,7 @@
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 Key ToggleVsync { get; set; }
@ -12,4 +14,4 @@
public Key VolumeUp { 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
{
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
public enum PlayerIndex : int
{
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
{
SoftwarePageTable,

View File

@ -1,5 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
public enum ScalingFilter
{
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.Reflection;
using System.Text;
using System.Text;
namespace Ryujinx.Common.Logging
{
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)
{
StringBuilder sb = _stringBuilderPool.Allocate();
StringBuilder sb = StringBuilderPool.Allocate();
try
{
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]}| ");
if (args.ThreadName != null)
@ -27,53 +25,17 @@ namespace Ryujinx.Common.Logging
sb.Append(args.Message);
if (args.Data != null)
if (args.Data is not null)
{
PropertyInfo[] props = args.Data.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(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('}');
sb.Append(' ');
DynamicObjectFormatter.Format(sb, args.Data);
}
return sb.ToString();
}
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
{
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
public enum LogClass
{
Application,

View File

@ -11,15 +11,7 @@ namespace Ryujinx.Common.Logging
public readonly string Message;
public readonly object Data;
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message)
{
Level = level;
Time = time;
ThreadName = threadName;
Message = message;
}
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data)
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
{
Level = level;
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
{
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
public enum LogLevel
{
Debug,

View File

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

View File

@ -33,6 +33,11 @@ namespace Ryujinx.Common.Memory
return data;
}
public ReadOnlySpan<byte> GetSpanSafe(int size)
{
return GetSpan((int)Math.Min((uint)_input.Length, (uint)size));
}
public T ReadAt<T>(int offset) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(_input.Slice(offset))[0];

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 Ryujinx.Common.Configuration.Hid.Controller.Motion;
using System.IO;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Ryujinx.Common.Utilities
{
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
{
@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities
return name;
}
StringBuilder builder = new StringBuilder();
StringBuilder builder = new();
for (int i = 0; i < name.Length; i++)
{
@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities
}
else
{
builder.Append("_");
builder.Append('_');
builder.Append(char.ToLowerInvariant(c));
}
}
@ -47,64 +94,5 @@ namespace Ryujinx.Common.Utilities
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));
}
}
}
}

View File

@ -0,0 +1,66 @@
using System.Net.NetworkInformation;
namespace Ryujinx.Common.Utilities
{
public static class NetworkHelpers
{
private static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(NetworkInterface adapter, bool isPreferred)
{
IPInterfaceProperties properties = adapter.GetIPProperties();
if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0))
{
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
{
// Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4)
{
return (properties, info);
}
}
}
return (null, null);
}
public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(string lanInterfaceId = "0")
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return (null, null);
}
IPInterfaceProperties targetProperties = null;
UnicastIPAddressInformation targetAddressInfo = null;
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
string guid = lanInterfaceId;
bool hasPreference = guid != "0";
foreach (NetworkInterface adapter in interfaces)
{
bool isPreferred = adapter.Id == guid;
// Ignore loopback and non IPv4 capable interface.
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
{
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
if (properties != null)
{
targetProperties = properties;
targetAddressInfo = info;
if (isPreferred || !hasPreference)
{
break;
}
}
}
}
return (targetProperties, targetAddressInfo);
}
}
}

View File

@ -0,0 +1,34 @@
#nullable enable
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Utilities
{
/// <summary>
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
/// </summary>
/// <remarks>
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
/// Get rid of this converter if dotnet supports similar functionality out of the box.
/// </remarks>
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var enumValue = reader.GetString();
if (string.IsNullOrEmpty(enumValue))
{
return default;
}
return Enum.Parse<TEnum>(enumValue);
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}

View File

@ -38,4 +38,25 @@ namespace Ryujinx.Graphics.GAL
Src1AlphaGl = 0xc902,
OneMinusSrc1AlphaGl = 0xc903
}
public static class BlendFactorExtensions
{
public static bool IsDualSource(this BlendFactor factor)
{
switch (factor)
{
case BlendFactor.Src1Color:
case BlendFactor.Src1ColorGl:
case BlendFactor.Src1Alpha:
case BlendFactor.Src1AlphaGl:
case BlendFactor.OneMinusSrc1Color:
case BlendFactor.OneMinusSrc1ColorGl:
case BlendFactor.OneMinusSrc1Alpha:
case BlendFactor.OneMinusSrc1AlphaGl:
return true;
}
return false;
}
}
}

View File

@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.GAL
public readonly float MaximumSupportedAnisotropy;
public readonly int StorageBufferOffsetAlignment;
public readonly int GatherBiasPrecision;
public Capabilities(
TargetApi api,
string vendorName,
@ -87,7 +89,8 @@ namespace Ryujinx.Graphics.GAL
uint maximumImagesPerStage,
int maximumComputeSharedMemorySize,
float maximumSupportedAnisotropy,
int storageBufferOffsetAlignment)
int storageBufferOffsetAlignment,
int gatherBiasPrecision)
{
Api = api;
VendorName = vendorName;
@ -128,6 +131,7 @@ namespace Ryujinx.Graphics.GAL
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
GatherBiasPrecision = gatherBiasPrecision;
}
}
}

View File

@ -328,5 +328,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Signal();
}
}
/// <summary>
/// Sets the dual-source blend enabled state.
/// </summary>
/// <param name="enabled">True if blending is enabled and using dual-source blend</param>
public void SetDualSourceBlendEnabled(bool enabled)
{
if (enabled != _graphics.DualSourceBlendEnable)
{
_graphics.DualSourceBlendEnable = enabled;
Signal();
}
}
}
}

View File

@ -1183,6 +1183,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool blendIndependent = _state.State.BlendIndependent;
ColorF blendConstant = _state.State.BlendConstant;
bool dualSourceBlendEnabled = false;
if (blendIndependent)
{
for (int index = 0; index < Constants.TotalRenderTargets; index++)
@ -1200,6 +1202,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaSrcFactor, index),
FilterBlendFactor(blend.AlphaDstFactor, index));
if (enable &&
(blend.ColorSrcFactor.IsDualSource() ||
blend.ColorDstFactor.IsDualSource() ||
blend.AlphaSrcFactor.IsDualSource() ||
blend.AlphaDstFactor.IsDualSource()))
{
dualSourceBlendEnabled = true;
}
_pipeline.BlendDescriptors[index] = descriptor;
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
}
@ -1219,12 +1230,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaSrcFactor, 0),
FilterBlendFactor(blend.AlphaDstFactor, 0));
if (enable &&
(blend.ColorSrcFactor.IsDualSource() ||
blend.ColorDstFactor.IsDualSource() ||
blend.AlphaSrcFactor.IsDualSource() ||
blend.AlphaDstFactor.IsDualSource()))
{
dualSourceBlendEnabled = true;
}
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
_pipeline.BlendDescriptors[index] = descriptor;
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
}
}
_currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
}
/// <summary>

View File

@ -732,12 +732,7 @@ namespace Ryujinx.Graphics.Gpu.Image
break;
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
// We don't support copy between multisample and non-multisample depth-stencil textures
// because there's no way to emulate that since most GPUs don't support writing a
// custom stencil value into the texture, among several other API limitations.
if ((rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) &&
!rhs.FormatInfo.Format.IsDepthOrStencil())
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return TextureViewCompatibility.CopyOnly;
}

View File

@ -141,6 +141,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 4368;
private const uint CodeGenVersion = 4703;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -157,6 +157,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _state.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{

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