Compare commits

...

17 Commits

Author SHA1 Message Date
riperiperi
43b4b34376 Implement Viewport Transform Disable (#3328)
* Initial implementation (no specialization)

* Use specialization

* Fix render scale, increase code gen version

* Revert accidental change

* Address Feedback
2022-05-12 10:47:13 -03:00
Ac_K
92ca1cb0cb hid: Various fixes and cleanup (#3326)
* hid: Various fix and cleanup

* Add IsValidNpadIdType

* remove ()
2022-05-08 00:28:54 +02:00
riperiperi
50d7ecf76d Add alternative "GL" enum values for StencilOp (#3321)
This PR adds the alternative enum values for StencilOp. Similar to the other enums, I added these with the same names but with Gl added to the end. These are used by homebrew using Nouveau, though they might be used by games with the official Vulkan driver.

39d90be897/rnndb/graph/nv_3ddefs.xml (L77)

Fixes some broken graphics in Citra, such as missing shadows in Mario Kart 7. Likely fixes other homebrew.
2022-05-05 21:16:58 +02:00
gdkchan
42a2a80b87 Enable JIT service LLE (#2959)
* Enable JIT service LLE

* Force disable PPTC when using the JIT service

PPTC does not support multiple guest processes

* Fix build

* Make SM service registration per emulation context rather than global

* Address PR feedback
2022-05-05 15:23:30 -03:00
gdkchan
54deded929 Fix shared memory leak on Windows (#3319)
* Fix shared memory leak on Windows

* Fix memory leak caused by RO session disposal not decrementing the memory manager ref count

* Fix UnmapViewInternal deadlock

* Was not supposed to add those back
2022-05-05 14:58:59 -03:00
Mary
39bdf6d41e infra: Warn about support drop of old Windows versions (#3299)
* infra: Warn about support drop of old Windows versions

See #3298.

* Address comment
2022-05-04 20:21:27 +02:00
gdkchan
074190e03c Remove AddProtection count > 0 assert (#3315) 2022-05-04 14:07:10 -03:00
voldemort2826
256514c7c9 Update the artifact build's version number (#3297) 2022-05-03 23:35:12 +02:00
gdkchan
556be08c4e Implement PM GetProcessInfo atmosphere extension (partially) (#2966) 2022-05-03 23:28:32 +02:00
gdkchan
1cbca5eecb Implement code memory syscalls (#2958)
* Implement code memory syscalls

* Remove owner process validation

* Add 32-bit code memory syscalls

* Remove unused field
2022-05-03 13:16:31 +02:00
gdkchan
95017b8c66 Support memory aliasing (#2954)
* Back to the origins: Make memory manager take guest PA rather than host address once again

* Direct mapping with alias support on Windows

* Fixes and remove more of the emulated shared memory

* Linux support

* Make shared and transfer memory not depend on SharedMemoryStorage

* More efficient view mapping on Windows (no more restricted to 4KB pages at a time)

* Handle potential access violations caused by partial unmap

* Implement host mapping using shared memory on Linux

* Add new GetPhysicalAddressChecked method, used to ensure the virtual address is mapped before address translation

Also align GetRef behaviour with software memory manager

* We don't need a mirrorable memory block for software memory manager mode

* Disable memory aliasing tests while we don't have shared memory support on Mac

* Shared memory & SIGBUS handler for macOS

* Fix typo + nits + re-enable memory tests

* Set MAP_JIT_DARWIN on x86 Mac too

* Add back the address space mirror

* Only set MAP_JIT_DARWIN if we are mapping as executable

* Disable aliasing tests again (still fails on Mac)

* Fix UnmapView4KB (by not casting size to int)

* Use ref counting on memory blocks to delay closing the shared memory handle until all blocks using it are disposed

* Address PR feedback

* Make RO hold a reference to the guest process memory manager to avoid early disposal

Co-authored-by: nastys <nastys@users.noreply.github.com>
2022-05-02 20:30:02 -03:00
riperiperi
4a892fbdc9 Fix flush action from multiple threads regression (#3311)
If two or more threads encounter a region of memory where a read action has been registered, then they must _both_ wait on the data.

Clearing the action before it completed was causing the null check above to fail, so the action would only be run on the first thread, and the second would end up continuing without waiting. Depending on what the game does, this could be disasterous.

This fixes a regression introduced by #3302 with Pokemon Legends Arceus, and possibly Catherine. There are likely other affected games. What is fixed in that PR should still be fixed.
2022-05-02 12:31:53 +02:00
gdkchan
9eb5b7a10d Restrict cases where vertex buffer size from index buffer type is used (#3304) 2022-05-01 11:12:34 -03:00
riperiperi
d64594ec74 Fix various issues with texture sync (#3302)
* Fix various issues with texture sync

A variable called _actionRegistered is used to keep track of whether a tracking action has been registered for a given texture group handle. This variable is set when the action is registered, and should be unset when it is consumed. This is used to skip registering the tracking action if it's already registered, saving some time for render targets that are modified very often.

There were two issues with this. The worst issue was that the tracking action handler exits early if the handle's modified flag is false... which means that it never reset _actionRegistered, as that was done within the Sync() method called later. The second issue was that this variable was set true after the sync action was registered, so it was technically possible for the action to run immediately, set the flag to false, then set it to true.

Both situations would lead to the action never being registered again, as the texture group handle would be sure the action is already registered. This breaks the texture for the remaining runtime, or until it is disposed.

It was also possible for a texture to register sync once, then on future frames the last modified sync number did not update. This may have caused some more minor issues.

Seems to fix the Xenoblade flashing bug. Obviously this needs a lot of testing, since it was random chance. I typically had the most luck getting it to happen by switching time of day on the event theatre screen for a while, then entering the equipment screen by pressing X on an event.

May also fix weird things like random chance air swimming in BOTW, maybe a few texture streaming bugs.

* Exchange rather than CompareExchange
2022-04-29 18:34:11 -03:00
merry
6a1a03566a T32: Implement load/store single (immediate) (#3186)
* T32: Implement load/store single (immediate)

* tests

* tidy formatting

* address comments
2022-04-21 01:25:43 +02:00
Harone
13f5294aa3 Update NpadController.cs (#3284) 2022-04-20 13:22:45 +02:00
gdkchan
9444b4a647 Implement HwOpus multistream functions (#3275)
* Implement HwOpus multistream functions

* Avoid one copy
2022-04-15 23:16:28 +02:00
119 changed files with 4936 additions and 2657 deletions

View File

@@ -46,6 +46,7 @@ jobs:
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1.0"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
@@ -59,24 +60,24 @@ jobs:
- name: Clear
run: dotnet clean && dotnet nuget locals all --clear
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test
run: dotnet test -c "${{ matrix.configuration }}"
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
if: github.event_name == 'pull_request'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
if: github.event_name == 'pull_request'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v2
with:
name: ryujinx-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v2
with:
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request'

View File

@@ -0,0 +1,25 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem
{
public int Rt { get; }
public int Rn { get; }
public bool WBack => false;
public bool IsLoad { get; }
public bool Index => true;
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode);
public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 12) & 0xf;
Rn = (opCode >> 16) & 0xf;
Immediate = opCode & 0xfff;
IsLoad = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@@ -0,0 +1,29 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem
{
public int Rt { get; }
public int Rn { get; }
public bool WBack { get; }
public bool IsLoad { get; }
public bool Index { get; }
public bool Add { get; }
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode);
public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 12) & 0xf;
Rn = (opCode >> 16) & 0xf;
Index = ((opCode >> 10) & 1) != 0;
Add = ((opCode >> 9) & 1) != 0;
WBack = ((opCode >> 8) & 1) != 0;
Immediate = opCode & 0xff;
IsLoad = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@@ -1065,6 +1065,26 @@ namespace ARMeilleure.Decoders
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
SetT32("111110000101xxxx<<<<10x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110000101xxxx<<<<1100xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110000101xxxx<<<<11x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110001101xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm12.Create);
SetT32("111110000001xxxx<<<<10x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110000001xxxx<<<<1100xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110000001xxxx<<<<11x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110001001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm12.Create);
SetT32("111110000011xxxx<<<<10x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110000011xxxx<<<<1100xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110000011xxxx<<<<11x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110001011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm12.Create);
SetT32("111110010001xxxx<<<<10x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110010001xxxx<<<<1100xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110010001xxxx<<<<11x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110011001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm12.Create);
SetT32("111110010011xxxx<<<<10x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110010011xxxx<<<<1100xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110010011xxxx<<<<11x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110011011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm12.Create);
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
@@ -1077,6 +1097,12 @@ namespace ARMeilleure.Decoders
SetT32("11110x01110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluImm.Create);
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
SetT32("11110x01011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluImm.Create);
SetT32("111110000100xxxxxxxx1<<>xxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm8.Create);
SetT32("111110001100xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm12.Create);
SetT32("111110000000xxxxxxxx1<<>xxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm8.Create);
SetT32("111110001000xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm12.Create);
SetT32("111110000010xxxxxxxx1<<>xxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm8.Create);
SetT32("111110001010xxxxxxxxxxxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm12.Create);
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);

View File

@@ -103,7 +103,7 @@ namespace ARMeilleure.Signal
// Unix siginfo struct locations.
// NOTE: These are incredibly likely to be different between kernel version and architectures.
config.StructAddressOffset = 16; // si_addr
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
config.StructWriteOffset = 8; // si_code
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));

View File

@@ -21,6 +21,7 @@ namespace ARMeilleure.Signal
static class UnixSignalHandlerRegistration
{
private const int SIGSEGV = 11;
private const int SIGBUS = 10;
private const int SA_SIGINFO = 0x00000004;
[DllImport("libc", SetLastError = true)]
@@ -43,7 +44,17 @@ namespace ARMeilleure.Signal
if (result != 0)
{
throw new InvalidOperationException($"Could not register sigaction. Error: {result}");
throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
}
if (OperatingSystem.IsMacOS())
{
result = sigaction(SIGBUS, ref sig, out SigAction oldb);
if (result != 0)
{
throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
}
}
return old;
@@ -51,7 +62,7 @@ namespace ARMeilleure.Signal
public static bool RestoreExceptionHandler(SigAction oldAction)
{
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
}
}
}

View File

@@ -8,7 +8,7 @@ namespace ARMeilleure.Translation
/// </summary>
/// <typeparam name="K">Key</typeparam>
/// <typeparam name="V">Value</typeparam>
public class IntervalTree<K, V> where K : IComparable<K>
class IntervalTree<K, V> where K : IComparable<K>
{
private const int ArrayGrowthSize = 32;
@@ -53,7 +53,7 @@ namespace ARMeilleure.Translation
/// <returns>Number of intervals found</returns>
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
{
GetValues(_root, start, end, ref overlaps, ref overlapCount);
GetKeys(_root, start, end, ref overlaps, ref overlapCount);
return overlapCount;
}
@@ -180,20 +180,20 @@ namespace ARMeilleure.Translation
}
/// <summary>
/// Retrieve all values that overlap the given start and end keys.
/// Retrieve all keys that overlap the given start and end keys.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Overlaps count to update</param>
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
private void GetKeys(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
{
if (node == null || start.CompareTo(node.Max) >= 0)
{
return;
}
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
GetKeys(node.Left, start, end, ref overlaps, ref overlapCount);
bool endsOnRight = end.CompareTo(node.Start) > 0;
if (endsOnRight)
@@ -208,7 +208,7 @@ namespace ARMeilleure.Translation
overlaps[overlapCount++] = node.Start;
}
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
GetKeys(node.Right, start, end, ref overlaps, ref overlapCount);
}
}
@@ -717,40 +717,40 @@ namespace ARMeilleure.Translation
/// </summary>
/// <typeparam name="K">Key type of the node</typeparam>
/// <typeparam name="V">Value type of the node</typeparam>
internal class IntervalTreeNode<K, V>
class IntervalTreeNode<K, V>
{
internal bool Color = true;
internal IntervalTreeNode<K, V> Left = null;
internal IntervalTreeNode<K, V> Right = null;
internal IntervalTreeNode<K, V> Parent = null;
public bool Color = true;
public IntervalTreeNode<K, V> Left = null;
public IntervalTreeNode<K, V> Right = null;
public IntervalTreeNode<K, V> Parent = null;
/// <summary>
/// The start of the range.
/// </summary>
internal K Start;
public K Start;
/// <summary>
/// The end of the range.
/// </summary>
internal K End;
public K End;
/// <summary>
/// The maximum end value of this node and all its children.
/// </summary>
internal K Max;
public K Max;
/// <summary>
/// Value stored on this node.
/// </summary>
internal V Value;
public V Value;
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
{
this.Start = start;
this.End = end;
this.Max = end;
this.Value = value;
this.Parent = parent;
Start = start;
End = end;
Max = end;
Value = value;
Parent = parent;
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Ryujinx.Common.Collections
@@ -779,37 +777,37 @@ namespace Ryujinx.Common.Collections
/// </summary>
/// <typeparam name="K">Key type of the node</typeparam>
/// <typeparam name="V">Value type of the node</typeparam>
internal class IntervalTreeNode<K, V>
class IntervalTreeNode<K, V>
{
internal bool Color = true;
internal IntervalTreeNode<K, V> Left = null;
internal IntervalTreeNode<K, V> Right = null;
internal IntervalTreeNode<K, V> Parent = null;
public bool Color = true;
public IntervalTreeNode<K, V> Left = null;
public IntervalTreeNode<K, V> Right = null;
public IntervalTreeNode<K, V> Parent = null;
/// <summary>
/// The start of the range.
/// </summary>
internal K Start;
public K Start;
/// <summary>
/// The end of the range - maximum of all in the Values list.
/// </summary>
internal K End;
public K End;
/// <summary>
/// The maximum end value of this node and all its children.
/// </summary>
internal K Max;
public K Max;
internal List<RangeNode<K, V>> Values;
public List<RangeNode<K, V>> Values;
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
{
this.Start = start;
this.End = end;
this.Max = end;
this.Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
this.Parent = parent;
Start = start;
End = end;
Max = end;
Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
Parent = parent;
}
}
}

View File

@@ -967,20 +967,20 @@ namespace Ryujinx.Common.Collections
/// </summary>
/// <typeparam name="K">Key of the node</typeparam>
/// <typeparam name="V">Value of the node</typeparam>
internal class Node<K, V>
class Node<K, V>
{
internal bool Color = true;
internal Node<K, V> Left = null;
internal Node<K, V> Right = null;
internal Node<K, V> Parent = null;
internal K Key;
internal V Value;
public bool Color = true;
public Node<K, V> Left = null;
public Node<K, V> Right = null;
public Node<K, V> Parent = null;
public K Key;
public V Value;
public Node(K key, V value, Node<K, V> parent)
{
this.Key = key;
this.Value = value;
this.Parent = parent;
Key = key;
Value = value;
Parent = parent;
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Cpu
_baseAddress = (ulong)_addressSpace.Pointer;
ulong endAddress = _baseAddress + addressSpace.Size;
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEventEh);
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)

View File

@@ -25,6 +25,7 @@ namespace Ryujinx.Cpu
private const int PointerTagBit = 62;
private readonly MemoryBlock _backingMemory;
private readonly InvalidAccessHandler _invalidAccessHandler;
/// <summary>
@@ -50,10 +51,12 @@ namespace Ryujinx.Cpu
/// <summary>
/// Creates a new instance of the memory manager.
/// </summary>
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
/// <param name="addressSpaceSize">Size of the address space</param>
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
public MemoryManager(ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
{
_backingMemory = backingMemory;
_invalidAccessHandler = invalidAccessHandler;
ulong asSize = PageSize;
@@ -73,18 +76,19 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
public void Map(ulong va, nuint hostAddress, ulong size)
public void Map(ulong va, ulong pa, ulong size)
{
AssertValidAddressAndSize(va, size);
ulong remainingSize = size;
ulong oVa = va;
ulong oPa = pa;
while (remainingSize != 0)
{
_pageTable.Write((va / PageSize) * PteSize, hostAddress);
_pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
va += PageSize;
hostAddress += PageSize;
pa += PageSize;
remainingSize -= PageSize;
}
Tracking.Map(oVa, size);
@@ -107,7 +111,7 @@ namespace Ryujinx.Cpu
ulong remainingSize = size;
while (remainingSize != 0)
{
_pageTable.Write((va / PageSize) * PteSize, (nuint)0);
_pageTable.Write((va / PageSize) * PteSize, 0UL);
va += PageSize;
remainingSize -= PageSize;
@@ -123,8 +127,21 @@ namespace Ryujinx.Cpu
/// <inheritdoc/>
public T ReadTracked<T>(ulong va) where T : unmanaged
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
try
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
return Read<T>(va);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return default;
}
}
/// <inheritdoc/>
@@ -177,7 +194,7 @@ namespace Ryujinx.Cpu
if (IsContiguousAndMapped(va, data.Length))
{
data.CopyTo(GetHostSpanContiguous(va, data.Length));
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
}
else
{
@@ -185,18 +202,22 @@ namespace Ryujinx.Cpu
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
}
}
}
@@ -224,7 +245,7 @@ namespace Ryujinx.Cpu
if (IsContiguousAndMapped(va, size))
{
return GetHostSpanContiguous(va, size);
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
}
else
{
@@ -251,7 +272,7 @@ namespace Ryujinx.Cpu
SignalMemoryTracking(va, (ulong)size, true);
}
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
else
{
@@ -264,7 +285,7 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
public ref T GetRef<T>(ulong va) where T : unmanaged
{
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
{
@@ -273,7 +294,7 @@ namespace Ryujinx.Cpu
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
return ref *(T*)GetHostAddress(va);
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
}
/// <summary>
@@ -293,7 +314,7 @@ namespace Ryujinx.Cpu
return (int)(vaSpan / PageSize);
}
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
@@ -315,7 +336,7 @@ namespace Ryujinx.Cpu
return false;
}
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
{
return false;
}
@@ -327,11 +348,11 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
if (size == 0)
{
return Enumerable.Empty<HostMemoryRange>();
return Enumerable.Empty<MemoryRange>();
}
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
@@ -341,9 +362,9 @@ namespace Ryujinx.Cpu
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<HostMemoryRange>();
var regions = new List<MemoryRange>();
nuint regionStart = GetHostAddress(va);
ulong regionStart = GetPhysicalAddressInternal(va);
ulong regionSize = PageSize;
for (int page = 0; page < pages - 1; page++)
@@ -353,12 +374,12 @@ namespace Ryujinx.Cpu
return null;
}
nuint newHostAddress = GetHostAddress(va + PageSize);
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
if (GetHostAddress(va) + PageSize != newHostAddress)
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
{
regions.Add(new HostMemoryRange(regionStart, regionSize));
regionStart = newHostAddress;
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
regionSize = 0;
}
@@ -366,7 +387,7 @@ namespace Ryujinx.Cpu
regionSize += PageSize;
}
regions.Add(new HostMemoryRange(regionStart, regionSize));
regions.Add(new MemoryRange(regionStart, regionSize));
return regions;
}
@@ -386,18 +407,22 @@ namespace Ryujinx.Cpu
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
@@ -446,7 +471,7 @@ namespace Ryujinx.Cpu
return false;
}
return _pageTable.Read<nuint>((va / PageSize) * PteSize) != 0;
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
}
private bool ValidateAddress(ulong va)
@@ -480,37 +505,20 @@ namespace Ryujinx.Cpu
}
}
/// <summary>
/// Get a span representing the given virtual address and size range in host memory.
/// This function assumes that the requested virtual memory region is contiguous.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>A span representing the given virtual range in host memory</returns>
/// <exception cref="InvalidMemoryRegionException">Throw when the base virtual address is not mapped</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
private ulong GetPhysicalAddress(ulong va)
{
return new Span<byte>((void*)GetHostAddress(va), size);
}
/// <summary>
/// Get the host address for a given virtual address, using the page table.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>The corresponding host address for the given virtual address</returns>
/// <exception cref="InvalidMemoryRegionException">Throw when the virtual address is not mapped</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private nuint GetHostAddress(ulong va)
{
nuint pageBase = _pageTable.Read<nuint>((va / PageSize) * PteSize) & unchecked((nuint)0xffff_ffff_ffffUL);
if (pageBase == 0)
// We return -1L if the virtual address is invalid or unmapped.
if (!ValidateAddress(va) || !IsMapped(va))
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
return ulong.MaxValue;
}
return pageBase + (nuint)(va & PageMask);
return GetPhysicalAddressInternal(va);
}
private ulong GetPhysicalAddressInternal(ulong va)
{
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
}
/// <inheritdoc/>
@@ -604,6 +612,16 @@ namespace Ryujinx.Cpu
}
}
private ulong PaToPte(ulong pa)
{
return (ulong)_backingMemory.GetPointer(pa, PageSize);
}
private ulong PteToPa(ulong pte)
{
return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
}
/// <summary>
/// Disposes of resources used by the memory manager.
/// </summary>

View File

@@ -5,7 +5,6 @@ using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -14,7 +13,7 @@ namespace Ryujinx.Cpu
/// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary>
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@@ -42,9 +41,12 @@ namespace Ryujinx.Cpu
private readonly MemoryBlock _addressSpaceMirror;
private readonly ulong _addressSpaceSize;
private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable;
private readonly MemoryEhMeilleure _memoryEh;
private ulong[] _pageTable;
private readonly ulong[] _pageBitmap;
public int AddressSpaceBits { get; }
@@ -59,11 +61,14 @@ namespace Ryujinx.Cpu
/// <summary>
/// Creates a new instance of the host mapped memory manager.
/// </summary>
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
/// <param name="addressSpaceSize">Size of the address space</param>
/// <param name="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
public MemoryManagerHostMapped(ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
{
_backingMemory = backingMemory;
_pageTable = new PageTable<ulong>();
_invalidAccessHandler = invalidAccessHandler;
_unsafeMode = unsafeMode;
_addressSpaceSize = addressSpaceSize;
@@ -79,9 +84,13 @@ namespace Ryujinx.Cpu
AddressSpaceBits = asBits;
_pageTable = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
_addressSpace = new MemoryBlock(asSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable);
_addressSpaceMirror = _addressSpace.CreateMirror();
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
_addressSpace = new MemoryBlock(asSize, asFlags);
_addressSpaceMirror = new MemoryBlock(asSize, asFlags | MemoryAllocationFlags.ForceWindows4KBViewMapping);
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
_memoryEh = new MemoryEhMeilleure(_addressSpace, Tracking);
}
@@ -136,12 +145,14 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
public void Map(ulong va, nuint hostAddress, ulong size)
public void Map(ulong va, ulong pa, ulong size)
{
AssertValidAddressAndSize(va, size);
_addressSpace.Commit(va, size);
_addressSpace.MapView(_backingMemory, pa, va, size);
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
AddMapping(va, size);
PtMap(va, pa, size);
Tracking.Map(va, size);
}
@@ -155,7 +166,32 @@ namespace Ryujinx.Cpu
Tracking.Unmap(va, size);
RemoveMapping(va, size);
_addressSpace.Decommit(va, size);
PtUnmap(va, size);
_addressSpace.UnmapView(_backingMemory, va, size);
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
}
private void PtMap(ulong va, ulong pa, ulong size)
{
while (size != 0)
{
_pageTable.Map(va, pa);
va += PageSize;
pa += PageSize;
size -= PageSize;
}
}
private void PtUnmap(ulong va, ulong size)
{
while (size != 0)
{
_pageTable.Unmap(va);
va += PageSize;
size -= PageSize;
}
}
/// <inheritdoc/>
@@ -216,6 +252,7 @@ namespace Ryujinx.Cpu
}
}
/// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged
{
@@ -267,7 +304,7 @@ namespace Ryujinx.Cpu
throw;
}
}
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
@@ -322,7 +359,7 @@ namespace Ryujinx.Cpu
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageTable[pageIndex];
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
@@ -373,7 +410,7 @@ namespace Ryujinx.Cpu
mask &= endMask;
}
ref ulong pageRef = ref _pageTable[pageIndex++];
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
@@ -389,16 +426,53 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
if (size == 0)
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
ulong regionStart = GetPhysicalAddressChecked(va);
ulong regionSize = PageSize;
for (int page = 0; page < pages - 1; page++)
{
return Enumerable.Empty<HostMemoryRange>();
if (!ValidateAddress(va + PageSize))
{
return null;
}
ulong newPa = GetPhysicalAddressChecked(va + PageSize);
if (GetPhysicalAddressChecked(va) + PageSize != newPa)
{
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
regionSize = 0;
}
va += PageSize;
regionSize += PageSize;
}
AssertMapped(va, size);
regions.Add(new MemoryRange(regionStart, regionSize));
return new HostMemoryRange[] { new HostMemoryRange(_addressSpaceMirror.GetPointer(va, size), size) };
return regions;
}
private ulong GetPhysicalAddressChecked(ulong va)
{
if (!IsMapped(va))
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
}
return GetPhysicalAddressInternal(va);
}
private ulong GetPhysicalAddressInternal(ulong va)
{
return _pageTable.Read(va) + (va & PageMask);
}
/// <inheritdoc/>
@@ -427,7 +501,7 @@ namespace Ryujinx.Cpu
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageTable[pageIndex];
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
@@ -459,7 +533,7 @@ namespace Ryujinx.Cpu
mask &= endMask;
}
ref ulong pageRef = ref _pageTable[pageIndex++];
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
@@ -530,7 +604,7 @@ namespace Ryujinx.Cpu
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageTable[pageIndex];
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
@@ -562,7 +636,7 @@ namespace Ryujinx.Cpu
mask &= endMask;
}
ref ulong pageRef = ref _pageTable[pageIndex++];
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
@@ -632,7 +706,7 @@ namespace Ryujinx.Cpu
mask &= endMask;
}
ref ulong pageRef = ref _pageTable[pageIndex++];
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
@@ -677,7 +751,7 @@ namespace Ryujinx.Cpu
mask |= endMask;
}
ref ulong pageRef = ref _pageTable[pageIndex++];
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
@@ -695,11 +769,11 @@ namespace Ryujinx.Cpu
/// </summary>
protected override void Destroy()
{
_addressSpaceMirror.Dispose();
_addressSpace.Dispose();
_addressSpaceMirror.Dispose();
_memoryEh.Dispose();
}
private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
}
}

View File

@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
void SetViewports(int first, ReadOnlySpan<Viewport> viewports);
void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform);
void TextureBarrier();
void TextureBarrierTiled();

View File

@@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public CommandType CommandType => CommandType.SetViewports;
private int _first;
private SpanRef<Viewport> _viewports;
private bool _disableTransform;
public void Set(int first, SpanRef<Viewport> viewports)
public void Set(int first, SpanRef<Viewport> viewports, bool disableTransform)
{
_first = first;
_viewports = viewports;
_disableTransform = disableTransform;
}
public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
renderer.Pipeline.SetViewports(command._first, viewports);
renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform);
command._viewports.Dispose(threaded);
}
}

View File

@@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports));
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports), disableTransform);
_renderer.QueueCommand();
}

View File

@@ -9,6 +9,15 @@ namespace Ryujinx.Graphics.GAL
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
DecrementAndWrap,
ZeroGl = 0x0,
InvertGl = 0x150a,
KeepGl = 0x1e00,
ReplaceGl = 0x1e01,
IncrementAndClampGl = 0x1e02,
DecrementAndClampGl = 0x1e03,
IncrementAndWrapGl = 0x8507,
DecrementAndWrapGl = 0x8508
}
}

View File

@@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL
UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count);
}
public void UpdateViewportInverse(Vector4<float> data)
{
Data.ViewportInverse = data;
MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
}
public void Commit()
{
if (_startOffset != -1)

View File

@@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.DepthMode),
nameof(ThreedClassState.ViewportTransform),
nameof(ThreedClassState.ViewportExtents),
nameof(ThreedClassState.YControl)),
nameof(ThreedClassState.YControl),
nameof(ThreedClassState.ViewportTransformEnable)),
new StateUpdateCallbackEntry(UpdatePolygonMode,
nameof(ThreedClassState.PolygonModeFront),
@@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state.
if (_shaderSpecState != null)
{
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
{
ForceShaderUpdate();
}
@@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var yControl = _state.State.YControl;
var face = _state.State.FaceState;
bool disableTransform = _state.State.ViewportTransformEnable == 0;
UpdateFrontFace(yControl, face.FrontFace);
UpdateDepthMode();
@@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
for (int index = 0; index < Constants.TotalViewports; index++)
{
if (disableTransform)
{
ref var scissor = ref _state.State.ScreenScissorState;
float rScale = _channel.TextureManager.RenderTargetScale;
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
continue;
}
ref var transform = ref _state.State.ViewportTransform[index];
ref var extents = ref _state.State.ViewportExtents[index];
@@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
}
_context.Renderer.Pipeline.SetViewports(0, viewports);
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
}
/// <summary>
@@ -928,7 +942,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
size = endAddress.Pack() - address + 1;
if (stride > 0 && indexTypeSmall)
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
{
// If the index type is a small integer type, then we might be still able
// to reduce the vertex buffer size based on the maximum possible index value.
@@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return new GpuChannelGraphicsState(
_state.State.EarlyZForce,
_drawState.Topology,
_state.State.TessMode);
_state.State.TessMode,
_state.State.ViewportTransformEnable == 0);
}
/// <summary>

View File

@@ -1393,6 +1393,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="size">The size of the flushing memory access</param>
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
{
// There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions.
handle.ClearActionRegistered();
if (!handle.Modified)
{
return;

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
@@ -32,9 +33,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private ulong _modifiedSync;
/// <summary>
/// Whether a tracking action is currently registered or not.
/// Whether a tracking action is currently registered or not. (0/1)
/// </summary>
private bool _actionRegistered;
private int _actionRegistered;
/// <summary>
/// Whether a sync action is currently registered or not.
@@ -171,11 +172,9 @@ namespace Ryujinx.Graphics.Gpu.Image
_syncActionRegistered = true;
}
if (!_actionRegistered)
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
{
_group.RegisterAction(this);
_actionRegistered = true;
}
}
@@ -233,8 +232,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="context">The GPU context used to wait for sync</param>
public void Sync(GpuContext context)
{
_actionRegistered = false;
bool needsSync = !context.IsGpuThread();
if (needsSync)
@@ -263,21 +260,39 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Clears the action registered variable, indicating that the tracking action should be
/// re-registered on the next modification.
/// </summary>
public void ClearActionRegistered()
{
Interlocked.Exchange(ref _actionRegistered, 0);
}
/// <summary>
/// Action to perform when a sync number is registered after modification.
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
/// </summary>
private void SyncAction()
{
// The storage will need to signal modified again to update the sync number in future.
_group.Storage.SignalModifiedDirty();
lock (Overlaps)
{
foreach (Texture texture in Overlaps)
{
texture.SignalModifiedDirty();
}
}
// Register region tracking for CPU? (again)
_registeredSync = _modifiedSync;
_syncActionRegistered = false;
if (!_actionRegistered)
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
{
_group.RegisterAction(this);
_actionRegistered = true;
}
}

View File

@@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
topology,
tessMode);
tessMode,
false);
TransformFeedbackDescriptor[] tfdNew = null;

View File

@@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

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

View File

@@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public readonly TessMode TessellationMode;
/// <summary>
/// Indicates whenever the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
ViewportTransformDisable = viewportTransformDisable;
}
}
}

View File

@@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses)
{
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses))
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses))
{
return gpShaders;
}
if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode))
if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode))
{
_gpPrograms[addresses] = gpShaders;
return gpShaders;
@@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel using the shader</param>
/// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
/// <param name="gpShaders">Cached graphics shaders</param>
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
/// <returns>True if the code is different, false otherwise</returns>
private static bool IsShaderEqual(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
CachedShaderProgram gpShaders,
ShaderAddresses addresses)
{
@@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState);
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
}
/// <summary>

View File

@@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </remarks>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="addresses">Guest addresses of the shaders to find</param>
/// <param name="program">Cached host program for the given state, if found</param>
/// <param name="guestCode">Cached guest code, if any found</param>
@@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool TryFind(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses,
out CachedShaderProgram program,
out CachedGraphicsGuestCode guestCode)
@@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
{
return specList.TryFindForGraphics(channel, poolState, out program);
return specList.TryFindForGraphics(channel, poolState, graphicsState, out program);
}
return false;

View File

@@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="program">Cached program, if found</param>
/// <returns>True if a compatible program is found, false otherwise</returns>
public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
public bool TryFindForGraphics(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
out CachedShaderProgram program)
{
foreach (var entry in _entries)
{
if (entry.SpecializationState.MatchesGraphics(channel, poolState))
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
{
program = entry;
return true;

View File

@@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <returns>True if the state matches, false otherwise</returns>
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
{
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{
return false;
}
return Matches(channel, poolState, isCompute: false);
}

View File

@@ -379,20 +379,28 @@ namespace Ryujinx.Graphics.OpenGL
switch (op)
{
case GAL.StencilOp.Keep:
case GAL.StencilOp.KeepGl:
return OpenTK.Graphics.OpenGL.StencilOp.Keep;
case GAL.StencilOp.Zero:
case GAL.StencilOp.ZeroGl:
return OpenTK.Graphics.OpenGL.StencilOp.Zero;
case GAL.StencilOp.Replace:
case GAL.StencilOp.ReplaceGl:
return OpenTK.Graphics.OpenGL.StencilOp.Replace;
case GAL.StencilOp.IncrementAndClamp:
case GAL.StencilOp.IncrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Incr;
case GAL.StencilOp.DecrementAndClamp:
case GAL.StencilOp.DecrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Decr;
case GAL.StencilOp.Invert:
case GAL.StencilOp.InvertGl:
return OpenTK.Graphics.OpenGL.StencilOp.Invert;
case GAL.StencilOp.IncrementAndWrap:
case GAL.StencilOp.IncrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap;
case GAL.StencilOp.DecrementAndWrap:
case GAL.StencilOp.DecrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap;
}

View File

@@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetVertexBuffers(vertexBuffers);
}
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{
Array.Resize(ref _viewportArray, viewports.Length * 4);
Array.Resize(ref _depthRangeArray, viewports.Length * 2);
@@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL
GL.ViewportArray(first, viewports.Length, viewportArray);
GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
float disableTransformF = disableTransform ? 1.0f : 0.0f;
if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform)
{
float scale = _renderScale[0].X;
_supportBuffer.UpdateViewportInverse(new Vector4<float>
{
X = scale * 2f / viewports[first].Region.Width,
Y = scale * 2f / viewports[first].Region.Height,
Z = 1,
W = disableTransformF
});
}
}
public void TextureBarrier()

View File

@@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
}
else if (isFragment)
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
{
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
}
@@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{
bool isFragment = stage == ShaderStage.Fragment;
if (!isFragment && scaleElements == 0)
bool needsSupportBlock = stage == ShaderStage.Fragment ||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)
{
return;
}
@@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
case ShaderStage.Vertex:
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
break;
case ShaderStage.Compute:

View File

@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string SupportBlockName = "support_block";
public const string SupportBlockAlphaTestName = "s_alpha_test";
public const string SupportBlockIsBgraName = "s_is_bgra";
public const string SupportBlockViewportInverse = "s_viewport_inverse";
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
public const string SupportBlockRenderScaleName = "s_render_scale";

View File

@@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) }
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) },
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", VariableType.F32) },
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", VariableType.F32) }
};
private Dictionary<AstOperand, string> _locals;

View File

@@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader
return false;
}
/// <summary>
/// Queries if host state disables the viewport transform.
/// </summary>
/// <returns>True if the viewport transform is disabled</returns>
bool QueryViewportTransformDisable()
{
return false;
}
/// <summary>
/// Registers a texture used by the shader.
/// </summary>

View File

@@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (emit)
{
context.EmitVertex();
if (context.Config.LastInVertexPipeline)
{
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
context.EmitVertex();
// Restore output position value before transformation.
if (tempXLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
}
if (tempYLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
}
if (tempZLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
}
}
else
{
context.EmitVertex();
}
}
if (cut)

View File

@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
public static int FragmentAlphaTestOffset;
public static int FragmentIsBgraOffset;
public static int ViewportInverseOffset;
public static int FragmentRenderScaleCountOffset;
public static int GraphicsRenderScaleOffset;
public static int ComputeRenderScaleOffset;
@@ -40,6 +41,7 @@ namespace Ryujinx.Graphics.Shader
FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest);
FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra);
ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse);
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
@@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader
public Vector4<int> FragmentAlphaTest;
public Array8<Vector4<int>> FragmentIsBgra;
public Vector4<float> ViewportInverse;
public Vector4<int> FragmentRenderScaleCount;
// Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs.

View File

@@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int FragmentOutputIsBgraBase = 0x1000100;
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
public const int SupportBlockViewInverseX = 0x1000200;
public const int SupportBlockViewInverseY = 0x1000204;
public const int ThreadIdX = 0x2000000;
public const int ThreadIdY = 0x2000004;
public const int ThreadIdZ = 0x2000008;

View File

@@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation
return label;
}
public void PrepareForVertexReturn()
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
Operand negativeOne = ConstF(-1.0f);
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
}
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
oldXLocal = Local();
this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
oldYLocal = Local();
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
}
else
{
oldXLocal = null;
oldYLocal = null;
}
// Will be used by Vulkan backend for depth mode emulation.
oldZLocal = null;
PrepareForVertexReturn();
}
public void PrepareForReturn()
{
if (!IsNonMain && Config.Stage == ShaderStage.Fragment)
if (IsNonMain)
{
return;
}
if (Config.LastInVertexPipeline &&
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
{
PrepareForVertexReturn();
}
else if (Config.Stage == ShaderStage.Fragment)
{
if (Config.OmapDepth)
{

View File

@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage { get; }
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; private set; }
public int ThreadsPerInputPrimitive { get; }
@@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
public int GetDepthRegister()
@@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
if (config.Stage != ShaderStage.Fragment)
{
LastInVertexPipeline = false;
}
}
public void MergeOutputUserAttributes(int mask, int maskPerPatch)

View File

@@ -17,12 +17,14 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using static LibHac.Fs.ApplicationSaveDataManagement;
using static Ryujinx.HLE.HOS.ModLoader;
@@ -307,6 +309,94 @@ namespace Ryujinx.HLE.HOS
LoadNca(nca, null, null);
}
public void LoadServiceNca(string ncaFile)
{
// Disable PPTC here as it does not support multiple processes running.
// TODO: This should be eventually removed and it should stop using global state and
// instead manage the cache per process.
Ptc.Close();
PtcProfiler.Stop();
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
if (mainNca.Header.ContentType != NcaContentType.Program)
{
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
return;
}
IFileSystem codeFs = null;
if (mainNca.CanOpenSection(NcaSectionType.Code))
{
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
}
if (codeFs == null)
{
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
return;
}
using var npdmFile = new UniqueRef<IFile>();
Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
MetaLoader metaData;
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
var npdmBuffer = new byte[fileSize];
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
metaData = new MetaLoader();
metaData.Load(npdmBuffer).ThrowIfFailure();
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
for (int i = 0; i < nsos.Length; i++)
{
string name = ExeFsPrefixes[i];
if (!codeFs.FileExists($"/{name}"))
{
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
using var nsoFile = new UniqueRef<IFile>();
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
}
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0');
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
}
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
{
if (mainNca.Header.ContentType != NcaContentType.Program)
@@ -507,7 +597,7 @@ namespace Ryujinx.HLE.HOS
{
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{
metaData = null; //TODO: Check if we should retain old npdm
metaData = null; // TODO: Check if we should retain old npdm.
}
metaData ??= ReadNpdm(codeFs);
@@ -520,7 +610,7 @@ namespace Ryujinx.HLE.HOS
if (!codeFs.FileExists($"/{name}"))
{
continue; // file doesn't exist, skip
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
@@ -532,13 +622,13 @@ namespace Ryujinx.HLE.HOS
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// ExeFs file replacements
// ExeFs file replacements.
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
// collect the nsos, ignoring ones that aren't used
// Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
// take the npdm from mods if present
// Take the npdm from mods if present.
if (modLoadResult.Npdm != null)
{
metaData = modLoadResult.Npdm;
@@ -561,10 +651,21 @@ namespace Ryujinx.HLE.HOS
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
_device.Gpu.HostInitalized.Set();
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode);
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
}
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
// We allow it for nx-hbloader because it can be used to launch homebrew.
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL;
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs);
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: programs);
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
}
@@ -573,7 +674,7 @@ namespace Ryujinx.HLE.HOS
{
MetaLoader metaData = GetDefaultNpdm();
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm);
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: true);
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
@@ -586,7 +687,7 @@ namespace Ryujinx.HLE.HOS
executable = obj;
// homebrew NRO can actually have some data after the actual NRO
// Homebrew NRO can actually have some data after the actual NRO.
if (input.Length > obj.FileSize)
{
input.Position = obj.FileSize;
@@ -665,7 +766,7 @@ namespace Ryujinx.HLE.HOS
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
// Explicitly null titleid to disable the shader cache
// Explicitly null titleid to disable the shader cache.
Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set();

View File

@@ -21,15 +21,20 @@ namespace Ryujinx.HLE.HOS
{
MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode;
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
mode = MemoryManagerMode.SoftwarePageTable;
}
switch (mode)
{
case MemoryManagerMode.SoftwarePageTable:
return new ArmProcessContext<MemoryManager>(pid, _gpu, new MemoryManager(addressSpaceSize, invalidAccessHandler), for64Bit);
return new ArmProcessContext<MemoryManager>(pid, _gpu, new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler), for64Bit);
case MemoryManagerMode.HostMapped:
case MemoryManagerMode.HostMappedUnsafe:
bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _gpu, new MemoryManagerHostMapped(addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit);
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _gpu, new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit);
default:
throw new ArgumentOutOfRangeException();

View File

@@ -72,6 +72,8 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; }
internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
@@ -291,7 +293,8 @@ namespace Ryujinx.HLE.HOS
private void InitializeServices()
{
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext));
SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe.

View File

@@ -59,5 +59,15 @@ namespace Ryujinx.HLE.HOS.Kernel
{
return GetCurrentThread().Owner;
}
internal static KProcess GetProcessByPid(ulong pid)
{
if (Context.Processes.TryGetValue(pid, out KProcess process))
{
return process;
}
return null;
}
}
}

View File

@@ -0,0 +1,168 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using System;
using System.Diagnostics;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KCodeMemory : KAutoObject
{
public KProcess Owner { get; private set; }
private readonly KPageList _pageList;
private readonly object _lock;
private ulong _address;
private bool _isOwnerMapped;
private bool _isMapped;
public KCodeMemory(KernelContext context) : base(context)
{
_pageList = new KPageList();
_lock = new object();
}
public KernelResult Initialize(ulong address, ulong size)
{
Owner = KernelStatic.GetCurrentProcess();
KernelResult result = Owner.MemoryManager.BorrowCodeMemory(_pageList, address, size);
if (result != KernelResult.Success)
{
return result;
}
Owner.CpuMemory.Fill(address, size, 0xff);
Owner.IncrementReferenceCount();
_address = address;
_isMapped = false;
_isOwnerMapped = false;
return KernelResult.Success;
}
public KernelResult Map(ulong address, ulong size, KMemoryPermission perm)
{
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
lock (_lock)
{
if (_isMapped)
{
return KernelResult.InvalidState;
}
KProcess process = KernelStatic.GetCurrentProcess();
KernelResult result = process.MemoryManager.MapPages(address, _pageList, MemoryState.CodeWritable, KMemoryPermission.ReadAndWrite);
if (result != KernelResult.Success)
{
return result;
}
_isMapped = true;
}
return KernelResult.Success;
}
public KernelResult MapToOwner(ulong address, ulong size, KMemoryPermission permission)
{
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
lock (_lock)
{
if (_isOwnerMapped)
{
return KernelResult.InvalidState;
}
Debug.Assert(permission == KMemoryPermission.Read || permission == KMemoryPermission.ReadAndExecute);
KernelResult result = Owner.MemoryManager.MapPages(address, _pageList, MemoryState.CodeReadOnly, permission);
if (result != KernelResult.Success)
{
return result;
}
_isOwnerMapped = true;
}
return KernelResult.Success;
}
public KernelResult Unmap(ulong address, ulong size)
{
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
lock (_lock)
{
KProcess process = KernelStatic.GetCurrentProcess();
KernelResult result = process.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeWritable);
if (result != KernelResult.Success)
{
return result;
}
Debug.Assert(_isMapped);
_isMapped = false;
}
return KernelResult.Success;
}
public KernelResult UnmapFromOwner(ulong address, ulong size)
{
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
lock (_lock)
{
KernelResult result = Owner.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeReadOnly);
if (result != KernelResult.Success)
{
return result;
}
Debug.Assert(_isOwnerMapped);
_isOwnerMapped = false;
}
return KernelResult.Success;
}
protected override void Destroy()
{
if (!_isMapped && !_isOwnerMapped)
{
ulong size = _pageList.GetPagesCount() * KPageTableBase.PageSize;
if (Owner.MemoryManager.UnborrowCodeMemory(_address, size, _pageList) != KernelResult.Success)
{
throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
}
}
Owner.DecrementReferenceCount();
}
}
}

View File

@@ -1,10 +1,7 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@@ -12,17 +9,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
private readonly IVirtualMemoryManager _cpuMemory;
public override bool SupportsMemoryAliasing => true;
public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
{
_cpuMemory = cpuMemory;
}
/// <inheritdoc/>
protected override IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList)
{
return _cpuMemory.GetPhysicalRegions(va, size);
var ranges = _cpuMemory.GetPhysicalRegions(va, size);
foreach (var range in ranges)
{
pageList.AddRange(range.Address + DramMemoryMap.DramBase, range.Size / PageSize);
}
}
/// <inheritdoc/>
@@ -34,7 +33,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <inheritdoc/>
protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission)
{
var srcRanges = GetPhysicalRegions(src, pagesCount * PageSize);
KPageList pageList = new KPageList();
GetPhysicalRegions(src, pagesCount * PageSize, pageList);
KernelResult result = Reprotect(src, pagesCount, KMemoryPermission.None);
@@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return result;
}
result = MapPages(dst, srcRanges, newDstPermission);
result = MapPages(dst, pageList, newDstPermission, false, 0);
if (result != KernelResult.Success)
{
@@ -59,10 +59,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong size = pagesCount * PageSize;
var srcRanges = GetPhysicalRegions(src, size);
var dstRanges = GetPhysicalRegions(dst, size);
KPageList srcPageList = new KPageList();
KPageList dstPageList = new KPageList();
if (!dstRanges.SequenceEqual(srcRanges))
GetPhysicalRegions(src, size, srcPageList);
GetPhysicalRegions(dst, size, dstPageList);
if (!dstPageList.IsEqual(srcPageList))
{
return KernelResult.InvalidMemRange;
}
@@ -78,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (result != KernelResult.Success)
{
KernelResult mapResult = MapPages(dst, dstRanges, oldDstPermission);
KernelResult mapResult = MapPages(dst, dstPageList, oldDstPermission, false, 0);
Debug.Assert(mapResult == KernelResult.Success);
}
@@ -92,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
Context.Memory.Commit(srcPa - DramMemoryMap.DramBase, size);
_cpuMemory.Map(dstVa, Context.Memory.GetPointer(srcPa - DramMemoryMap.DramBase, size), size);
_cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size);
if (DramMemoryMap.IsHeapPhysicalAddress(srcPa))
{
@@ -121,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
Context.Memory.Commit(addr, size);
_cpuMemory.Map(currentVa, Context.Memory.GetPointer(addr, size), size);
_cpuMemory.Map(currentVa, addr, size);
if (shouldFillPages)
{
@@ -136,33 +139,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission)
{
ulong currentVa = address;
foreach (var range in ranges)
{
ulong size = range.Size;
ulong pa = GetDramAddressFromHostAddress(range.Address);
if (pa != ulong.MaxValue)
{
pa += DramMemoryMap.DramBase;
if (DramMemoryMap.IsHeapPhysicalAddress(pa))
{
Context.MemoryManager.IncrementPagesReferenceCount(pa, size / PageSize);
}
}
_cpuMemory.Map(currentVa, range.Address, size);
currentVa += size;
}
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult Unmap(ulong address, ulong pagesCount)
{
@@ -172,13 +148,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
foreach (var region in regions)
{
ulong pa = GetDramAddressFromHostAddress(region.Address);
if (pa == ulong.MaxValue)
{
continue;
}
pa += DramMemoryMap.DramBase;
ulong pa = region.Address + DramMemoryMap.DramBase;
if (DramMemoryMap.IsHeapPhysicalAddress(pa))
{
pagesToClose.AddRange(pa, region.Size / PageSize);
@@ -217,15 +187,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
_cpuMemory.Write(va, data);
}
private ulong GetDramAddressFromHostAddress(nuint hostAddress)
{
if (hostAddress < (nuint)(ulong)Context.Memory.Pointer || hostAddress >= (nuint)((ulong)Context.Memory.Pointer + Context.Memory.Size))
{
return ulong.MaxValue;
}
return hostAddress - (ulong)Context.Memory.Pointer;
}
}
}

View File

@@ -1,11 +1,9 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@@ -73,8 +71,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
private MersenneTwister _randomNumberGenerator;
public abstract bool SupportsMemoryAliasing { get; }
private MemoryFillValue _heapFillValue;
private MemoryFillValue _ipcFillValue;
@@ -380,8 +376,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
}
public KernelResult UnmapPages(ulong address, ulong pagesCount, IEnumerable<HostMemoryRange> ranges, MemoryState stateExpected)
public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected)
{
ulong pagesCount = pageList.GetPagesCount();
ulong size = pagesCount * PageSize;
ulong endAddr = address + size;
@@ -405,9 +402,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
lock (_blockManager)
{
var currentRanges = GetPhysicalRegions(address, size);
KPageList currentPageList = new KPageList();
if (!currentRanges.SequenceEqual(ranges))
GetPhysicalRegions(address, size, currentPageList);
if (!currentPageList.IsEqual(pageList))
{
return KernelResult.InvalidMemRange;
}
@@ -1096,6 +1095,77 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
}
public KernelResult UnmapProcessMemory(ulong dst, ulong size, KPageTableBase srcPageTable, ulong src)
{
lock (_blockManager)
{
lock (srcPageTable._blockManager)
{
bool success = CheckRange(
dst,
size,
MemoryState.Mask,
MemoryState.ProcessMemory,
KMemoryPermission.ReadAndWrite,
KMemoryPermission.ReadAndWrite,
MemoryAttribute.Mask,
MemoryAttribute.None,
MemoryAttribute.IpcAndDeviceMapped,
out _,
out _,
out _);
success &= srcPageTable.CheckRange(
src,
size,
MemoryState.MapProcessAllowed,
MemoryState.MapProcessAllowed,
KMemoryPermission.None,
KMemoryPermission.None,
MemoryAttribute.Mask,
MemoryAttribute.None,
MemoryAttribute.IpcAndDeviceMapped,
out _,
out _,
out _);
if (!success)
{
return KernelResult.InvalidMemState;
}
KPageList srcPageList = new KPageList();
KPageList dstPageList = new KPageList();
srcPageTable.GetPhysicalRegions(src, size, srcPageList);
GetPhysicalRegions(dst, size, dstPageList);
if (!dstPageList.IsEqual(srcPageList))
{
return KernelResult.InvalidMemRange;
}
}
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong pagesCount = size / PageSize;
KernelResult result = Unmap(dst, pagesCount);
if (result != KernelResult.Success)
{
return result;
}
_blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped);
return KernelResult.Success;
}
}
public KernelResult SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
{
lock (_blockManager)
@@ -1690,11 +1760,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
bool send,
out ulong dst)
{
if (!SupportsMemoryAliasing)
{
throw new NotSupportedException("Memory aliasing not supported, can't map IPC buffers.");
}
dst = 0;
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
@@ -1828,7 +1893,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong alignedSize = endAddrTruncated - addressRounded;
KernelResult result = MapPages(currentVa, srcPageTable.GetPhysicalRegions(addressRounded, alignedSize), permission);
KPageList pageList = new KPageList();
srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
KernelResult result = MapPages(currentVa, pageList, permission);
if (result != KernelResult.Success)
{
@@ -2026,6 +2094,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
block.RestoreIpcMappingPermission();
}
public KernelResult GetPagesIfStateEquals(
ulong address,
ulong size,
MemoryState stateMask,
MemoryState stateExpected,
KMemoryPermission permissionMask,
KMemoryPermission permissionExpected,
MemoryAttribute attributeMask,
MemoryAttribute attributeExpected,
KPageList pageList)
{
if (!InsideAddrSpace(address, size))
{
return KernelResult.InvalidMemState;
}
lock (_blockManager)
{
if (CheckRange(
address,
size,
stateMask | MemoryState.IsPoolAllocated,
stateExpected | MemoryState.IsPoolAllocated,
permissionMask,
permissionExpected,
attributeMask,
attributeExpected,
MemoryAttribute.IpcAndDeviceMapped,
out _,
out _,
out _))
{
GetPhysicalRegions(address, size, pageList);
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
}
public KernelResult BorrowIpcBuffer(ulong address, ulong size)
{
return SetAttributesAndChangePermission(
@@ -2041,7 +2152,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute.Borrowed);
}
public KernelResult BorrowTransferMemory(List<HostMemoryRange> ranges, ulong address, ulong size, KMemoryPermission permission)
public KernelResult BorrowTransferMemory(KPageList pageList, ulong address, ulong size, KMemoryPermission permission)
{
return SetAttributesAndChangePermission(
address,
@@ -2054,7 +2165,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute.None,
permission,
MemoryAttribute.Borrowed,
ranges);
pageList);
}
public KernelResult BorrowCodeMemory(KPageList pageList, ulong address, ulong size)
{
return SetAttributesAndChangePermission(
address,
size,
MemoryState.CodeMemoryAllowed,
MemoryState.CodeMemoryAllowed,
KMemoryPermission.Mask,
KMemoryPermission.ReadAndWrite,
MemoryAttribute.Mask,
MemoryAttribute.None,
KMemoryPermission.None,
MemoryAttribute.Borrowed,
pageList);
}
private KernelResult SetAttributesAndChangePermission(
@@ -2068,7 +2195,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute attributeExpected,
KMemoryPermission newPermission,
MemoryAttribute attributeSetMask,
List<HostMemoryRange> ranges = null)
KPageList pageList = null)
{
if (address + size <= address || !InsideAddrSpace(address, size))
{
@@ -2093,7 +2220,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong pagesCount = size / PageSize;
ranges?.AddRange(GetPhysicalRegions(address, size));
if (pageList != null)
{
GetPhysicalRegions(address, pagesCount * PageSize, pageList);
}
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
{
@@ -2143,7 +2273,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute.Borrowed);
}
public KernelResult UnborrowTransferMemory(ulong address, ulong size, List<HostMemoryRange> ranges)
public KernelResult UnborrowTransferMemory(ulong address, ulong size, KPageList pageList)
{
return ClearAttributesAndChangePermission(
address,
@@ -2156,7 +2286,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute.Borrowed,
KMemoryPermission.ReadAndWrite,
MemoryAttribute.Borrowed,
ranges);
pageList);
}
public KernelResult UnborrowCodeMemory(ulong address, ulong size, KPageList pageList)
{
return ClearAttributesAndChangePermission(
address,
size,
MemoryState.CodeMemoryAllowed,
MemoryState.CodeMemoryAllowed,
KMemoryPermission.None,
KMemoryPermission.None,
MemoryAttribute.Mask,
MemoryAttribute.Borrowed,
KMemoryPermission.ReadAndWrite,
MemoryAttribute.Borrowed,
pageList);
}
private KernelResult ClearAttributesAndChangePermission(
@@ -2170,7 +2316,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryAttribute attributeExpected,
KMemoryPermission newPermission,
MemoryAttribute attributeClearMask,
List<HostMemoryRange> ranges = null)
KPageList pageList = null)
{
if (address + size <= address || !InsideAddrSpace(address, size))
{
@@ -2195,11 +2341,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong pagesCount = size / PageSize;
if (ranges != null)
if (pageList != null)
{
var currentRanges = GetPhysicalRegions(address, size);
KPageList currentPageList = new KPageList();
if (!currentRanges.SequenceEqual(ranges))
GetPhysicalRegions(address, pagesCount * PageSize, currentPageList);
if (!currentPageList.IsEqual(pageList))
{
return KernelResult.InvalidMemRange;
}
@@ -2741,8 +2889,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>Array of physical regions</returns>
protected abstract IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size);
/// <param name="pageList">Page list where the ranges will be added</param>
protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList);
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
@@ -2803,16 +2951,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <returns>Result of the mapping operation</returns>
protected abstract KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages = false, byte fillValue = 0);
/// <summary>
/// Maps a region of memory into the specified host memory ranges.
/// </summary>
/// <param name="address">Destination virtual address that should be mapped</param>
/// <param name="ranges">Ranges of host memory that should be mapped</param>
/// <param name="permission">Permission of the region to be mapped</param>
/// <returns>Result of the mapping operation</returns>
/// <exception cref="NotSupportedException">The implementation does not support memory aliasing</exception>
protected abstract KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission);
/// <summary>
/// Unmaps a region of memory that was previously mapped with one of the page mapping methods.
/// </summary>

View File

@@ -1,139 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KPageTableHostMapped : KPageTableBase
{
private const int CopyChunckSize = 0x100000;
private readonly IVirtualMemoryManager _cpuMemory;
public override bool SupportsMemoryAliasing => false;
public KPageTableHostMapped(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
{
_cpuMemory = cpuMemory;
}
/// <inheritdoc/>
protected override IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
return _cpuMemory.GetPhysicalRegions(va, size);
}
/// <inheritdoc/>
protected override ReadOnlySpan<byte> GetSpan(ulong va, int size)
{
return _cpuMemory.GetSpan(va, size);
}
/// <inheritdoc/>
protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission)
{
ulong size = pagesCount * PageSize;
_cpuMemory.Map(dst, 0, size);
ulong currentSize = size;
while (currentSize > 0)
{
ulong copySize = Math.Min(currentSize, CopyChunckSize);
_cpuMemory.Write(dst, _cpuMemory.GetSpan(src, (int)copySize));
currentSize -= copySize;
}
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission)
{
ulong size = pagesCount * PageSize;
// TODO: Validation.
ulong currentSize = size;
while (currentSize > 0)
{
ulong copySize = Math.Min(currentSize, CopyChunckSize);
_cpuMemory.Write(src, _cpuMemory.GetSpan(dst, (int)copySize));
currentSize -= copySize;
}
_cpuMemory.Unmap(dst, size);
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult MapPages(ulong dstVa, ulong pagesCount, ulong srcPa, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
{
_cpuMemory.Map(dstVa, 0, pagesCount * PageSize);
if (shouldFillPages)
{
_cpuMemory.Fill(dstVa, pagesCount * PageSize, fillValue);
}
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
{
ulong pagesCount = pageList.GetPagesCount();
_cpuMemory.Map(address, 0, pagesCount * PageSize);
if (shouldFillPages)
{
_cpuMemory.Fill(address, pagesCount * PageSize, fillValue);
}
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
protected override KernelResult Unmap(ulong address, ulong pagesCount)
{
_cpuMemory.Unmap(address, pagesCount * PageSize);
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
{
// TODO.
return KernelResult.Success;
}
/// <inheritdoc/>
protected override KernelResult ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
{
// TODO.
return KernelResult.Success;
}
/// <inheritdoc/>
protected override void SignalMemoryTracking(ulong va, ulong size, bool write)
{
_cpuMemory.SignalMemoryTracking(va, size, write);
}
/// <inheritdoc/>
protected override void Write(ulong va, ReadOnlySpan<byte> data)
{
_cpuMemory.Write(va, data);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KSharedMemory : KAutoObject
{
private readonly SharedMemoryStorage _storage;
private readonly KPageList _pageList;
private readonly ulong _ownerPid;
@@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryPermission ownerPermission,
KMemoryPermission userPermission) : base(context)
{
_storage = storage;
_pageList = storage.GetPageList();
_ownerPid = ownerPid;
_ownerPermission = ownerPermission;
_userPermission = userPermission;
@@ -33,10 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KProcess process,
KMemoryPermission permission)
{
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
var pageList = _storage.GetPageList();
if (pageList.GetPagesCount() != pagesCountRounded)
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
@@ -50,35 +47,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return KernelResult.InvalidPermission;
}
KernelResult result = memoryManager.MapPages(address, pageList, MemoryState.SharedMemory, permission);
if (result == KernelResult.Success && !memoryManager.SupportsMemoryAliasing)
{
_storage.Borrow(process, address);
}
return result;
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
}
public KernelResult UnmapFromProcess(
KPageTableBase memoryManager,
ulong address,
ulong size,
KProcess process)
public KernelResult UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)
{
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
var pageList = _storage.GetPageList();
ulong pagesCount = pageList.GetPagesCount();
if (pagesCount != pagesCountRounded)
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
var ranges = _storage.GetRanges();
return memoryManager.UnmapPages(address, pagesCount, ranges, MemoryState.SharedMemory);
return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory);
}
}
}

View File

@@ -1,9 +1,7 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@@ -14,9 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
// TODO: Remove when we no longer need to read it from the owner directly.
public KProcess Creator => _creator;
private readonly List<HostMemoryRange> _ranges;
private readonly SharedMemoryStorage _storage;
private readonly KPageList _pageList;
public ulong Address { get; private set; }
public ulong Size { get; private set; }
@@ -28,12 +24,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
public KTransferMemory(KernelContext context) : base(context)
{
_ranges = new List<HostMemoryRange>();
_pageList = new KPageList();
}
public KTransferMemory(KernelContext context, SharedMemoryStorage storage) : base(context)
{
_storage = storage;
_pageList = storage.GetPageList();
Permission = KMemoryPermission.ReadAndWrite;
_hasBeenInitialized = true;
@@ -46,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
_creator = creator;
KernelResult result = creator.MemoryManager.BorrowTransferMemory(_ranges, address, size, permission);
KernelResult result = creator.MemoryManager.BorrowTransferMemory(_pageList, address, size, permission);
if (result != KernelResult.Success)
{
@@ -71,15 +67,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KProcess process,
KMemoryPermission permission)
{
if (_storage == null)
{
throw new NotImplementedException();
}
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
var pageList = _storage.GetPageList();
if (pageList.GetPagesCount() != pagesCountRounded)
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
@@ -91,16 +79,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
KernelResult result = memoryManager.MapPages(address, pageList, state, KMemoryPermission.ReadAndWrite);
KernelResult result = memoryManager.MapPages(address, _pageList, state, KMemoryPermission.ReadAndWrite);
if (result == KernelResult.Success)
{
_isMapped = true;
if (!memoryManager.SupportsMemoryAliasing)
{
_storage.Borrow(process, address);
}
}
return result;
@@ -112,26 +95,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
ulong size,
KProcess process)
{
if (_storage == null)
{
throw new NotImplementedException();
}
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
var pageList = _storage.GetPageList();
ulong pagesCount = pageList.GetPagesCount();
if (pagesCount != pagesCountRounded)
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
{
return KernelResult.InvalidSize;
}
var ranges = _storage.GetRanges();
MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
KernelResult result = memoryManager.UnmapPages(address, pagesCount, ranges, state);
KernelResult result = memoryManager.UnmapPages(address, _pageList, state);
if (result == KernelResult.Success)
{
@@ -145,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
if (_hasBeenInitialized)
{
if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _ranges) != KernelResult.Success)
if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _pageList) != KernelResult.Success)
{
throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
}

View File

@@ -1,8 +1,4 @@
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@@ -12,9 +8,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
private readonly KPageList _pageList;
private readonly ulong _size;
private IVirtualMemoryManager _borrowerMemory;
private ulong _borrowerVa;
public SharedMemoryStorage(KernelContext context, KPageList pageList)
{
_context = context;
@@ -29,24 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
}
public void Borrow(KProcess dstProcess, ulong va)
{
ulong currentOffset = 0;
foreach (KPageNode pageNode in _pageList)
{
ulong address = pageNode.Address - DramMemoryMap.DramBase;
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
dstProcess.CpuMemory.Write(va + currentOffset, _context.Memory.GetSpan(address + currentOffset, (int)size));
currentOffset += size;
}
_borrowerMemory = dstProcess.CpuMemory;
_borrowerVa = va;
}
public void ZeroFill()
{
for (ulong offset = 0; offset < _size; offset += sizeof(ulong))
@@ -57,42 +32,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
public ref T GetRef<T>(ulong offset) where T : unmanaged
{
if (_borrowerMemory == null)
if (_pageList.Nodes.Count == 1)
{
if (_pageList.Nodes.Count == 1)
{
ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase;
return ref _context.Memory.GetRef<T>(address + offset);
}
throw new NotImplementedException("Non-contiguous shared memory is not yet supported.");
ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase;
return ref _context.Memory.GetRef<T>(address + offset);
}
else
{
return ref _borrowerMemory.GetRef<T>(_borrowerVa + offset);
}
}
public IEnumerable<HostMemoryRange> GetRanges()
{
if (_borrowerMemory == null)
{
var ranges = new List<HostMemoryRange>();
foreach (KPageNode pageNode in _pageList)
{
ulong address = pageNode.Address - DramMemoryMap.DramBase;
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
ranges.Add(new HostMemoryRange(_context.Memory.GetPointer(address, size), size));
}
return ranges;
}
else
{
return _borrowerMemory.GetPhysicalRegions(_borrowerVa, _size);
}
throw new NotImplementedException("Non-contiguous shared memory is not yet supported.");
}
public KPageList GetPageList()

View File

@@ -60,6 +60,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public KProcessCapabilities Capabilities { get; private set; }
public bool AllowCodeMemoryForJit { get; private set; }
public ulong TitleId { get; private set; }
public bool IsApplication { get; private set; }
public ulong Pid { get; private set; }
@@ -90,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public HleProcessDebugger Debugger { get; private set; }
public KProcess(KernelContext context) : base(context)
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{
_processLock = new object();
_threadingLock = new object();
@@ -102,6 +104,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Capabilities = new KProcessCapabilities();
AllowCodeMemoryForJit = allowCodeMemoryForJit;
RandomEntropy = new ulong[KScheduler.CpuCoresCount];
PinnedThreads = new KThread[KScheduler.CpuCoresCount];
@@ -1076,14 +1080,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit);
if (Context.AddressSpace is MemoryManagerHostMapped)
{
MemoryManager = new KPageTableHostMapped(KernelContext, CpuMemory);
}
else
{
MemoryManager = new KPageTable(KernelContext, CpuMemory);
}
MemoryManager = new KPageTable(KernelContext, CpuMemory);
}
private bool InvalidAccessHandler(ulong va)

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
{
return new ProcessContext(new AddressSpaceManager(addressSpaceSize));
return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize));
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
enum CodeMemoryOperation : uint
{
Map,
MapToOwner,
Unmap,
UnmapFromOwner
};
}

View File

@@ -7,7 +7,6 @@ using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
using System.Threading;
@@ -1317,6 +1316,248 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.UnmapPhysicalMemory(address, size);
}
public KernelResult CreateCodeMemory(ulong address, ulong size, out int handle)
{
handle = 0;
if (!PageAligned(address))
{
return KernelResult.InvalidAddress;
}
if (!PageAligned(size) || size == 0)
{
return KernelResult.InvalidSize;
}
if (size + address <= address)
{
return KernelResult.InvalidMemState;
}
KCodeMemory codeMemory = new KCodeMemory(_context);
using var _ = new OnScopeExit(codeMemory.DecrementReferenceCount);
KProcess currentProcess = KernelStatic.GetCurrentProcess();
if (!currentProcess.MemoryManager.InsideAddrSpace(address, size))
{
return KernelResult.InvalidMemState;
}
KernelResult result = codeMemory.Initialize(address, size);
if (result != KernelResult.Success)
{
return result;
}
return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle);
}
public KernelResult ControlCodeMemory(int handle, CodeMemoryOperation op, ulong address, ulong size, KMemoryPermission permission)
{
KProcess currentProcess = KernelStatic.GetCurrentProcess();
KCodeMemory codeMemory = currentProcess.HandleTable.GetObject<KCodeMemory>(handle);
// Newer versions of the kernel also returns an error here if the owner and process
// where the operation will happen are the same. We do not return an error here
// for homebrew because some of them requires this to be patched out to work (for JIT).
if (codeMemory == null || (!currentProcess.AllowCodeMemoryForJit && codeMemory.Owner == currentProcess))
{
return KernelResult.InvalidHandle;
}
switch (op)
{
case CodeMemoryOperation.Map:
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
{
return KernelResult.InvalidMemRange;
}
if (permission != KMemoryPermission.ReadAndWrite)
{
return KernelResult.InvalidPermission;
}
return codeMemory.Map(address, size, permission);
case CodeMemoryOperation.MapToOwner:
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
{
return KernelResult.InvalidMemRange;
}
if (permission != KMemoryPermission.Read && permission != KMemoryPermission.ReadAndExecute)
{
return KernelResult.InvalidPermission;
}
return codeMemory.MapToOwner(address, size, permission);
case CodeMemoryOperation.Unmap:
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
{
return KernelResult.InvalidMemRange;
}
if (permission != KMemoryPermission.None)
{
return KernelResult.InvalidPermission;
}
return codeMemory.Unmap(address, size);
case CodeMemoryOperation.UnmapFromOwner:
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
{
return KernelResult.InvalidMemRange;
}
if (permission != KMemoryPermission.None)
{
return KernelResult.InvalidPermission;
}
return codeMemory.UnmapFromOwner(address, size);
default: return KernelResult.InvalidEnumValue;
}
}
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission)
{
if (!PageAligned(src))
{
return KernelResult.InvalidAddress;
}
if (!PageAligned(size) || size == 0)
{
return KernelResult.InvalidSize;
}
if (permission != KMemoryPermission.None &&
permission != KMemoryPermission.Read &&
permission != KMemoryPermission.ReadAndWrite &&
permission != KMemoryPermission.ReadAndExecute)
{
return KernelResult.InvalidPermission;
}
KProcess currentProcess = KernelStatic.GetCurrentProcess();
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
if (targetProcess == null)
{
return KernelResult.InvalidHandle;
}
if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
{
return KernelResult.InvalidMemState;
}
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
}
public KernelResult MapProcessMemory(ulong dst, int handle, ulong src, ulong size)
{
if (!PageAligned(src) || !PageAligned(dst))
{
return KernelResult.InvalidAddress;
}
if (!PageAligned(size) || size == 0)
{
return KernelResult.InvalidSize;
}
if (dst + size <= dst || src + size <= src)
{
return KernelResult.InvalidMemRange;
}
KProcess dstProcess = KernelStatic.GetCurrentProcess();
KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
if (srcProcess == null)
{
return KernelResult.InvalidHandle;
}
if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
!dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
{
return KernelResult.InvalidMemRange;
}
KPageList pageList = new KPageList();
KernelResult result = srcProcess.MemoryManager.GetPagesIfStateEquals(
src,
size,
MemoryState.MapProcessAllowed,
MemoryState.MapProcessAllowed,
KMemoryPermission.None,
KMemoryPermission.None,
MemoryAttribute.Mask,
MemoryAttribute.None,
pageList);
if (result != KernelResult.Success)
{
return result;
}
return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite);
}
public KernelResult UnmapProcessMemory(ulong dst, int handle, ulong src, ulong size)
{
if (!PageAligned(src) || !PageAligned(dst))
{
return KernelResult.InvalidAddress;
}
if (!PageAligned(size) || size == 0)
{
return KernelResult.InvalidSize;
}
if (dst + size <= dst || src + size <= src)
{
return KernelResult.InvalidMemRange;
}
KProcess dstProcess = KernelStatic.GetCurrentProcess();
KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
if (srcProcess == null)
{
return KernelResult.InvalidHandle;
}
if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
!dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
{
return KernelResult.InvalidMemRange;
}
KernelResult result = dstProcess.MemoryManager.UnmapProcessMemory(dst, size, srcProcess.MemoryManager, src);
if (result != KernelResult.Success)
{
return result;
}
return KernelResult.Success;
}
public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
{
if (!PageAligned(dst) || !PageAligned(src))
@@ -1391,43 +1632,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
}
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission)
{
if (!PageAligned(src))
{
return KernelResult.InvalidAddress;
}
if (!PageAligned(size) || size == 0)
{
return KernelResult.InvalidSize;
}
if (permission != KMemoryPermission.None &&
permission != KMemoryPermission.Read &&
permission != KMemoryPermission.ReadAndWrite &&
permission != KMemoryPermission.ReadAndExecute)
{
return KernelResult.InvalidPermission;
}
KProcess currentProcess = KernelStatic.GetCurrentProcess();
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
if (targetProcess == null)
{
return KernelResult.InvalidHandle;
}
if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
{
return KernelResult.InvalidMemState;
}
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
}
private static bool PageAligned(ulong address)
{
return (address & (KPageTableBase.PageSize - 1)) == 0;

View File

@@ -143,6 +143,26 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.CreateTransferMemory(out handle, address, size, permission);
}
public KernelResult CreateCodeMemory32([R(1)] uint address, [R(2)] uint size, [R(1)] out int handle)
{
return _syscall.CreateCodeMemory(address, size, out handle);
}
public KernelResult ControlCodeMemory32(
[R(0)] int handle,
[R(1)] CodeMemoryOperation op,
[R(2)] uint addressLow,
[R(3)] uint addressHigh,
[R(4)] uint sizeLow,
[R(5)] uint sizeHigh,
[R(6)] KMemoryPermission permission)
{
ulong address = addressLow | ((ulong)addressHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
}
public KernelResult MapTransferMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapTransferMemory(handle, address, size, permission);
@@ -163,6 +183,34 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.UnmapPhysicalMemory(address, size);
}
public KernelResult SetProcessMemoryPermission32(
[R(0)] int handle,
[R(1)] uint sizeLow,
[R(2)] uint srcLow,
[R(3)] uint srcHigh,
[R(4)] uint sizeHigh,
[R(5)] KMemoryPermission permission)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
public KernelResult MapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
return _syscall.MapProcessMemory(dst, handle, src, size);
}
public KernelResult UnmapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
return _syscall.UnmapProcessMemory(dst, handle, src, size);
}
public KernelResult MapProcessCodeMemory32([R(0)] int handle, [R(1)] uint srcLow, [R(2)] uint dstLow, [R(3)] uint dstHigh, [R(4)] uint srcHigh, [R(5)] uint sizeLow, [R(6)] uint sizeHigh)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
@@ -181,20 +229,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
}
public KernelResult SetProcessMemoryPermission32(
[R(0)] int handle,
[R(1)] uint sizeLow,
[R(2)] uint srcLow,
[R(3)] uint srcHigh,
[R(4)] uint sizeHigh,
[R(5)] KMemoryPermission permission)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
// System
public void ExitProcess32()

View File

@@ -160,6 +160,16 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.CreateTransferMemory(out handle, address, size, permission);
}
public KernelResult CreateCodeMemory64([R(1)] ulong address, [R(2)] ulong size, [R(1)] out int handle)
{
return _syscall.CreateCodeMemory(address, size, out handle);
}
public KernelResult ControlCodeMemory64([R(0)] int handle, [R(1)] CodeMemoryOperation op, [R(2)] ulong address, [R(3)] ulong size, [R(4)] KMemoryPermission permission)
{
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
}
public KernelResult MapTransferMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapTransferMemory(handle, address, size, permission);
@@ -180,6 +190,21 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.UnmapPhysicalMemory(address, size);
}
public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
public KernelResult MapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.MapProcessMemory(dst, handle, src, size);
}
public KernelResult UnmapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.UnmapProcessMemory(dst, handle, src, size);
}
public KernelResult MapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.MapProcessCodeMemory(handle, dst, src, size);
@@ -190,11 +215,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
}
public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
// System
public void ExitProcess64()

View File

@@ -78,6 +78,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x43, nameof(Syscall64.ReplyAndReceive64) },
{ 0x44, nameof(Syscall64.ReplyAndReceiveWithUserBuffer64) },
{ 0x45, nameof(Syscall64.CreateEvent64) },
{ 0x4b, nameof(Syscall64.CreateCodeMemory64) },
{ 0x4c, nameof(Syscall64.ControlCodeMemory64) },
{ 0x51, nameof(Syscall64.MapTransferMemory64) },
{ 0x52, nameof(Syscall64.UnmapTransferMemory64) },
{ 0x65, nameof(Syscall64.GetProcessList64) },
@@ -86,6 +88,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x71, nameof(Syscall64.ManageNamedPort64) },
{ 0x72, nameof(Syscall64.ConnectToPort64) },
{ 0x73, nameof(Syscall64.SetProcessMemoryPermission64) },
{ 0x74, nameof(Syscall64.MapProcessMemory64) },
{ 0x75, nameof(Syscall64.UnmapProcessMemory64) },
{ 0x77, nameof(Syscall64.MapProcessCodeMemory64) },
{ 0x78, nameof(Syscall64.UnmapProcessCodeMemory64) },
{ 0x7B, nameof(Syscall64.TerminateProcess64) },
@@ -152,6 +156,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x41, nameof(Syscall32.AcceptSession32) },
{ 0x43, nameof(Syscall32.ReplyAndReceive32) },
{ 0x45, nameof(Syscall32.CreateEvent32) },
{ 0x4b, nameof(Syscall32.CreateCodeMemory32) },
{ 0x4c, nameof(Syscall32.ControlCodeMemory32) },
{ 0x51, nameof(Syscall32.MapTransferMemory32) },
{ 0x52, nameof(Syscall32.UnmapTransferMemory32) },
{ 0x5F, nameof(Syscall32.FlushProcessDataCache32) },
@@ -161,6 +167,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x71, nameof(Syscall32.ManageNamedPort32) },
{ 0x72, nameof(Syscall32.ConnectToPort32) },
{ 0x73, nameof(Syscall32.SetProcessMemoryPermission32) },
{ 0x74, nameof(Syscall32.MapProcessMemory32) },
{ 0x75, nameof(Syscall32.UnmapProcessMemory32) },
{ 0x77, nameof(Syscall32.MapProcessCodeMemory32) },
{ 0x78, nameof(Syscall32.UnmapProcessCodeMemory32) },
{ 0x7B, nameof(Syscall32.TerminateProcess32) },

View File

@@ -20,11 +20,13 @@ namespace Ryujinx.HLE.HOS
{
public string Name;
public ulong ProgramId;
public bool AllowCodeMemoryForJit;
public ProgramInfo(in Npdm npdm)
public ProgramInfo(in Npdm npdm, bool allowCodeMemoryForJit)
{
Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName);
ProgramId = npdm.Aci.Value.ProgramId.Value;
AllowCodeMemoryForJit = allowCodeMemoryForJit;
}
}
@@ -141,7 +143,13 @@ namespace Ryujinx.HLE.HOS
return true;
}
public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, MetaLoader metaData, ProgramInfo programInfo, byte[] arguments = null, params IExecutable[] executables)
public static bool LoadNsos(
KernelContext context,
out ProcessTamperInfo tamperInfo,
MetaLoader metaData,
ProgramInfo programInfo,
byte[] arguments = null,
params IExecutable[] executables)
{
LibHac.Result rc = metaData.GetNpdm(out var npdm);
@@ -243,7 +251,7 @@ namespace Ryujinx.HLE.HOS
return false;
}
KProcess process = new KProcess(context);
KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit);
MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Value.Flags >> 2) & 0xf);

View File

@@ -2,9 +2,12 @@ using LibHac;
using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -15,6 +18,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
using Ryujinx.HLE.HOS.SystemState;
using System;
using System.Numerics;
using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement;
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
@@ -37,6 +41,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle;
private bool _gamePlayRecordingState;
private int _jitLoaded;
private HorizonClient _horizon;
public IApplicationFunctions(Horizon system)
@@ -342,6 +350,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandHipc(65)] // 3.0.0+
// IsGamePlayRecordingSupported() -> u8
public ResultCode IsGamePlayRecordingSupported(ServiceCtx context)
{
context.ResponseData.Write(_gamePlayRecordingState);
return ResultCode.Success;
}
[CommandHipc(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context)
@@ -355,9 +372,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// SetGamePlayRecordingState(u32)
public ResultCode SetGamePlayRecordingState(ServiceCtx context)
{
int state = context.RequestData.ReadInt32();
_gamePlayRecordingState = context.RequestData.ReadInt32() != 0;
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { state });
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState });
return ResultCode.Success;
}
@@ -631,5 +648,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandHipc(1001)] // 10.0.0+
// PrepareForJit()
public ResultCode PrepareForJit(ServiceCtx context)
{
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
{
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath);
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
}
context.Device.Application.LoadServiceNca(filePath);
// FIXME: Most likely not how this should be done?
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
{
context.Device.System.SmRegistry.WaitForServiceRegistration();
}
}
return ResultCode.Success;
}
}
}

View File

@@ -0,0 +1,27 @@
using Concentus.Structs;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class Decoder : IDecoder
{
private readonly OpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount)
{
_decoder = new OpusDecoder(sampleRate, channelsCount);
}
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
}
public void ResetState()
{
_decoder.ResetState();
}
}
}

View File

@@ -0,0 +1,92 @@
using Concentus;
using Concentus.Enums;
using Concentus.Structs;
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
static class DecoderCommon
{
private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
{
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
numSamples = result;
if (result == OpusError.OPUS_INVALID_PACKET)
{
return ResultCode.OpusInvalidInput;
}
else if (result == OpusError.OPUS_BAD_ARG)
{
return ResultCode.OpusInvalidInput;
}
return ResultCode.Success;
}
public static ResultCode DecodeInterleaved(
this IDecoder decoder,
bool reset,
ReadOnlySpan<byte> input,
out short[] outPcmData,
ulong outputSize,
out uint outConsumed,
out int outSamples)
{
outPcmData = null;
outConsumed = 0;
outSamples = 0;
int streamSize = input.Length;
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
{
return ResultCode.OpusInvalidInput;
}
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
uint totalSize = header.length + (uint)headerSize;
if (totalSize > streamSize)
{
return ResultCode.OpusInvalidInput;
}
byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
if (result == ResultCode.Success)
{
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
{
return ResultCode.OpusInvalidInput;
}
outPcmData = new short[numSamples * decoder.ChannelsCount];
if (reset)
{
decoder.ResetState();
}
try
{
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
outConsumed = totalSize;
}
catch (OpusException)
{
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
return ResultCode.OpusInvalidInput;
}
}
return ResultCode.Success;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
interface IDecoder
{
int SampleRate { get; }
int ChannelsCount { get; }
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
void ResetState();
}
}

View File

@@ -1,244 +1,112 @@
using Concentus;
using Concentus.Enums;
using Concentus.Structs;
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class IHardwareOpusDecoder : IpcService
{
private int _sampleRate;
private int _channelsCount;
private bool _reset;
private readonly IDecoder _decoder;
private readonly OpusDecoderFlags _flags;
private OpusDecoder _decoder;
public IHardwareOpusDecoder(int sampleRate, int channelsCount)
public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
{
_sampleRate = sampleRate;
_channelsCount = channelsCount;
_reset = false;
_decoder = new OpusDecoder(sampleRate, channelsCount);
_decoder = new Decoder(sampleRate, channelsCount);
_flags = flags;
}
private ResultCode GetPacketNumSamples(out int numSamples, byte[] packet)
public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
{
int result = OpusPacketInfo.GetNumSamples(_decoder, packet, 0, packet.Length);
numSamples = result;
if (result == OpusError.OPUS_INVALID_PACKET)
{
return ResultCode.OpusInvalidInput;
}
else if (result == OpusError.OPUS_BAD_ARG)
{
return ResultCode.OpusInvalidInput;
}
return ResultCode.Success;
}
private ResultCode DecodeInterleavedInternal(BinaryReader input, out short[] outPcmData, long outputSize, out uint outConsumed, out int outSamples)
{
outPcmData = null;
outConsumed = 0;
outSamples = 0;
long streamSize = input.BaseStream.Length;
if (streamSize < Marshal.SizeOf<OpusPacketHeader>())
{
return ResultCode.OpusInvalidInput;
}
OpusPacketHeader header = OpusPacketHeader.FromStream(input);
uint totalSize = header.length + (uint)Marshal.SizeOf<OpusPacketHeader>();
if (totalSize > streamSize)
{
return ResultCode.OpusInvalidInput;
}
byte[] opusData = input.ReadBytes((int)header.length);
ResultCode result = GetPacketNumSamples(out int numSamples, opusData);
if (result == ResultCode.Success)
{
if ((uint)numSamples * (uint)_channelsCount * sizeof(short) > outputSize)
{
return ResultCode.OpusInvalidInput;
}
outPcmData = new short[numSamples * _channelsCount];
if (_reset)
{
_reset = false;
_decoder.ResetState();
}
try
{
outSamples = _decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / _channelsCount);
outConsumed = totalSize;
}
catch (OpusException)
{
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
return ResultCode.OpusInvalidInput;
}
}
return ResultCode.Success;
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
_flags = flags;
}
[CommandHipc(0)]
// DecodeInterleaved(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
public ResultCode DecodeInterleavedOriginal(ServiceCtx context)
// DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
public ResultCode DecodeInterleavedOld(ServiceCtx context)
{
ResultCode result;
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
}
ulong inPosition = context.Request.SendBuff[0].Position;
ulong inSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
byte[] buffer = new byte[inSize];
context.Memory.Read(inPosition, buffer);
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
{
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
if (result == ResultCode.Success)
{
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
context.Memory.Write(outputPosition, pcmDataBytes);
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
}
}
return result;
[CommandHipc(2)]
// DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
}
[CommandHipc(4)] // 6.0.0+
// DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
{
ResultCode result;
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
}
ulong inPosition = context.Request.SendBuff[0].Position;
ulong inSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
byte[] buffer = new byte[inSize];
context.Memory.Read(inPosition, buffer);
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
{
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
if (result == ResultCode.Success)
{
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
context.Memory.Write(outputPosition, pcmDataBytes);
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
// This is the time the DSP took to process the request, TODO: fill this.
context.ResponseData.Write(0);
}
}
return result;
[CommandHipc(5)] // 6.0.0+
// DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
}
[CommandHipc(6)] // 6.0.0+
// DecodeInterleavedOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedOld(ServiceCtx context)
// DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
{
ResultCode result;
bool reset = context.RequestData.ReadBoolean();
_reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
}
ulong inPosition = context.Request.SendBuff[0].Position;
ulong inSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
[CommandHipc(7)] // 6.0.0+
// DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
byte[] buffer = new byte[inSize];
context.Memory.Read(inPosition, buffer);
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
{
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
if (result == ResultCode.Success)
{
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
context.Memory.Write(outputPosition, pcmDataBytes);
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
// This is the time the DSP took to process the request, TODO: fill this.
context.ResponseData.Write(0);
}
}
return result;
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
}
[CommandHipc(8)] // 7.0.0+
// DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleaved(ServiceCtx context)
{
ResultCode result;
bool reset = context.RequestData.ReadBoolean();
_reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
}
[CommandHipc(9)] // 7.0.0+
// DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
}
private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
{
ulong inPosition = context.Request.SendBuff[0].Position;
ulong inSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
byte[] buffer = new byte[inSize];
ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
context.Memory.Read(inPosition, buffer);
ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
if (result == ResultCode.Success)
{
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
if (result == ResultCode.Success)
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
if (withPerf)
{
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
context.Memory.Write(outputPosition, pcmDataBytes);
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
// This is the time the DSP took to process the request, TODO: fill this.
context.ResponseData.Write(0);
context.ResponseData.Write(0UL);
}
}

View File

@@ -0,0 +1,28 @@
using Concentus.Structs;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class MultiSampleDecoder : IDecoder
{
private readonly OpusMSDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount { get; }
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{
ChannelsCount = channelsCount;
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
}
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
}
public void ResetState()
{
_decoder.ResetState();
}
}
}

View File

@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio
{
@@ -16,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio
int sampleRate = context.RequestData.ReadInt32();
int channelsCount = context.RequestData.ReadInt32();
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount));
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
@@ -28,11 +29,50 @@ namespace Ryujinx.HLE.HOS.Services.Audio
// GetWorkBufferSize(bytes<8, 4>) -> u32
public ResultCode GetWorkBufferSize(ServiceCtx context)
{
// NOTE: The sample rate is ignored because it is fixed to 48KHz.
int sampleRate = context.RequestData.ReadInt32();
int channelsCount = context.RequestData.ReadInt32();
context.ResponseData.Write(GetOpusDecoderSize(channelsCount));
int opusDecoderSize = GetOpusDecoderSize(channelsCount);
int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
int totalSize = opusDecoderSize + 1536 + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandHipc(2)] // 3.0.0+
// InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode InitializeForMultiStream(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandHipc(3)] // 3.0.0+
// GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + streamSize + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
@@ -44,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelCount));
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
@@ -58,15 +98,84 @@ namespace Ryujinx.HLE.HOS.Services.Audio
{
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
// NOTE: The sample rate is ignored because it is fixed to 48KHz.
context.ResponseData.Write(GetOpusDecoderSize(parameters.ChannelCount));
int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + 1536 + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandHipc(6)] // 12.0.0+
// InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.ToSpan()).ToArray();
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
MakeObject(context, new IHardwareOpusDecoder(
parameters.SampleRate,
parameters.ChannelsCount,
parameters.NumberOfStreams,
parameters.NumberOfStereoStreams,
parameters.Flags,
mappings));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandHipc(7)] // 12.0.0+
// GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + streamSize + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
{
if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
{
return 0;
}
int coupledSize = GetOpusDecoderSize(2);
int monoSize = GetOpusDecoderSize(1);
return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
}
private static int Align4(int value)
{
return BitUtils.AlignUp(value, 4);
}
private static int GetOpusDecoderSize(int channelsCount)
{
const int silkDecoderSize = 0x2198;
const int SilkDecoderSize = 0x2160;
if (channelsCount < 1 || channelsCount > 2)
{
@@ -74,24 +183,23 @@ namespace Ryujinx.HLE.HOS.Services.Audio
}
int celtDecoderSize = GetCeltDecoderSize(channelsCount);
int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
int opusDecoderSize = (channelsCount * 0x800 + 0x4807) & -0x800 | 0x50;
return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
}
return opusDecoderSize + silkDecoderSize + celtDecoderSize;
private static int GetOpusDecoderAllocSize(int channelsCount)
{
return (channelsCount * 0x800 + 0x4803) & -0x800;
}
private static int GetCeltDecoderSize(int channelsCount)
{
const int decodeBufferSize = 0x2030;
const int celtDecoderSize = 0x58;
const int celtSigSize = 0x4;
const int overlap = 120;
const int eBandsCount = 21;
const int DecodeBufferSize = 0x2030;
const int Overlap = 120;
const int EBandsCount = 21;
return (decodeBufferSize + overlap * 4) * channelsCount +
eBandsCount * 16 +
celtDecoderSize +
celtSigSize;
return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
}
}
}

View File

@@ -0,0 +1,15 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x110)]
struct OpusMultiStreamParameters
{
public int SampleRate;
public int ChannelsCount;
public int NumberOfStreams;
public int NumberOfStereoStreams;
public Array64<uint> ChannelMappings;
}
}

View File

@@ -0,0 +1,19 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x118)]
struct OpusMultiStreamParametersEx
{
public int SampleRate;
public int ChannelsCount;
public int NumberOfStreams;
public int NumberOfStereoStreams;
public OpusDecoderFlags Flags;
Array4<byte> Padding1;
public Array64<uint> ChannelMappings;
}
}

View File

@@ -12,9 +12,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.Types
public uint length;
public uint finalRange;
public static OpusPacketHeader FromStream(BinaryReader reader)
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
{
OpusPacketHeader header = reader.ReadStruct<OpusPacketHeader>();
OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;

View File

@@ -7,8 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Audio.Types
struct OpusParametersEx
{
public int SampleRate;
public int ChannelCount;
public OpusDecoderFlags UseLargeFrameSize;
public int ChannelsCount;
public OpusDecoderFlags Flags;
Array4<byte> Padding1;
}

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private ControllerType[] _configuredTypes;
private KEvent[] _styleSetUpdateEvents;
private bool[] _supportedPlayers;
private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue
private static VibrationValue _neutralVibrationValue = new VibrationValue
{
AmplitudeLow = 0f,
FrequencyLow = 160f,
@@ -33,8 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
internal ControllerType SupportedStyleSets { get; set; }
public Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>();
public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>();
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>();
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>();
public NpadDevices(Switch device, bool active = true) : base(device, active)
{
@@ -588,21 +588,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
}
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues)
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues)
{
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue))
{
if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue))
{
leftVibrationValue = _neutralVibrationValue;
}
if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue))
{
rightVibrationValue = _neutralVibrationValue;
}
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
{
currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
@@ -611,9 +611,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
}
public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
{
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue))
{
return _neutralVibrationValue;
}
@@ -621,11 +621,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
}
public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index)
{
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue))
{
rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>();
_device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
}

View File

@@ -35,5 +35,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
PlayerIndex.Unknown => NpadIdType.Unknown,
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
public static bool IsValidNpadIdType(NpadIdType npadIdType)
{
return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown;
}
}
}

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadJoyAssignmentMode
{
Dual,
Single
}
}

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadHandheldActivationMode
public enum NpadHandheldActivationMode
{
Dual,
Single,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadJoyDeviceType
public enum NpadJoyDeviceType
{
Left,
Right

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidAccelerometerParameters
public struct AccelerometerParameters
{
public float X;
public float Y;

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidGyroscopeZeroDriftMode
public enum GyroscopeZeroDriftMode
{
Loose,
Standard,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidSensorFusionParameters
public struct SensorFusionParameters
{
public float RevisePower;
public float ReviseRange;

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationDeviceValue
{
public HidVibrationDeviceType DeviceType;
public HidVibrationDevicePosition Position;
}
}

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationDeviceHandle
public struct VibrationDeviceHandle
{
public byte DeviceType;
public byte PlayerId;

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidVibrationDevicePosition
public enum VibrationDevicePosition
{
None,
Left,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidVibrationDeviceType
public enum VibrationDeviceType
{
None,
LinearResonantActuator,

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct VibrationDeviceValue
{
public VibrationDeviceType DeviceType;
public VibrationDevicePosition Position;
}
}

View File

@@ -1,9 +1,8 @@
using Ryujinx.HLE.HOS.Tamper;
using System;
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationValue
public struct VibrationValue
{
public float AmplitudeLow;
public float FrequencyLow;
@@ -12,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public override bool Equals(object obj)
{
return obj is HidVibrationValue value &&
return obj is VibrationValue value &&
AmplitudeLow == value.AmplitudeLow &&
AmplitudeHigh == value.AmplitudeHigh;
}

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@@ -26,9 +27,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
private bool _isSixAxisSensorUnalteredPassthroughEnabled;
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private NpadHandheldActivationMode _npadHandheldActivationMode;
private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private long _npadCommunicationMode;
private uint _accelerometerPlayMode;
@@ -37,22 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
#pragma warning restore CS0649
private float _sevenSixAxisSensorFusionStrength;
private HidSensorFusionParameters _sensorFusionParams;
private HidAccelerometerParameters _accelerometerParams;
private SensorFusionParameters _sensorFusionParams;
private AccelerometerParameters _accelerometerParams;
public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
{
_xpadIdEvent = new KEvent(context.Device.System.KernelContext);
_palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext);
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
_npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual;
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
_npadHandheldActivationMode = NpadHandheldActivationMode.Dual;
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
_isFirmwareUpdateAvailableForSixAxisSensor = false;
_sensorFusionParams = new HidSensorFusionParameters();
_accelerometerParams = new HidAccelerometerParameters();
_sensorFusionParams = new SensorFusionParameters();
_accelerometerParams = new AccelerometerParameters();
// TODO: signal event at right place
_xpadIdEvent.ReadableEvent.Signal();
@@ -393,7 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
_sensorFusionParams = new HidSensorFusionParameters
_sensorFusionParams = new SensorFusionParameters
{
RevisePower = context.RequestData.ReadInt32(),
ReviseRange = context.RequestData.ReadInt32()
@@ -445,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
_accelerometerParams = new HidAccelerometerParameters
_accelerometerParams = new AccelerometerParameters
{
X = context.RequestData.ReadInt32(),
Y = context.RequestData.ReadInt32()
@@ -539,7 +538,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
_gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@@ -570,7 +569,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@@ -909,54 +908,63 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
}
return ResultCode.Success;
}
[CommandHipc(123)]
// SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType)
// SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType)
public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _);
}
return ResultCode.Success;
}
[CommandHipc(124)]
// SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId)
// SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
{
PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
}
return ResultCode.Success;
}
[CommandHipc(125)]
// MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId)
// MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId)
public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context)
{
long singleJoyId0 = context.RequestData.ReadInt32();
long singleJoyId1 = context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32();
NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 });
if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 });
}
return ResultCode.Success;
}
@@ -988,7 +996,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetNpadHandheldActivationMode(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
_npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64();
_npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
@@ -1049,37 +1057,47 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandHipc(133)] // 5.0.0+
// SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1
// SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet
public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
if (HidUtils.IsValidNpadIdType(npadIdType))
{
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet);
context.ResponseData.Write(0); //Unknown0
context.ResponseData.Write(0); //Unknown1
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
hidControllerId,
hidNpadJoyDeviceType,
_npadJoyAssignmentMode,
Unknown0 = 0,
Unknown1 = 0
});
if (npadIdTypeIsSet)
{
context.ResponseData.Write(npadIdTypeIsSet);
context.ResponseData.Write((uint)npadIdTypeSet);
}
}
return ResultCode.Success;
}
private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet)
{
npadIdTypeSet = default;
npadIdTypeIsSet = false;
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
// TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual.
// If one is found, it returns the npadIdType of the other Npad and a bool.
// If not, it returns nothing.
}
[CommandHipc(200)]
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<HidVibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{
@@ -1093,28 +1111,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return ResultCode.InvalidDeviceIndex;
}
HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None;
VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None;
if (Enum.IsDefined<NpadStyleIndex>(deviceType))
if (Enum.IsDefined(deviceType))
{
vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator;
vibrationDeviceType = VibrationDeviceType.LinearResonantActuator;
}
else if ((uint)deviceType == 8)
{
vibrationDeviceType = HidVibrationDeviceType.GcErm;
vibrationDeviceType = VibrationDeviceType.GcErm;
}
HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None;
VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None;
if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator)
if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator)
{
if (deviceHandle.Position == 0)
{
vibrationDevicePosition = HidVibrationDevicePosition.Left;
vibrationDevicePosition = VibrationDevicePosition.Left;
}
else if (deviceHandle.Position == 1)
{
vibrationDevicePosition = HidVibrationDevicePosition.Right;
vibrationDevicePosition = VibrationDevicePosition.Right;
}
else
{
@@ -1122,7 +1140,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
}
HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue
VibrationDeviceValue deviceInfo = new VibrationDeviceValue
{
DeviceType = vibrationDeviceType,
Position = vibrationDevicePosition
@@ -1140,7 +1158,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
public ResultCode SendVibrationValue(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
@@ -1148,7 +1166,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
Reserved = context.RequestData.ReadByte()
};
HidVibrationValue vibrationValue = new HidVibrationValue
VibrationValue vibrationValue = new VibrationValue
{
AmplitudeLow = context.RequestData.ReadSingle(),
FrequencyLow = context.RequestData.ReadSingle(),
@@ -1158,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64();
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
dualVibrationValues[deviceHandle.Position] = vibrationValue;
@@ -1171,7 +1189,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
public ResultCode GetActualVibrationValue(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
@@ -1181,7 +1199,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64();
HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
context.ResponseData.Write(vibrationValue.AmplitudeLow);
context.ResponseData.Write(vibrationValue.FrequencyLow);
@@ -1234,12 +1252,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
Span<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer);
Span<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer);
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId;
for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++)
@@ -1250,7 +1268,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
if (index != currentIndex || dualVibrationValues.Count == 2)
{
context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
dualVibrationValues = new Dictionary<byte, VibrationValue>();
}
dualVibrationValues[position] = vibrationValues[deviceCounter];

View File

@@ -1,8 +1,31 @@
namespace Ryujinx.HLE.HOS.Services.Pm
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
namespace Ryujinx.HLE.HOS.Services.Pm
{
[Service("pm:dmnt")]
class IDebugMonitorInterface : IpcService
{
public IDebugMonitorInterface(ServiceCtx context) { }
[CommandHipc(65000)]
// AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status
public ResultCode GetProcessInfo(ServiceCtx context)
{
ulong pid = context.RequestData.ReadUInt64();
KProcess process = KernelStatic.GetProcessByPid(pid);
if (context.Process.HandleTable.GenerateHandle(process, out int processHandle) != KernelResult.Success)
{
throw new System.Exception("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(processHandle);
return ResultCode.Success;
}
}
}

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.IO;
@@ -29,6 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
private List<NroInfo> _nroInfos;
private KProcess _owner;
private IVirtualMemoryManager _ownerMm;
private static Random _random = new Random();
@@ -37,6 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
_nrrInfos = new List<NrrInfo>(MaxNrr);
_nroInfos = new List<NroInfo>(MaxNro);
_owner = null;
_ownerMm = null;
}
private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize)
@@ -563,12 +566,28 @@ namespace Ryujinx.HLE.HOS.Services.Ro
return ResultCode.InvalidSession;
}
_owner = context.Process.HandleTable.GetKProcess(context.Request.HandleDesc.ToCopy[0]);
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
int processHandle = context.Request.HandleDesc.ToCopy[0];
_owner = context.Process.HandleTable.GetKProcess(processHandle);
_ownerMm = _owner?.CpuMemory;
context.Device.System.KernelContext.Syscall.CloseHandle(processHandle);
if (_ownerMm is IRefCounted rc)
{
rc.IncrementReferenceCount();
}
return ResultCode.Success;
}
[CommandHipc(10)]
// LoadNrr2(u64, u64, u64, pid)
public ResultCode LoadNrr2(ServiceCtx context)
{
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return LoadNrr(context);
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
@@ -579,6 +598,11 @@ namespace Ryujinx.HLE.HOS.Services.Ro
}
_nroInfos.Clear();
if (_ownerMm is IRefCounted rc)
{
rc.DecrementReferenceCount();
}
}
}
}

View File

@@ -4,7 +4,6 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Sm;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;

View File

@@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandHipc(60)]
[CommandHipc(60)]
// IsUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
@@ -234,6 +234,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandHipc(62)]
// GetDebugModeFlag() -> bool
public ResultCode GetDebugModeFlag(ServiceCtx context)
{
context.ResponseData.Write(false);
Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
[CommandHipc(77)]
// GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16>
public ResultCode GetDeviceNickName(ServiceCtx context)

View File

@@ -1,11 +1,9 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -17,21 +15,19 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
private static Dictionary<string, Type> _services;
private static readonly ConcurrentDictionary<string, KPort> _registeredServices;
private readonly SmRegistry _registry;
private readonly ServerBase _commonServer;
private bool _isInitialized;
public IUserInterface(KernelContext context)
public IUserInterface(KernelContext context, SmRegistry registry)
{
_commonServer = new ServerBase(context, "CommonServer");
_registry = registry;
}
static IUserInterface()
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
_services = Assembly.GetExecutingAssembly().GetTypes()
.SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true)
.Select(service => (((ServiceAttribute)service).Name, type)))
@@ -74,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KSession session = new KSession(context.Device.System.KernelContext);
if (_registeredServices.TryGetValue(name, out KPort port))
if (_registry.TryGetService(name, out KPort port))
{
KernelResult result = port.EnqueueIncomingSession(session.ServerSession);
@@ -82,6 +78,15 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\".");
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
}
else
{
@@ -107,18 +112,18 @@ namespace Ryujinx.HLE.HOS.Services.Sm
throw new NotImplementedException(name);
}
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
return ResultCode.Success;
}
@@ -179,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0);
if (!_registeredServices.TryAdd(name, port))
if (!_registry.TryRegister(name, port))
{
return ResultCode.AlreadyRegistered;
}
@@ -219,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
return ResultCode.InvalidName;
}
if (!_registeredServices.TryRemove(name, out _))
if (!_registry.Unregister(name))
{
return ResultCode.NotRegistered;
}

View File

@@ -0,0 +1,49 @@
using Ryujinx.HLE.HOS.Kernel.Ipc;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sm
{
class SmRegistry
{
private readonly ConcurrentDictionary<string, KPort> _registeredServices;
private readonly AutoResetEvent _serviceRegistrationEvent;
public SmRegistry()
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
_serviceRegistrationEvent = new AutoResetEvent(false);
}
public bool TryGetService(string name, out KPort port)
{
return _registeredServices.TryGetValue(name, out port);
}
public bool TryRegister(string name, KPort port)
{
if (_registeredServices.TryAdd(name, port))
{
_serviceRegistrationEvent.Set();
return true;
}
return false;
}
public bool Unregister(string name)
{
return _registeredServices.TryRemove(name, out _);
}
public bool IsServiceRegistered(string name)
{
return _registeredServices.TryGetValue(name, out _);
}
public void WaitForServiceRegistration()
{
_serviceRegistrationEvent.WaitOne();
}
}
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Audio.Backends.CompatLayer;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
@@ -48,8 +49,12 @@ namespace Ryujinx.HLE
FileSystem = Configuration.VirtualFileSystem;
UiHandler = Configuration.HostUiHandler;
MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable
? MemoryAllocationFlags.Reserve
: MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable;
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), MemoryAllocationFlags.Reserve);
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
Gpu = new GpuContext(Configuration.GpuRenderer);
System = new Horizon(this);
Statistics = new PerformanceStatistics();

View File

@@ -302,7 +302,7 @@ namespace Ryujinx.Input.HLE
Vector3 gyroscope = _gamepad.GetMotionData(MotionInputId.Gyroscope);
accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, gyroscope.Z, gyroscope.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);
_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
@@ -543,14 +543,14 @@ namespace Ryujinx.Input.HLE
Dispose(true);
}
public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue)
public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue)
{
if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
{
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
{
HidVibrationValue leftVibrationValue = dualVibrationValue.Item1;
HidVibrationValue rightVibrationValue = dualVibrationValue.Item2;
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Memory.Tests
{
}
public void Map(ulong va, nuint hostAddress, ulong size)
public void Map(ulong va, ulong pa, ulong size)
{
throw new NotImplementedException();
}
@@ -59,9 +59,9 @@ namespace Ryujinx.Memory.Tests
throw new NotImplementedException();
}
IEnumerable<HostMemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
{
return NoMappings ? new HostMemoryRange[0] : new HostMemoryRange[] { new HostMemoryRange((nuint)va, size) };
return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) };
}
public bool IsMapped(ulong va)

View File

@@ -1,5 +1,4 @@
using NUnit.Framework;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
@@ -38,5 +37,48 @@ namespace Ryujinx.Memory.Tests
Assert.AreEqual(Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040), 0xbadc0de);
}
[Test, Explicit]
public void Test_Alias()
{
using MemoryBlock backing = new MemoryBlock(0x10000, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(0x10000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
toAlias.MapView(backing, 0x1000, 0, 0x4000);
toAlias.UnmapView(backing, 0x3000, 0x1000);
toAlias.Write(0, 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, 0x1000), 0xbadc0de);
}
[Test, Explicit]
public void Test_AliasRandom()
{
using MemoryBlock backing = new MemoryBlock(0x80000, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(0x80000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
Random rng = new Random(123);
for (int i = 0; i < 20000; i++)
{
int srcPage = rng.Next(0, 64);
int dstPage = rng.Next(0, 64);
int pages = rng.Next(1, 65);
if ((rng.Next() & 1) != 0)
{
toAlias.MapView(backing, (ulong)srcPage << 12, (ulong)dstPage << 12, (ulong)pages << 12);
int offset = rng.Next(0, 0x1000 - sizeof(int));
toAlias.Write((ulong)((dstPage << 12) + offset), 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << 12) + offset), 0xbadc0de);
}
else
{
toAlias.UnmapView(backing, (ulong)dstPage << 12, (ulong)pages << 12);
}
}
}
}
}

View File

@@ -13,9 +13,9 @@ namespace Ryujinx.Memory
/// </summary>
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
{
public const int PageBits = PageTable<nuint>.PageBits;
public const int PageSize = PageTable<nuint>.PageSize;
public const int PageMask = PageTable<nuint>.PageMask;
public const int PageBits = PageTable<ulong>.PageBits;
public const int PageSize = PageTable<ulong>.PageSize;
public const int PageMask = PageTable<ulong>.PageMask;
/// <summary>
/// Address space width in bits.
@@ -24,14 +24,15 @@ namespace Ryujinx.Memory
private readonly ulong _addressSpaceSize;
private readonly PageTable<nuint> _pageTable;
private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable;
/// <summary>
/// Creates a new instance of the memory manager.
/// </summary>
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
/// <param name="addressSpaceSize">Size of the address space</param>
public AddressSpaceManager(ulong addressSpaceSize)
public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize)
{
ulong asSize = PageSize;
int asBits = PageBits;
@@ -44,37 +45,26 @@ namespace Ryujinx.Memory
AddressSpaceBits = asBits;
_addressSpaceSize = asSize;
_pageTable = new PageTable<nuint>();
_backingMemory = backingMemory;
_pageTable = new PageTable<ulong>();
}
/// <summary>
/// Maps a virtual memory range into a physical memory range.
/// </summary>
/// <remarks>
/// Addresses and size must be page aligned.
/// </remarks>
/// <param name="va">Virtual memory address</param>
/// <param name="hostAddress">Physical memory address</param>
/// <param name="size">Size to be mapped</param>
public void Map(ulong va, nuint hostAddress, ulong size)
/// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size)
{
AssertValidAddressAndSize(va, size);
while (size != 0)
{
_pageTable.Map(va, hostAddress);
_pageTable.Map(va, pa);
va += PageSize;
hostAddress += PageSize;
pa += PageSize;
size -= PageSize;
}
}
/// <summary>
/// Unmaps a previously mapped range of virtual memory.
/// </summary>
/// <param name="va">Virtual address of the range to be unmapped</param>
/// <param name="size">Size of the range to be unmapped</param>
/// <inheritdoc/>
public void Unmap(ulong va, ulong size)
{
AssertValidAddressAndSize(va, size);
@@ -88,47 +78,25 @@ namespace Ryujinx.Memory
}
}
/// <summary>
/// Reads data from mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data being read</typeparam>
/// <param name="va">Virtual address of the data in memory</param>
/// <returns>The data</returns>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
/// <summary>
/// Reads data from mapped memory.
/// </summary>
/// <param name="va">Virtual address of the data in memory</param>
/// <param name="data">Span to store the data being read into</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
{
ReadImpl(va, data);
}
/// <summary>
/// Writes data to mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="value">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <summary>
/// Writes data to mapped memory.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
@@ -140,7 +108,7 @@ namespace Ryujinx.Memory
if (IsContiguousAndMapped(va, data.Length))
{
data.CopyTo(GetHostSpanContiguous(va, data.Length));
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
}
else
{
@@ -148,34 +116,27 @@ namespace Ryujinx.Memory
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
}
}
}
/// <summary>
/// Gets a read-only span of data from mapped memory.
/// </summary>
/// <remarks>
/// This may perform a allocation if the data is not contiguous in memory.
/// For this reason, the span is read-only, you can't modify the data.
/// </remarks>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <param name="tracked">True if read tracking is triggered on the span</param>
/// <returns>A read-only span of the data</returns>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
if (size == 0)
@@ -185,7 +146,7 @@ namespace Ryujinx.Memory
if (IsContiguousAndMapped(va, size))
{
return GetHostSpanContiguous(va, size);
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
}
else
{
@@ -197,19 +158,7 @@ namespace Ryujinx.Memory
}
}
/// <summary>
/// Gets a region of memory that can be written to.
/// </summary>
/// <remarks>
/// If the requested region is not contiguous in physical memory,
/// this will perform an allocation, and flush the data (writing it
/// back to the backing memory) on disposal.
/// </remarks>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <param name="tracked">True if write tracking is triggered on the span</param>
/// <returns>A writable region of memory containing the data</returns>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <inheritdoc/>
public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{
if (size == 0)
@@ -219,7 +168,7 @@ namespace Ryujinx.Memory
if (IsContiguousAndMapped(va, size))
{
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
else
{
@@ -231,33 +180,18 @@ namespace Ryujinx.Memory
}
}
/// <summary>
/// Gets a reference for the given type at the specified virtual memory address.
/// </summary>
/// <remarks>
/// The data must be located at a contiguous memory region.
/// </remarks>
/// <typeparam name="T">Type of the data to get the reference</typeparam>
/// <param name="va">Virtual address of the data</param>
/// <returns>A reference to the data in memory</returns>
/// <exception cref="MemoryNotContiguousException">Throw if the specified memory region is not contiguous in physical memory</exception>
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
/// <inheritdoc/>
public ref T GetRef<T>(ulong va) where T : unmanaged
{
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
{
ThrowMemoryNotContiguous();
}
return ref *(T*)GetHostAddress(va);
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
}
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetPagesCount(ulong va, uint size, out ulong startVa)
{
@@ -268,7 +202,7 @@ namespace Ryujinx.Memory
return (int)(vaSpan / PageSize);
}
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
@@ -290,7 +224,7 @@ namespace Ryujinx.Memory
return false;
}
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
{
return false;
}
@@ -301,18 +235,12 @@ namespace Ryujinx.Memory
return true;
}
/// <summary>
/// Gets the physical regions that make up the given virtual address region.
/// If any part of the virtual region is unmapped, null is returned.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>Array of physical regions</returns>
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
/// <inheritdoc/>
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
if (size == 0)
{
return Enumerable.Empty<HostMemoryRange>();
return Enumerable.Empty<MemoryRange>();
}
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
@@ -322,9 +250,9 @@ namespace Ryujinx.Memory
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<HostMemoryRange>();
var regions = new List<MemoryRange>();
nuint regionStart = GetHostAddress(va);
ulong regionStart = GetPhysicalAddressInternal(va);
ulong regionSize = PageSize;
for (int page = 0; page < pages - 1; page++)
@@ -334,12 +262,12 @@ namespace Ryujinx.Memory
return null;
}
nuint newHostAddress = GetHostAddress(va + PageSize);
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
if (GetHostAddress(va) + PageSize != newHostAddress)
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
{
regions.Add(new HostMemoryRange(regionStart, regionSize));
regionStart = newHostAddress;
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
regionSize = 0;
}
@@ -347,7 +275,7 @@ namespace Ryujinx.Memory
regionSize += PageSize;
}
regions.Add(new HostMemoryRange(regionStart, regionSize));
regions.Add(new MemoryRange(regionStart, regionSize));
return regions;
}
@@ -365,26 +293,26 @@ namespace Ryujinx.Memory
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
/// <summary>
/// Checks if the page at a given virtual address is mapped.
/// </summary>
/// <param name="va">Virtual address to check</param>
/// <returns>True if the address is mapped, false otherwise</returns>
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
@@ -396,12 +324,7 @@ namespace Ryujinx.Memory
return _pageTable.Read(va) != 0;
}
/// <summary>
/// Checks if a memory range is mapped.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the entire range is mapped, false otherwise</returns>
/// <inheritdoc/>
public bool IsRangeMapped(ulong va, ulong size)
{
if (size == 0UL)
@@ -460,14 +383,9 @@ namespace Ryujinx.Memory
}
}
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
private ulong GetPhysicalAddressInternal(ulong va)
{
return new Span<byte>((void*)GetHostAddress(va), size);
}
private nuint GetHostAddress(ulong va)
{
return _pageTable.Read(va) + (nuint)(va & PageMask);
return _pageTable.Read(va) + (va & PageMask);
}
/// <summary>

View File

@@ -13,9 +13,9 @@ namespace Ryujinx.Memory
/// Addresses and size must be page aligned.
/// </remarks>
/// <param name="va">Virtual memory address</param>
/// <param name="hostAddress">Pointer where the region should be mapped to</param>
/// <param name="pa">Physical memory address where the region should be mapped to</param>
/// <param name="size">Size to be mapped</param>
void Map(ulong va, nuint hostAddress, ulong size);
void Map(ulong va, ulong pa, ulong size);
/// <summary>
/// Unmaps a previously mapped range of virtual memory.
@@ -111,7 +111,7 @@ namespace Ryujinx.Memory
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>Array of physical regions</returns>
IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size);
IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size);
/// <summary>
/// Checks if the page at a given CPU virtual address is mapped.

View File

@@ -29,6 +29,18 @@ namespace Ryujinx.Memory
/// Enables mirroring of the memory block through aliasing of memory pages.
/// When enabled, this allows creating more memory blocks sharing the same backing storage.
/// </summary>
Mirrorable = 1 << 2
Mirrorable = 1 << 2,
/// <summary>
/// Indicates that the memory block should support mapping views of a mirrorable memory block.
/// The block that is to have their views mapped should be created with the <see cref="Mirrorable"/> flag.
/// </summary>
ViewCompatible = 1 << 3,
/// <summary>
/// Forces views to be mapped page by page on Windows. When partial unmaps are done, this avoids the need
/// to unmap the full range and remap sub-ranges, which creates a time window with incorrectly unmapped memory.
/// </summary>
ForceWindows4KBViewMapping = 1 << 4
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -11,8 +12,12 @@ namespace Ryujinx.Memory
{
private readonly bool _usesSharedMemory;
private readonly bool _isMirror;
private readonly bool _viewCompatible;
private readonly bool _forceWindows4KBView;
private IntPtr _sharedMemory;
private IntPtr _pointer;
private ConcurrentDictionary<MemoryBlock, byte> _viewStorages;
private int _viewCount;
/// <summary>
/// Pointer to the memory block data.
@@ -36,12 +41,14 @@ namespace Ryujinx.Memory
if (flags.HasFlag(MemoryAllocationFlags.Mirrorable))
{
_sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve));
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory);
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size);
_usesSharedMemory = true;
}
else if (flags.HasFlag(MemoryAllocationFlags.Reserve))
{
_pointer = MemoryManagement.Reserve(size);
_viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible);
_forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping);
_pointer = MemoryManagement.Reserve(size, _viewCompatible, _forceWindows4KBView);
}
else
{
@@ -49,6 +56,10 @@ namespace Ryujinx.Memory
}
Size = size;
_viewStorages = new ConcurrentDictionary<MemoryBlock, byte>();
_viewStorages.TryAdd(this, 0);
_viewCount = 1;
}
/// <summary>
@@ -60,7 +71,7 @@ namespace Ryujinx.Memory
/// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
private MemoryBlock(ulong size, IntPtr sharedMemory)
{
_pointer = MemoryManagement.MapSharedMemory(sharedMemory);
_pointer = MemoryManagement.MapSharedMemory(sharedMemory, size);
Size = size;
_usesSharedMemory = true;
_isMirror = true;
@@ -112,6 +123,42 @@ namespace Ryujinx.Memory
return MemoryManagement.Decommit(GetPointerInternal(offset, size), size);
}
/// <summary>
/// Maps a view of memory from another memory block.
/// </summary>
/// <param name="srcBlock">Memory block from where the backing memory will be taken</param>
/// <param name="srcOffset">Offset on <paramref name="srcBlock"/> of the region that should be mapped</param>
/// <param name="dstOffset">Offset to map the view into on this block</param>
/// <param name="size">Size of the range to be mapped</param>
/// <exception cref="NotSupportedException">Throw when the source memory block does not support mirroring</exception>
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
{
if (srcBlock._sharedMemory == IntPtr.Zero)
{
throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block.");
}
if (_viewStorages.TryAdd(srcBlock, 0))
{
srcBlock.IncrementViewCount();
}
MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, _forceWindows4KBView);
}
/// <summary>
/// Unmaps a view of memory from another memory block.
/// </summary>
/// <param name="srcBlock">Memory block from where the backing memory was taken during map</param>
/// <param name="offset">Offset of the view previously mapped with <see cref="MapView"/></param>
/// <param name="size">Size of the range to be unmapped</param>
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
{
MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, _forceWindows4KBView);
}
/// <summary>
/// Reprotects a region of memory.
/// </summary>
@@ -124,21 +171,7 @@ namespace Ryujinx.Memory
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
{
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, throwOnFail);
}
/// <summary>
/// Remaps a region of memory into this memory block.
/// </summary>
/// <param name="offset">Starting offset of the range to be remapped into</param>
/// <param name="sourceAddress">Starting offset of the range to be remapped from</param>
/// <param name="size">Size of the range to be remapped</param>
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
public void Remap(ulong offset, IntPtr sourceAddress, ulong size)
{
MemoryManagement.Remap(GetPointerInternal(offset, size), sourceAddress, size);
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, _forceWindows4KBView, throwOnFail);
}
/// <summary>
@@ -274,7 +307,7 @@ namespace Ryujinx.Memory
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public nuint GetPointer(ulong offset, ulong size) => (nuint)(ulong)GetPointerInternal(offset, size);
public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IntPtr GetPointerInternal(ulong offset, ulong size)
@@ -367,22 +400,63 @@ namespace Ryujinx.Memory
{
if (_usesSharedMemory)
{
MemoryManagement.UnmapSharedMemory(ptr);
if (_sharedMemory != IntPtr.Zero && !_isMirror)
{
MemoryManagement.DestroySharedMemory(_sharedMemory);
_sharedMemory = IntPtr.Zero;
}
MemoryManagement.UnmapSharedMemory(ptr, Size);
}
else
{
MemoryManagement.Free(ptr);
MemoryManagement.Free(ptr, Size, _forceWindows4KBView);
}
foreach (MemoryBlock viewStorage in _viewStorages.Keys)
{
viewStorage.DecrementViewCount();
}
_viewStorages.Clear();
}
}
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
/// <summary>
/// Increments the number of views that uses this memory block as storage.
/// </summary>
private void IncrementViewCount()
{
Interlocked.Increment(ref _viewCount);
}
/// <summary>
/// Decrements the number of views that uses this memory block as storage.
/// </summary>
private void DecrementViewCount()
{
if (Interlocked.Decrement(ref _viewCount) == 0 && _sharedMemory != IntPtr.Zero && !_isMirror)
{
MemoryManagement.DestroySharedMemory(_sharedMemory);
_sharedMemory = IntPtr.Zero;
}
}
/// <summary>
/// Checks if the specified memory allocation flags are supported on the current platform.
/// </summary>
/// <param name="flags">Flags to be checked</param>
/// <returns>True if the platform supports all the flags, false otherwise</returns>
public static bool SupportsFlags(MemoryAllocationFlags flags)
{
if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible))
{
if (OperatingSystem.IsWindows())
{
return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134);
}
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
}
return true;
}
private static void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
}
}

View File

@@ -8,12 +8,9 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Allocate(sizeNint);
return MemoryManagementWindows.Allocate((IntPtr)size);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Allocate(size);
}
@@ -23,16 +20,13 @@ namespace Ryujinx.Memory
}
}
public static IntPtr Reserve(ulong size)
public static IntPtr Reserve(ulong size, bool viewCompatible, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Reserve(sizeNint);
return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible, force4KBMap);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Reserve(size);
}
@@ -46,12 +40,9 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Commit(address, sizeNint);
return MemoryManagementWindows.Commit(address, (IntPtr)size);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Commit(address, size);
}
@@ -65,12 +56,9 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Decommit(address, sizeNint);
return MemoryManagementWindows.Decommit(address, (IntPtr)size);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Decommit(address, size);
}
@@ -80,18 +68,68 @@ namespace Ryujinx.Memory
}
}
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool throwOnFail)
public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
if (force4KBMap)
{
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
}
else
{
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
MemoryManagementUnix.MapView(sharedMemory, srcOffset, address, size);
}
else
{
throw new PlatformNotSupportedException();
}
}
public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
if (force4KBMap)
{
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
}
else
{
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
MemoryManagementUnix.UnmapView(address, size);
}
else
{
throw new PlatformNotSupportedException();
}
}
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool force4KBMap, bool throwOnFail)
{
bool result;
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
result = MemoryManagementWindows.Reprotect(address, sizeNint, permission);
if (forView && force4KBMap)
{
result = MemoryManagementWindows.Reprotect4KB(address, (IntPtr)size, permission, forView);
}
else
{
result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
}
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
result = MemoryManagementUnix.Reprotect(address, size, permission);
}
@@ -106,14 +144,13 @@ namespace Ryujinx.Memory
}
}
public static bool Free(IntPtr address)
public static bool Free(IntPtr address, ulong size, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
return MemoryManagementWindows.Free(address);
return MemoryManagementWindows.Free(address, (IntPtr)size, force4KBMap);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Free(address);
}
@@ -127,12 +164,9 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.CreateSharedMemory(sizeNint, reserve);
return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.CreateSharedMemory(size, reserve);
}
@@ -148,8 +182,7 @@ namespace Ryujinx.Memory
{
MemoryManagementWindows.DestroySharedMemory(handle);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
MemoryManagementUnix.DestroySharedMemory(handle);
}
@@ -159,16 +192,15 @@ namespace Ryujinx.Memory
}
}
public static IntPtr MapSharedMemory(IntPtr handle)
public static IntPtr MapSharedMemory(IntPtr handle, ulong size)
{
if (OperatingSystem.IsWindows())
{
return MemoryManagementWindows.MapSharedMemory(handle);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.MapSharedMemory(handle);
return MemoryManagementUnix.MapSharedMemory(handle, size);
}
else
{
@@ -176,29 +208,15 @@ namespace Ryujinx.Memory
}
}
public static void UnmapSharedMemory(IntPtr address)
public static void UnmapSharedMemory(IntPtr address, ulong size)
{
if (OperatingSystem.IsWindows())
{
MemoryManagementWindows.UnmapSharedMemory(address);
}
else if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
MemoryManagementUnix.UnmapSharedMemory(address);
}
else
{
throw new PlatformNotSupportedException();
}
}
public static IntPtr Remap(IntPtr target, IntPtr source, ulong size)
{
if (OperatingSystem.IsLinux() ||
OperatingSystem.IsMacOS())
{
return MemoryManagementUnix.Remap(target, source, size);
MemoryManagementUnix.UnmapSharedMemory(address, size);
}
else
{

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