Compare commits

...

28 Commits

Author SHA1 Message Date
jhorv
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
Mary
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
dependabot[bot]
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
gdkchan
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
TSRBerry
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
TSRBerry
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
Andrey Sukharev
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
dependabot[bot]
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
Mary
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
TSRBerry
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
Ac_K
4c2d9ff3ff HLE: Refactoring of ApplicationLoader (#4480)
* HLE: Refactoring of ApplicationLoader

* Fix SDL2 Headless

* Addresses gdkchan feedback

* Fixes LoadUnpackedNca RomFS loading

* remove useless casting

* Cleanup and fixe empty application name

* Remove ProcessInfo

* Fixes typo

* ActiveProcess to ActiveApplication

* Update check

* Clean using.

* Use the correct filepath when loading Homebrew.npdm

* Fix NRE in ProcessResult if MetaLoader is null

* Add more checks for valid processId & return success

* Add missing logging statement for npdm error

* Return result for LoadKip()

* Move error logging out of PFS load extension method

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

* Fix GUIs not checking load results

* Fix style and formatting issues

* Fix formatting and wording

* gtk: Refactor LoadApplication()

---------

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

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

* Remove a redundant type conversion

* Reduce nesting by inverting "if" statements

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

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

* Add line breaks to long LINQ

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

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

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

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

* Visual Studio Moment

* Address feedback

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

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

* add step to tag commit with release version

* Rename step to “Create Tag”

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

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

* Use separate model for LogEventArgs serialization

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

* Do not inherit json version of LogEventArgs from EventArgs

* Fix extra space in object formatting

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

* Rebase fixes

* Rebase fixes

* Rebase fixes

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

* Apply suggestions from code review

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

* Rebase indent fix

* Fix indent

* Delete unnecessary json properties

* Rebase fix

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

* Apply suggestions from code review

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

* Use default json options in github api calls

* Indentation and spacing fixes

---------

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

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

* ARMeilleure: Add initial support for EVEX instruction encoding

Does not implement rounding, or exception controls.

* ARMeilleure: Add `X86Vpternlogd`

Accelerates the vector-`Not` instruction.

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

* ARMeilleure: Add check for `XCR0` flags

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

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

* ARMeilleure: Move XCR0 procedure to GetXcr0Eax

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

* ARMeilleure: Utilize `ReadOnlySpan` for Xcr0 assembly

Avoids an additional allocation

* ARMeilleure: Formatting fixes

* ARMeilleure: Fix EVEX encoding src2 register index

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

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

Passes unit tests, verified instruction utilization

* ARMeilleure: Fix EVEX register operand designations

Operand 2 was being sourced improperly.

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

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

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

* ARMeilleure: PTC version bump

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

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

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

* Ensure migrated data is in place before flushing.

* Fix issue where old waitable would be signalled.

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

* Swap bound Auto<> instances when swapping buffer backing

* Fix conversion buffers

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

* Make GPU methods return PinnedSpan with scope

* Storage Hint

* Fix stupidity

* Fix rebase

* Tweak rules

Attempt to sidestep BOTW slowdown

* Remove line

* Migrate only when command buffers flush

* Change backing swap log to debug

* Address some feedback

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

* Make PinnedSpan from ReadOnlySpan explicitly unsafe

* Fix some small issues

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

* Remove alternative flags

* Address feedback
2023-03-19 17:56:48 -03:00
207 changed files with 4374 additions and 2856 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
Reg8Dest = 1 << 2, Reg8Dest = 1 << 2,
RexW = 1 << 3, RexW = 1 << 3,
Vex = 1 << 4, Vex = 1 << 4,
Evex = 1 << 5,
PrefixBit = 16, PrefixBit = 16,
PrefixMask = 7 << PrefixBit, PrefixMask = 7 << PrefixBit,
@@ -278,6 +279,7 @@ namespace ARMeilleure.CodeGen.X86
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None)); Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex)); Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));

View File

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

View File

@@ -180,6 +180,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma)); Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary)); Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary)); Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
} }

View File

@@ -219,6 +219,7 @@ namespace ARMeilleure.CodeGen.X86
Vfnmsub231sd, Vfnmsub231sd,
Vfnmsub231ss, Vfnmsub231ss,
Vpblendvb, Vpblendvb,
Vpternlogd,
Xor, Xor,
Xorpd, Xorpd,
Xorps, Xorps,

View File

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

View File

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

View File

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

View File

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

View File

@@ -173,6 +173,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Vfnmadd231ss, X86Vfnmadd231ss,
X86Vfnmsub231sd, X86Vfnmsub231sd,
X86Vfnmsub231ss, X86Vfnmsub231ss,
X86Vpternlogd,
X86Xorpd, X86Xorpd,
X86Xorps, X86Xorps,

View File

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

View File

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

View File

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

View File

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

View File

@@ -320,10 +320,14 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true; _viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}"; var activeProcess = Device.Processes.ActiveApplication;
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}"; var nacp = activeProcess.ApplicationControlProperties;
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})"; int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
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)";
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava
private void Dispose() private void Dispose()
{ {
if (Device.Application != null) if (Device.Processes != null)
{ {
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText); _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
} }
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
Device.LoadNca(ApplicationPath); if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
else if (Directory.Exists(ApplicationPath)) else if (Directory.Exists(ApplicationPath))
{ {
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
Device.LoadCart(ApplicationPath, romFsFiles[0]); if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
{
Device.Dispose();
return false;
}
} }
else else
{ {
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
Device.LoadCart(ApplicationPath); if (!Device.LoadCart(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
} }
else if (File.Exists(ApplicationPath)) else if (File.Exists(ApplicationPath))
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as XCI."); Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
Device.LoadXci(ApplicationPath); if (!Device.LoadXci(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as NCA."); Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
Device.LoadNca(ApplicationPath); if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava
{ {
Logger.Info?.Print(LogClass.Application, "Loading as NSP."); Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
Device.LoadNsp(ApplicationPath); if (!Device.LoadNsp(ApplicationPath))
{
Device.Dispose();
return false;
}
break; break;
} }
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava
try try
{ {
Device.LoadProgram(ApplicationPath); if (!Device.LoadProgram(ApplicationPath))
{
Device.Dispose();
return false;
}
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
{ {
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
Dispose(); Device.Dispose();
return false; return false;
} }
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava
{ {
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Dispose(); Device.Dispose();
return false; return false;
} }
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{ {
appMetadata.LastPlayed = DateTime.UtcNow.ToString(); appMetadata.LastPlayed = DateTime.UtcNow.ToString();
}); });
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava
{ {
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{ {
Device.Application.DiskCacheLoadState?.Cancel(); Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
} }
}); });
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava
return state; return state;
} }
} }
} }

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Buffers; using System.Buffers;
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
return; return;
} }
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null) if (updatePatchNca != null)
{ {
patchNca = updatePatchNca; patchNca = updatePatchNca;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void SetUIProgressHandlers(Switch emulationContext) public void SetUIProgressHandlers(Switch emulationContext)
{ {
if (emulationContext.Application.DiskCacheLoadState != null) if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
{ {
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
} }
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
if (string.IsNullOrWhiteSpace(titleName)) if (string.IsNullOrWhiteSpace(titleName))
{ {
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Application.TitleName; TitleName = AppHost.Device.Processes.ActiveApplication.Name;
} }
SwitchToRenderer(startFullscreen); SwitchToRenderer(startFullscreen);

View File

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

View File

@@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson); var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
if (!strings.TryGetValue("Language", out string languageName)) if (!strings.TryGetValue("Language", out string languageName))
{ {
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{ {
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper(); string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
await window.ShowDialog(Window); await window.ShowDialog(Window);
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
return; return;
} }
ApplicationLoader application = ViewModel.AppHost.Device.Application; string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
if (application != null)
{
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats(); await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
}
ViewModel.AppHost.Device.EnableCheats();
} }
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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, Src1AlphaGl = 0xc902,
OneMinusSrc1AlphaGl = 0xc903 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

@@ -15,7 +15,12 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false); void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size); BufferHandle CreateBuffer(int size, BufferHandle storageHint);
BufferHandle CreateBuffer(int size)
{
return CreateBuffer(size, BufferHandle.Null);
}
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
@@ -26,7 +31,7 @@ namespace Ryujinx.Graphics.GAL
void DeleteBuffer(BufferHandle buffer); void DeleteBuffer(BufferHandle buffer);
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size); PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
Capabilities GetCapabilities(); Capabilities GetCapabilities();
ulong GetCurrentSync(); ulong GetCurrentSync();

View File

@@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.GAL
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel); ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
ReadOnlySpan<byte> GetData(); PinnedSpan<byte> GetData();
ReadOnlySpan<byte> GetData(int layer, int level); PinnedSpan<byte> GetData(int layer, int level);
void SetData(SpanOrArray<byte> data); void SetData(SpanOrArray<byte> data);
void SetData(SpanOrArray<byte> data, int layer, int level); void SetData(SpanOrArray<byte> data, int layer, int level);

View File

@@ -21,9 +21,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ReadOnlySpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size); PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
command._result.Get(threaded).Result = new PinnedSpan<byte>(result); command._result.Get(threaded).Result = result;
} }
} }
} }

View File

@@ -5,16 +5,25 @@
public CommandType CommandType => CommandType.CreateBuffer; public CommandType CommandType => CommandType.CreateBuffer;
private BufferHandle _threadedHandle; private BufferHandle _threadedHandle;
private int _size; private int _size;
private BufferHandle _storageHint;
public void Set(BufferHandle threadedHandle, int size) public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
{ {
_threadedHandle = threadedHandle; _threadedHandle = threadedHandle;
_size = size; _size = size;
_storageHint = storageHint;
} }
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size)); BufferHandle hint = BufferHandle.Null;
if (command._storageHint != BufferHandle.Null)
{
hint = threaded.Buffers.MapBuffer(command._storageHint);
}
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
} }
} }
} }

View File

@@ -18,9 +18,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(); PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
command._result.Get(threaded).Result = new PinnedSpan<byte>(result); command._result.Get(threaded).Result = result;
} }
} }
} }

View File

@@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level); PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
command._result.Get(threaded).Result = new PinnedSpan<byte>(result); command._result.Get(threaded).Result = result;
} }
} }
} }

View File

@@ -1,23 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.GAL.Multithreading.Model
{
unsafe struct PinnedSpan<T> where T : unmanaged
{
private void* _ptr;
private int _size;
public PinnedSpan(ReadOnlySpan<T> span)
{
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
_size = span.Length;
}
public ReadOnlySpan<T> Get()
{
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
}
}
}

View File

@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return newTex; return newTex;
} }
public ReadOnlySpan<byte> GetData() public PinnedSpan<byte> GetData()
{ {
if (_renderer.IsGpuThread()) if (_renderer.IsGpuThread())
{ {
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box)); _renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
_renderer.InvokeCommand(); _renderer.InvokeCommand();
return box.Result.Get(); return box.Result;
} }
else else
{ {
@@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
} }
} }
public ReadOnlySpan<byte> GetData(int layer, int level) public PinnedSpan<byte> GetData(int layer, int level)
{ {
if (_renderer.IsGpuThread()) if (_renderer.IsGpuThread())
{ {
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level); _renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
_renderer.InvokeCommand(); _renderer.InvokeCommand();
return box.Result.Get(); return box.Result;
} }
else else
{ {

View File

@@ -265,10 +265,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
} }
} }
public BufferHandle CreateBuffer(int size) public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{ {
BufferHandle handle = Buffers.CreateBufferHandle(); BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateBufferCommand>().Set(handle, size); New<CreateBufferCommand>().Set(handle, size, storageHint);
QueueCommand(); QueueCommand();
return handle; return handle;
@@ -329,7 +329,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
QueueCommand(); QueueCommand();
} }
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size) public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{ {
if (IsGpuThread()) if (IsGpuThread())
{ {
@@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box)); New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
InvokeCommand(); InvokeCommand();
return box.Result.Get(); return box.Result;
} }
else else
{ {

View File

@@ -0,0 +1,53 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.GAL
{
public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
{
private void* _ptr;
private int _size;
private Action _disposeAction;
/// <summary>
/// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
/// The data must be guaranteed to live until disposeAction is called.
/// </summary>
/// <param name="span">Existing span</param>
/// <param name="disposeAction">Action to call on dispose</param>
/// <remarks>
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
/// </remarks>
public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
{
return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
}
/// <summary>
/// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
/// </summary>
/// <param name="ptr">Pointer to the region</param>
/// <param name="size">The total items of T the region contains</param>
/// <param name="disposeAction">Action to call on dispose</param>
/// <remarks>
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
/// </remarks>
public PinnedSpan(void* ptr, int size, Action disposeAction = null)
{
_ptr = ptr;
_size = size;
_disposeAction = disposeAction;
}
public ReadOnlySpan<T> Get()
{
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
}
public void Dispose()
{
_disposeAction?.Invoke();
}
}
}

View File

@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int firstInstance = (int)_state.State.FirstInstance; int firstInstance = (int)_state.State.FirstInstance;
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(); int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
if (inlineIndexCount != 0) if (inlineIndexCount != 0)
{ {
@@ -670,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
if (indexedInline) if (indexedInline)
{ {
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(); int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);

View File

@@ -11,9 +11,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
struct IbStreamer struct IbStreamer
{ {
private const int BufferCapacity = 256; // Must be a power of 2.
private BufferHandle _inlineIndexBuffer; private BufferHandle _inlineIndexBuffer;
private int _inlineIndexBufferSize; private int _inlineIndexBufferSize;
private int _inlineIndexCount; private int _inlineIndexCount;
private uint[] _buffer;
private int _bufferOffset;
/// <summary> /// <summary>
/// Indicates if any index buffer data has been pushed. /// Indicates if any index buffer data has been pushed.
@@ -38,9 +42,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Gets the number of elements on the current inline index buffer, /// Gets the number of elements on the current inline index buffer,
/// while also reseting it to zero for the next draw. /// while also reseting it to zero for the next draw.
/// </summary> /// </summary>
/// <param name="renderer">Host renderer</param>
/// <returns>Inline index bufffer count</returns> /// <returns>Inline index bufffer count</returns>
public int GetAndResetInlineIndexCount() public int GetAndResetInlineIndexCount(IRenderer renderer)
{ {
UpdateRemaining(renderer);
int temp = _inlineIndexCount; int temp = _inlineIndexCount;
_inlineIndexCount = 0; _inlineIndexCount = 0;
return temp; return temp;
@@ -58,16 +64,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
byte i2 = (byte)(argument >> 16); byte i2 = (byte)(argument >> 16);
byte i3 = (byte)(argument >> 24); byte i3 = (byte)(argument >> 24);
Span<uint> data = stackalloc uint[4]; int offset = _inlineIndexCount;
data[0] = i0; PushData(renderer, offset, i0);
data[1] = i1; PushData(renderer, offset + 1, i1);
data[2] = i2; PushData(renderer, offset + 2, i2);
data[3] = i3; PushData(renderer, offset + 3, i3);
int offset = _inlineIndexCount * 4;
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
_inlineIndexCount += 4; _inlineIndexCount += 4;
} }
@@ -82,14 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ushort i0 = (ushort)argument; ushort i0 = (ushort)argument;
ushort i1 = (ushort)(argument >> 16); ushort i1 = (ushort)(argument >> 16);
Span<uint> data = stackalloc uint[2]; int offset = _inlineIndexCount;
data[0] = i0; PushData(renderer, offset, i0);
data[1] = i1; PushData(renderer, offset + 1, i1);
int offset = _inlineIndexCount * 4;
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
_inlineIndexCount += 2; _inlineIndexCount += 2;
} }
@@ -103,13 +101,61 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
uint i0 = (uint)argument; uint i0 = (uint)argument;
Span<uint> data = stackalloc uint[1]; int offset = _inlineIndexCount++;
data[0] = i0; PushData(renderer, offset, i0);
}
int offset = _inlineIndexCount++ * 4; /// <summary>
/// Pushes a 32-bit value to the index buffer.
/// </summary>
/// <param name="renderer">Host renderer</param>
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
/// <param name="value">Index value to be written</param>
private void PushData(IRenderer renderer, int offset, uint value)
{
if (_buffer == null)
{
_buffer = new uint[BufferCapacity];
}
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data)); // We upload data in chunks.
// If we are at the start of a chunk, then the buffer might be full,
// in that case we need to submit any existing data before overwriting the buffer.
int subOffset = offset & (BufferCapacity - 1);
if (subOffset == 0 && offset != 0)
{
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
}
_buffer[subOffset] = value;
}
/// <summary>
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
/// </summary>
/// <param name="renderer">Host renderer</param>
private void UpdateRemaining(IRenderer renderer)
{
int offset = _inlineIndexCount;
if (offset == 0)
{
return;
}
int count = offset & (BufferCapacity - 1);
if (count == 0)
{
count = BufferCapacity;
}
int baseOffset = (offset - count) * sizeof(uint);
int length = count * sizeof(uint);
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
} }
/// <summary> /// <summary>
@@ -117,12 +163,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
/// <param name="renderer">Host renderer</param> /// <param name="renderer">Host renderer</param>
/// <param name="offset">Offset where the data will be written</param> /// <param name="offset">Offset where the data will be written</param>
/// <param name="length">Number of bytes that will be written</param>
/// <returns>Buffer handle</returns> /// <returns>Buffer handle</returns>
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset) private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
{ {
// Calculate a reasonable size for the buffer that can fit all the data, // Calculate a reasonable size for the buffer that can fit all the data,
// and that also won't require frequent resizes if we need to push more data. // and that also won't require frequent resizes if we need to push more data.
int size = BitUtils.AlignUp(offset + 0x10, 0x200); int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
if (_inlineIndexBuffer == BufferHandle.Null) if (_inlineIndexBuffer == BufferHandle.Null)
{ {

View File

@@ -328,5 +328,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Signal(); 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; bool blendIndependent = _state.State.BlendIndependent;
ColorF blendConstant = _state.State.BlendConstant; ColorF blendConstant = _state.State.BlendConstant;
bool dualSourceBlendEnabled = false;
if (blendIndependent) if (blendIndependent)
{ {
for (int index = 0; index < Constants.TotalRenderTargets; index++) for (int index = 0; index < Constants.TotalRenderTargets; index++)
@@ -1200,6 +1202,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaSrcFactor, index), FilterBlendFactor(blend.AlphaSrcFactor, index),
FilterBlendFactor(blend.AlphaDstFactor, 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; _pipeline.BlendDescriptors[index] = descriptor;
_context.Renderer.Pipeline.SetBlendState(index, descriptor); _context.Renderer.Pipeline.SetBlendState(index, descriptor);
} }
@@ -1219,12 +1230,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
FilterBlendFactor(blend.AlphaSrcFactor, 0), FilterBlendFactor(blend.AlphaSrcFactor, 0),
FilterBlendFactor(blend.AlphaDstFactor, 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++) for (int index = 0; index < Constants.TotalRenderTargets; index++)
{ {
_pipeline.BlendDescriptors[index] = descriptor; _pipeline.BlendDescriptors[index] = descriptor;
_context.Renderer.Pipeline.SetBlendState(index, descriptor); _context.Renderer.Pipeline.SetBlendState(index, descriptor);
} }
} }
_currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
} }
/// <summary> /// <summary>

View File

@@ -1022,13 +1022,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// This method should be used to retrieve data that was modified by the host GPU. /// This method should be used to retrieve data that was modified by the host GPU.
/// This is not cheap, avoid doing that unless strictly needed. /// This is not cheap, avoid doing that unless strictly needed.
/// </remarks> /// </remarks>
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param> /// <param name="output">An output span to place the texture data into</param>
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param> /// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param> /// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
/// <returns>The span containing the texture data</returns> private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
{ {
ReadOnlySpan<byte> data; PinnedSpan<byte> data;
if (texture != null) if (texture != null)
{ {
@@ -1054,9 +1053,9 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
data = ConvertFromHostCompatibleFormat(output, data); ConvertFromHostCompatibleFormat(output, data.Get());
return data; data.Dispose();
} }
/// <summary> /// <summary>
@@ -1071,10 +1070,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">The level of the texture to flush</param> /// <param name="level">The level of the texture to flush</param>
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param> /// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param> /// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
/// <returns>The span containing the texture data</returns> public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
{ {
ReadOnlySpan<byte> data; PinnedSpan<byte> data;
if (texture != null) if (texture != null)
{ {
@@ -1100,9 +1098,9 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
data = ConvertFromHostCompatibleFormat(output, data, level, true); ConvertFromHostCompatibleFormat(output, data.Get(), level, true);
return data; data.Dispose();
} }
/// <summary> /// <summary>

View File

@@ -130,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image
return ref descriptor; return ref descriptor;
} }
} }
else
{
texture.SynchronizeMemory();
}
Items[id] = texture; Items[id] = texture;
@@ -233,7 +237,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
/// <summary> /// <summary>
/// Queues a request to update a texture's mapping. /// Queues a request to update a texture's mapping.
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped. /// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
/// </summary> /// </summary>
/// <param name="texture">Texture with potential mapping change</param> /// <param name="texture">Texture with potential mapping change</param>

View File

@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Address = address; Address = address;
Size = size; Size = size;
Handle = context.Renderer.CreateBuffer((int)size); Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
_useGranular = size > GranularBufferThreshold; _useGranular = size > GranularBufferThreshold;
@@ -415,10 +415,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
int offset = (int)(address - Address); int offset = (int)(address - Address);
ReadOnlySpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size); using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, data); _physicalMemory.WriteUntracked(address, data.Get());
} }
/// <summary> /// <summary>

View File

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

View File

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

View File

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

View File

@@ -92,6 +92,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
public Array8<AttributeType> FragmentOutputTypes; public Array8<AttributeType> FragmentOutputTypes;
/// <summary>
/// Indicates whether dual source blend is enabled.
/// </summary>
public bool DualSourceBlendEnable;
/// <summary> /// <summary>
/// Creates a new GPU graphics state. /// Creates a new GPU graphics state.
/// </summary> /// </summary>
@@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param> /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param> /// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param> /// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Type of the vertex attributes consumed by the shader</param>
public GpuChannelGraphicsState( public GpuChannelGraphicsState(
bool earlyZForce, bool earlyZForce,
PrimitiveTopology topology, PrimitiveTopology topology,
@@ -127,7 +133,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ref Array32<AttributeType> attributeTypes, ref Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters, bool hasConstantBufferDrawParameters,
bool hasUnalignedStorageBuffer, bool hasUnalignedStorageBuffer,
ref Array8<AttributeType> fragmentOutputTypes) ref Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable)
{ {
EarlyZForce = earlyZForce; EarlyZForce = earlyZForce;
Topology = topology; Topology = topology;
@@ -145,6 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters; HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer; HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
FragmentOutputTypes = fragmentOutputTypes; FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
} }
} }
} }

View File

@@ -535,6 +535,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
return false; return false;
} }
if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
{
return false;
}
return Matches(channel, ref poolState, checkTextures, isCompute: false); return Matches(channel, ref poolState, checkTextures, isCompute: false);
} }

View File

@@ -55,11 +55,14 @@ namespace Ryujinx.Graphics.OpenGL
(IntPtr)size); (IntPtr)size);
} }
public static unsafe ReadOnlySpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size) public static unsafe PinnedSpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
{ {
// Data in the persistent buffer and host array is guaranteed to be available
// until the next time the host thread requests data.
if (HwCapabilities.UsePersistentBufferForFlush) if (HwCapabilities.UsePersistentBufferForFlush)
{ {
return renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size); return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
} }
else else
{ {
@@ -69,7 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target); GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
return new ReadOnlySpan<byte>(target.ToPointer(), size); return new PinnedSpan<byte>(target.ToPointer(), size);
} }
} }

View File

@@ -39,12 +39,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotSupportedException(); throw new NotSupportedException();
} }
public ReadOnlySpan<byte> GetData() public PinnedSpan<byte> GetData()
{ {
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize); return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
} }
public ReadOnlySpan<byte> GetData(int layer, int level) public PinnedSpan<byte> GetData(int layer, int level)
{ {
return GetData(); return GetData();
} }

View File

@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter); _renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
} }
public unsafe ReadOnlySpan<byte> GetData() public unsafe PinnedSpan<byte> GetData()
{ {
int size = 0; int size = 0;
int levels = Info.GetLevelsClamped(); int levels = Info.GetLevelsClamped();
@@ -196,16 +196,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
data = FormatConverter.ConvertD24S8ToS8D24(data); data = FormatConverter.ConvertD24S8ToS8D24(data);
} }
return data; return PinnedSpan<byte>.UnsafeFromSpan(data);
} }
public unsafe ReadOnlySpan<byte> GetData(int layer, int level) public unsafe PinnedSpan<byte> GetData(int layer, int level)
{ {
int size = Info.GetMipSize(level); int size = Info.GetMipSize(level);
if (HwCapabilities.UsePersistentBufferForFlush) if (HwCapabilities.UsePersistentBufferForFlush)
{ {
return _renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level); return PinnedSpan<byte>.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
} }
else else
{ {
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
int offset = WriteTo2D(target, layer, level); int offset = WriteTo2D(target, layer, level);
return new ReadOnlySpan<byte>(target.ToPointer(), size).Slice(offset); return new PinnedSpan<byte>((byte*)target.ToPointer() + offset, size);
} }
} }

View File

@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.OpenGL
ResourcePool = new ResourcePool(); ResourcePool = new ResourcePool();
} }
public BufferHandle CreateBuffer(int size) public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{ {
BufferCount++; BufferCount++;
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.OpenGL
return new HardwareInfo(GpuVendor, GpuRenderer); return new HardwareInfo(GpuVendor, GpuRenderer);
} }
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size) public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{ {
return Buffer.GetData(this, buffer, offset, size); return Buffer.GetData(this, buffer, offset, size);
} }

View File

@@ -833,31 +833,13 @@ namespace Ryujinx.Graphics.OpenGL
(BlendingFactorSrc)blend.AlphaSrcFactor.Convert(), (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(),
(BlendingFactorDest)blend.AlphaDstFactor.Convert()); (BlendingFactorDest)blend.AlphaDstFactor.Convert());
static bool IsDualSource(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;
}
EnsureFramebuffer(); EnsureFramebuffer();
_framebuffer.SetDualSourceBlend( _framebuffer.SetDualSourceBlend(
IsDualSource(blend.ColorSrcFactor) || blend.ColorSrcFactor.IsDualSource() ||
IsDualSource(blend.ColorDstFactor) || blend.ColorDstFactor.IsDualSource() ||
IsDualSource(blend.AlphaSrcFactor) || blend.AlphaSrcFactor.IsDualSource() ||
IsDualSource(blend.AlphaDstFactor)); blend.AlphaDstFactor.IsDualSource());
if (_blendConstant != blend.BlendConstant) if (_blendConstant != blend.BlendConstant)
{ {

View File

@@ -612,6 +612,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
else else
{ {
int usedAttributes = context.Config.UsedOutputAttributes; int usedAttributes = context.Config.UsedOutputAttributes;
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
{
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
int mask = 3 << firstOutput;
if ((usedAttributes & mask) == mask)
{
usedAttributes &= ~mask;
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
}
}
while (usedAttributes != 0) while (usedAttributes != 0)
{ {
int index = BitOperations.TrailingZeroCount(usedAttributes); int index = BitOperations.TrailingZeroCount(usedAttributes);
@@ -690,6 +703,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
} }
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
{
string name = $"{DefaultNames.OAttributePrefix}{attr}";
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs) private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{ {
foreach (int attr in attrs.Order()) foreach (int attr in attrs.Order())

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Numerics;
using static Spv.Specification; using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
@@ -622,7 +623,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd) else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
{ {
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16; int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
{
int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
int index = location - firstLocation;
int mask = 3 << firstLocation;
if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
{
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
}
else
{
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
}
else
{
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
} }
if (!isOutAttr) if (!isOutAttr)

View File

@@ -205,6 +205,15 @@ namespace Ryujinx.Graphics.Shader
return false; return false;
} }
/// <summary>
/// Queries dual source blend state.
/// </summary>
/// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
bool QueryDualSourceBlendEnable()
{
return false;
}
/// <summary> /// <summary>
/// Queries host about the presence of the FrontFacing built-in variable bug. /// Queries host about the presence of the FrontFacing built-in variable bug.
/// </summary> /// </summary>

View File

@@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Vulkan
{
internal enum BufferAllocationType
{
Auto = 0,
HostMappedNoCache,
HostMapped,
DeviceLocal,
DeviceLocalMapped
}
}

View File

@@ -1,7 +1,10 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using VkBuffer = Silk.NET.Vulkan.Buffer; using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkFormat = Silk.NET.Vulkan.Format; using VkFormat = Silk.NET.Vulkan.Format;
@@ -11,6 +14,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
private const int MaxUpdateBufferSize = 0x10000; private const int MaxUpdateBufferSize = 0x10000;
private const int SetCountThreshold = 100;
private const int WriteCountThreshold = 50;
private const int FlushCountThreshold = 5;
public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
public const AccessFlags DefaultAccessFlags = public const AccessFlags DefaultAccessFlags =
AccessFlags.IndirectCommandReadBit | AccessFlags.IndirectCommandReadBit |
AccessFlags.ShaderReadBit | AccessFlags.ShaderReadBit |
@@ -21,10 +30,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
private readonly MemoryAllocation _allocation; private MemoryAllocation _allocation;
private readonly Auto<DisposableBuffer> _buffer; private Auto<DisposableBuffer> _buffer;
private readonly Auto<MemoryAllocation> _allocationAuto; private Auto<MemoryAllocation> _allocationAuto;
private readonly ulong _bufferHandle; private ulong _bufferHandle;
private CacheByRange<BufferHolder> _cachedConvertedBuffers; private CacheByRange<BufferHolder> _cachedConvertedBuffers;
@@ -32,11 +41,28 @@ namespace Ryujinx.Graphics.Vulkan
private IntPtr _map; private IntPtr _map;
private readonly MultiFenceHolder _waitable; private MultiFenceHolder _waitable;
private bool _lastAccessIsWrite; private bool _lastAccessIsWrite;
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size) private BufferAllocationType _baseType;
private BufferAllocationType _currentType;
private bool _swapQueued;
public BufferAllocationType DesiredType { get; private set; }
private int _setCount;
private int _writeCount;
private int _flushCount;
private int _flushTemp;
private ReaderWriterLock _flushLock;
private FenceHolder _flushFence;
private int _flushWaiting;
private List<Action> _swapActions;
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
{ {
_gd = gd; _gd = gd;
_device = device; _device = device;
@@ -47,9 +73,153 @@ namespace Ryujinx.Graphics.Vulkan
_bufferHandle = buffer.Handle; _bufferHandle = buffer.Handle;
Size = size; Size = size;
_map = allocation.HostPointer; _map = allocation.HostPointer;
_baseType = type;
_currentType = currentType;
DesiredType = currentType;
_flushLock = new ReaderWriterLock();
} }
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size) public bool TryBackingSwap(ref CommandBufferScoped? cbs)
{
if (_swapQueued && DesiredType != _currentType)
{
// Only swap if the buffer is not used in any queued command buffer.
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
{
var currentAllocation = _allocationAuto;
var currentBuffer = _buffer;
IntPtr currentMap = _map;
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
if (buffer.Handle != 0)
{
_flushLock.AcquireWriterLock(Timeout.Infinite);
ClearFlushFence();
_waitable = new MultiFenceHolder(Size);
_allocation = allocation;
_allocationAuto = new Auto<MemoryAllocation>(allocation);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
_map = allocation.HostPointer;
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
{
// Copy data directly. Readbacks don't have to wait if this is done.
unsafe
{
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
}
}
else
{
if (cbs == null)
{
cbs = _gd.CommandBufferPool.Rent();
}
CommandBufferScoped cbsV = cbs.Value;
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
// Need to wait for the data to reach the new buffer before data can be flushed.
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
_flushFence.Get();
}
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
_currentType = resultType;
if (_swapActions != null)
{
foreach (var action in _swapActions)
{
action();
}
_swapActions.Clear();
}
currentBuffer.Dispose();
currentAllocation.Dispose();
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
_flushLock.ReleaseWriterLock();
}
_swapQueued = false;
return true;
}
else
{
return false;
}
}
else
{
_swapQueued = false;
return true;
}
}
private void ConsiderBackingSwap()
{
if (_baseType == BufferAllocationType.Auto)
{
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
{
if (_flushCount > 0 || _flushTemp-- > 0)
{
// Buffers that flush should ideally be mapped in host address space for easy copies.
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
// It's harder for a buffer that is flushed to revert to another type of mapping.
if (_flushCount > 0)
{
_flushTemp = 1000;
}
}
else if (_writeCount >= WriteCountThreshold)
{
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
DesiredType = BufferAllocationType.DeviceLocal;
}
else if (_setCount > SetCountThreshold)
{
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
DesiredType = BufferAllocationType.HostMapped;
}
_flushCount = 0;
_writeCount = 0;
_setCount = 0;
}
if (!_swapQueued && DesiredType != _currentType)
{
_swapQueued = true;
_gd.PipelineInternal.AddBackingSwap(this);
}
}
}
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
{ {
var bufferViewCreateInfo = new BufferViewCreateInfo() var bufferViewCreateInfo = new BufferViewCreateInfo()
{ {
@@ -62,9 +232,19 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError(); _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
(_swapActions ??= new List<Action>()).Add(invalidateView);
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer); return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
} }
public void InheritMetrics(BufferHolder other)
{
_setCount = other._setCount;
_writeCount = other._writeCount;
_flushCount = other._flushCount;
_flushTemp = other._flushTemp;
}
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite) public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
{ {
// If the last access is write, we always need a barrier to be sure we will read or modify // If the last access is write, we always need a barrier to be sure we will read or modify
@@ -104,12 +284,22 @@ namespace Ryujinx.Graphics.Vulkan
return _buffer; return _buffer;
} }
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false) public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
{ {
if (isWrite) if (isWrite)
{ {
_writeCount++;
SignalWrite(0, Size); SignalWrite(0, Size);
} }
else if (isSSBO)
{
// Always consider SSBO access for swapping to device local memory.
_writeCount++;
ConsiderBackingSwap();
}
return _buffer; return _buffer;
} }
@@ -118,6 +308,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (isWrite) if (isWrite)
{ {
_writeCount++;
SignalWrite(offset, size); SignalWrite(offset, size);
} }
@@ -126,6 +318,8 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalWrite(int offset, int size) public void SignalWrite(int offset, int size)
{ {
ConsiderBackingSwap();
if (offset == 0 && size == Size) if (offset == 0 && size == Size)
{ {
_cachedConvertedBuffers.Clear(); _cachedConvertedBuffers.Clear();
@@ -147,11 +341,76 @@ namespace Ryujinx.Graphics.Vulkan
return _map; return _map;
} }
public unsafe ReadOnlySpan<byte> GetData(int offset, int size) private void ClearFlushFence()
{ {
// Asusmes _flushLock is held as writer.
if (_flushFence != null)
{
if (_flushWaiting == 0)
{
_flushFence.Put();
}
_flushFence = null;
}
}
private void WaitForFlushFence()
{
// Assumes the _flushLock is held as reader, returns in same state.
if (_flushFence != null)
{
// If storage has changed, make sure the fence has been reached so that the data is in place.
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
if (_flushFence != null)
{
var fence = _flushFence;
Interlocked.Increment(ref _flushWaiting);
// Don't wait in the lock.
var restoreCookie = _flushLock.ReleaseLock();
fence.Wait();
_flushLock.RestoreLock(ref restoreCookie);
if (Interlocked.Decrement(ref _flushWaiting) == 0)
{
fence.Put();
}
_flushFence = null;
}
_flushLock.DowngradeFromWriterLock(ref cookie);
}
}
public unsafe PinnedSpan<byte> GetData(int offset, int size)
{
_flushLock.AcquireReaderLock(Timeout.Infinite);
WaitForFlushFence();
_flushCount++;
Span<byte> result;
if (_map != IntPtr.Zero) if (_map != IntPtr.Zero)
{ {
return GetDataStorage(offset, size); result = GetDataStorage(offset, size);
// Need to be careful here, the buffer can't be unmapped while the data is being used.
_buffer.IncrementReferenceCount();
_flushLock.ReleaseReaderLock();
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
} }
else else
{ {
@@ -161,12 +420,17 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_gd.FlushAllCommands(); _gd.FlushAllCommands();
return resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size); result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
} }
else else
{ {
return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size); result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
} }
_flushLock.ReleaseReaderLock();
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
return PinnedSpan<byte>.UnsafeFromSpan(result);
} }
} }
@@ -190,6 +454,8 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
_setCount++;
if (_map != IntPtr.Zero) if (_map != IntPtr.Zero)
{ {
// If persistently mapped, set the data directly if the buffer is not currently in use. // If persistently mapped, set the data directly if the buffer is not currently in use.
@@ -268,6 +534,8 @@ namespace Ryujinx.Graphics.Vulkan
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value; var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
_writeCount--;
InsertBufferBarrier( InsertBufferBarrier(
_gd, _gd,
cbs.CommandBuffer, cbs.CommandBuffer,
@@ -502,11 +770,19 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose() public void Dispose()
{ {
_swapQueued = false;
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size); _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
_buffer.Dispose(); _buffer.Dispose();
_allocationAuto.Dispose(); _allocationAuto.Dispose();
_cachedConvertedBuffers.Dispose(); _cachedConvertedBuffers.Dispose();
_flushLock.AcquireWriterLock(Timeout.Infinite);
ClearFlushFence();
_flushLock.ReleaseWriterLock();
} }
} }
} }

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