mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-02-07 01:59:42 +00:00
merge from master
This commit is contained in:
commit
03f87b3967
195 changed files with 3266 additions and 3507 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -50,7 +50,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-dotnet@v1
|
- uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.x
|
dotnet-version: 6.0.x
|
||||||
- name: Ensure NuGet Source
|
- name: Ensure NuGet Source
|
||||||
uses: fabriciomurta/ensure-nuget-source@v1
|
uses: fabriciomurta/ensure-nuget-source@v1
|
||||||
- name: Get git short hash
|
- name: Get git short hash
|
||||||
|
@ -63,10 +63,10 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test -c "${{ matrix.configuration }}"
|
run: dotnet test -c "${{ matrix.configuration }}"
|
||||||
- name: Publish Ryujinx
|
- name: Publish Ryujinx
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Publish Ryujinx.Headless.SDL2
|
- name: Publish Ryujinx.Headless.SDL2
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.0.0" /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'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -151,7 +151,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||||
|
|
||||||
public static CallConvName GetCurrentCallConv()
|
public static CallConvName GetCurrentCallConv()
|
||||||
{
|
{
|
||||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
return OperatingSystem.IsWindows()
|
||||||
? CallConvName.Windows
|
? CallConvName.Windows
|
||||||
: CallConvName.SystemV;
|
: CallConvName.SystemV;
|
||||||
}
|
}
|
||||||
|
|
|
@ -661,6 +661,7 @@ namespace ARMeilleure.Decoders
|
||||||
SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create);
|
SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, OpCode32AluReg.Create);
|
SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<00010100xxxxxxxx00000100xxxx", InstName.Crc32w, InstEmit32.Crc32w, OpCode32AluReg.Create);
|
SetA32("<<<<00010100xxxxxxxx00000100xxxx", InstName.Crc32w, InstEmit32.Crc32w, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<0011001000001111000000010100", InstName.Csdb, InstEmit32.Csdb, OpCode32.Create);
|
||||||
SetA32("1111010101111111111100000101xxxx", InstName.Dmb, InstEmit32.Dmb, OpCode32.Create);
|
SetA32("1111010101111111111100000101xxxx", InstName.Dmb, InstEmit32.Dmb, OpCode32.Create);
|
||||||
SetA32("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.Create);
|
SetA32("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.Create);
|
||||||
SetA32("<<<<0010001xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluImm.Create);
|
SetA32("<<<<0010001xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluImm.Create);
|
||||||
|
@ -776,6 +777,7 @@ namespace ARMeilleure.Decoders
|
||||||
SetA32("<<<<00010001xxxx0000xxxx0xx1xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsReg.Create);
|
SetA32("<<<<00010001xxxx0000xxxx0xx1xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsReg.Create);
|
||||||
SetA32("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.Create);
|
SetA32("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.Create);
|
||||||
SetA32("<<<<01110011xxxx1111xxxx0001xxxx", InstName.Udiv, InstEmit32.Udiv, OpCode32AluMla.Create);
|
SetA32("<<<<01110011xxxx1111xxxx0001xxxx", InstName.Udiv, InstEmit32.Udiv, OpCode32AluMla.Create);
|
||||||
|
SetA32("<<<<01100111xxxxxxxx11111001xxxx", InstName.Uhadd8, InstEmit32.Uhadd8, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
||||||
|
|
|
@ -472,6 +472,24 @@ namespace ARMeilleure.Instructions
|
||||||
EmitDiv(context, true);
|
EmitDiv(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Uhadd8(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
Operand m = GetIntA32(context, op.Rm);
|
||||||
|
Operand n = GetIntA32(context, op.Rn);
|
||||||
|
|
||||||
|
Operand xor, res;
|
||||||
|
|
||||||
|
res = context.BitwiseAnd(m, n);
|
||||||
|
xor = context.BitwiseExclusiveOr(m, n);
|
||||||
|
xor = context.ShiftRightUI(xor, Const(1));
|
||||||
|
xor = context.BitwiseAnd(xor, Const(0x7F7F7F7Fu));
|
||||||
|
res = context.Add(res, xor);
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, res);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Usat(ArmEmitterContext context)
|
public static void Usat(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
||||||
|
|
|
@ -16,6 +16,11 @@ namespace ARMeilleure.Instructions
|
||||||
EmitClearExclusive(context);
|
EmitClearExclusive(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Csdb(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
// Execute as no-op.
|
||||||
|
}
|
||||||
|
|
||||||
public static void Dmb(ArmEmitterContext context) => EmitBarrier(context);
|
public static void Dmb(ArmEmitterContext context) => EmitBarrier(context);
|
||||||
|
|
||||||
public static void Dsb(ArmEmitterContext context) => EmitBarrier(context);
|
public static void Dsb(ArmEmitterContext context) => EmitBarrier(context);
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace ARMeilleure.Instructions
|
||||||
Crc32ch,
|
Crc32ch,
|
||||||
Crc32cw,
|
Crc32cw,
|
||||||
Crc32cx,
|
Crc32cx,
|
||||||
|
Csdb,
|
||||||
Csel,
|
Csel,
|
||||||
Csinc,
|
Csinc,
|
||||||
Csinv,
|
Csinv,
|
||||||
|
@ -541,6 +542,7 @@ namespace ARMeilleure.Instructions
|
||||||
Trap,
|
Trap,
|
||||||
Tst,
|
Tst,
|
||||||
Ubfx,
|
Ubfx,
|
||||||
|
Uhadd8,
|
||||||
Umaal,
|
Umaal,
|
||||||
Umlal,
|
Umlal,
|
||||||
Umull,
|
Umull,
|
||||||
|
|
|
@ -95,7 +95,7 @@ namespace ARMeilleure.Signal
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
bool unix = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
bool unix = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
|
||||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||||
|
|
||||||
if (unix)
|
if (unix)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using Mono.Unix.Native;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
namespace ARMeilleure.Signal
|
||||||
|
@ -21,6 +20,7 @@ namespace ARMeilleure.Signal
|
||||||
|
|
||||||
static class UnixSignalHandlerRegistration
|
static class UnixSignalHandlerRegistration
|
||||||
{
|
{
|
||||||
|
private const int SIGSEGV = 11;
|
||||||
private const int SA_SIGINFO = 0x00000004;
|
private const int SA_SIGINFO = 0x00000004;
|
||||||
|
|
||||||
[DllImport("libc", SetLastError = true)]
|
[DllImport("libc", SetLastError = true)]
|
||||||
|
@ -39,7 +39,7 @@ namespace ARMeilleure.Signal
|
||||||
|
|
||||||
sigemptyset(ref sig.sa_mask);
|
sigemptyset(ref sig.sa_mask);
|
||||||
|
|
||||||
int result = sigaction((int)Signum.SIGSEGV, ref sig, out SigAction old);
|
int result = sigaction(SIGSEGV, ref sig, out SigAction old);
|
||||||
|
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,7 @@ namespace ARMeilleure.Signal
|
||||||
|
|
||||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||||
{
|
{
|
||||||
return sigaction((int)Signum.SIGSEGV, ref oldAction, out SigAction _) == 0;
|
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace ARMeilleure.Translation.Cache
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(PageSize));
|
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(PageSize));
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 2721; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 2908; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
|
@ -960,10 +960,10 @@ namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
uint osPlatform = 0u;
|
uint osPlatform = 0u;
|
||||||
|
|
||||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ? 1u : 0u) << 0;
|
osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0;
|
||||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 1u : 0u) << 1;
|
osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1;
|
||||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1u : 0u) << 2;
|
osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2;
|
||||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1u : 0u) << 3;
|
osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3;
|
||||||
|
|
||||||
return osPlatform;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
|
||||||
|
|
||||||
If you wish to build the emulator yourself you will need to:
|
If you wish to build the emulator yourself you will need to:
|
||||||
|
|
||||||
**Step one:** Install the X64 version of [.NET 5.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/5.0).
|
**Step one:** Install the X64 version of [.NET 6.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/6.0).
|
||||||
|
|
||||||
**Step two (choose one):**
|
**Step two (choose one):**
|
||||||
**(Variant one)**
|
**(Variant one)**
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||||
{
|
{
|
||||||
if (channelCount == 0)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -73,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
throw new ArgumentException($"{channelCount}");
|
throw new ArgumentException($"{channelCount}");
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||||
|
|
||||||
_sessions.TryAdd(session, 0);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
|
|
||||||
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||||
|
@ -27,6 +27,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
_targetFormat = GetALFormat();
|
_targetFormat = GetALFormat();
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
_playedSampleCount = 0;
|
_playedSampleCount = 0;
|
||||||
|
SetVolume(requestedVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ALFormat GetALFormat()
|
private ALFormat GetALFormat()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
return _pauseEvent;
|
return _pauseEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||||
{
|
{
|
||||||
if (channelCount == 0)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||||
|
|
||||||
_sessions.TryAdd(session, 0);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
private float _volume;
|
private float _volume;
|
||||||
private ushort _nativeSampleFormat;
|
private ushort _nativeSampleFormat;
|
||||||
|
|
||||||
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
|
@ -37,16 +37,18 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||||
_sampleCount = uint.MaxValue;
|
_sampleCount = uint.MaxValue;
|
||||||
_started = false;
|
_started = false;
|
||||||
_volume = 1.0f;
|
_volume = requestedVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||||
{
|
{
|
||||||
bool needAudioSetup = _outputStream == 0 || ((uint)GetSampleCount(buffer) % _sampleCount) != 0;
|
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||||
|
bool needAudioSetup = _outputStream == 0 ||
|
||||||
|
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||||
|
|
||||||
if (needAudioSetup)
|
if (needAudioSetup)
|
||||||
{
|
{
|
||||||
_sampleCount = Math.Max(Constants.TargetSampleCount, (uint)GetSampleCount(buffer));
|
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
|
||||||
|
|
||||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
|
|
||||||
if (frameCount == 0)
|
if (frameCount == 0)
|
||||||
{
|
{
|
||||||
// SDL2 left the responsability to the user to clear the buffer.
|
// SDL2 left the responsibility to the user to clear the buffer.
|
||||||
streamSpan.Fill(0);
|
streamSpan.Fill(0);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -92,11 +94,16 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
|
|
||||||
_ringBuffer.Read(samples, 0, samples.Length);
|
_ringBuffer.Read(samples, 0, samples.Length);
|
||||||
|
|
||||||
samples.AsSpan().CopyTo(streamSpan);
|
fixed (byte* p = samples)
|
||||||
streamSpan.Slice(samples.Length).Fill(0);
|
{
|
||||||
|
IntPtr pStreamSrc = (IntPtr)p;
|
||||||
|
|
||||||
// Apply volume to written data
|
// Zero the dest buffer
|
||||||
SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
streamSpan.Fill(0);
|
||||||
|
|
||||||
|
// Apply volume to written data
|
||||||
|
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
||||||
|
}
|
||||||
|
|
||||||
ulong sampleCount = GetSampleCount(samples.Length);
|
ulong sampleCount = GetSampleCount(samples.Length);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
return _pauseEvent;
|
return _pauseEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||||
{
|
{
|
||||||
if (channelCount == 0)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -142,12 +142,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
volume = Math.Clamp(volume, 0, 1);
|
||||||
|
|
||||||
if (direction != Direction.Output)
|
if (direction != Direction.Output)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||||
|
|
||||||
_sessions.TryAdd(session, 0);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
private ManualResetEvent _updateRequiredEvent;
|
private ManualResetEvent _updateRequiredEvent;
|
||||||
private int _disposeState;
|
private int _disposeState;
|
||||||
|
|
||||||
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||||
_ringBuffer = new DynamicRingBuffer();
|
_ringBuffer = new DynamicRingBuffer();
|
||||||
|
|
||||||
SetupOutputStream();
|
SetupOutputStream(requestedVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupOutputStream()
|
private void SetupOutputStream(float requestedVolume)
|
||||||
{
|
{
|
||||||
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
||||||
_outputStream.WriteCallback += Update;
|
_outputStream.WriteCallback += Update;
|
||||||
|
_outputStream.Volume = requestedVolume;
|
||||||
// TODO: Setup other callbacks (errors, ect).
|
// TODO: Setup other callbacks (errors, ect).
|
||||||
|
|
||||||
_outputStream.Open();
|
_outputStream.Open();
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
<TargetPath>libsoundio.dll</TargetPath>
|
|
||||||
</ContentWithTargetPath>
|
|
||||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dylib">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
<TargetPath>libsoundio.dylib</TargetPath>
|
|
||||||
</ContentWithTargetPath>
|
|
||||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.so">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
<TargetPath>libsoundio.so</TargetPath>
|
|
||||||
</ContentWithTargetPath>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||||
{
|
{
|
||||||
if (channelCount == 0)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -80,6 +80,8 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
volume = Math.Clamp(volume, 0, 1);
|
||||||
|
|
||||||
if (!_realDriver.SupportsDirection(direction))
|
if (!_realDriver.SupportsDirection(direction))
|
||||||
{
|
{
|
||||||
if (direction == Direction.Input)
|
if (direction == Direction.Input)
|
||||||
|
@ -94,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
|
|
||||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
||||||
|
|
||||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount);
|
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount, volume);
|
||||||
|
|
||||||
if (hardwareChannelCount == channelCount)
|
if (hardwareChannelCount == channelCount)
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
_pauseEvent = new ManualResetEvent(true);
|
_pauseEvent = new ManualResetEvent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||||
{
|
{
|
||||||
if (sampleRate == 0)
|
if (sampleRate == 0)
|
||||||
{
|
{
|
||||||
|
@ -49,7 +49,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
|
|
||||||
if (direction == Direction.Output)
|
if (direction == Direction.Output)
|
||||||
{
|
{
|
||||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
|
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
|
|
||||||
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||||
{
|
{
|
||||||
_volume = 1.0f;
|
_volume = requestedVolume;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ namespace Ryujinx.Audio.Common
|
||||||
_bufferAppendedCount = 0;
|
_bufferAppendedCount = 0;
|
||||||
_bufferRegisteredCount = 0;
|
_bufferRegisteredCount = 0;
|
||||||
_bufferReleasedCount = 0;
|
_bufferReleasedCount = 0;
|
||||||
_volume = 1.0f;
|
_volume = deviceSession.GetVolume();
|
||||||
_state = AudioDeviceState.Stopped;
|
_state = AudioDeviceState.Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ namespace Ryujinx.Audio.Common
|
||||||
|
|
||||||
for (int i = 0; i < buffersToFlush.Length; i++)
|
for (int i = 0; i < buffersToFlush.Length; i++)
|
||||||
{
|
{
|
||||||
buffersToFlush[i] = _buffers[_hardwareBufferIndex];
|
buffersToFlush[i] = _buffers[hardwareBufferIndex];
|
||||||
|
|
||||||
_bufferAppendedCount--;
|
_bufferAppendedCount--;
|
||||||
_bufferRegisteredCount++;
|
_bufferRegisteredCount++;
|
||||||
|
|
|
@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Integration
|
||||||
|
|
||||||
private byte[] _buffer;
|
private byte[] _buffer;
|
||||||
|
|
||||||
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
|
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
|
||||||
{
|
{
|
||||||
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
|
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
|
||||||
_channelCount = channelCount;
|
_channelCount = channelCount;
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
_currentBufferTag = 0;
|
_currentBufferTag = 0;
|
||||||
|
@ -56,6 +56,16 @@ namespace Ryujinx.Audio.Integration
|
||||||
_currentBufferTag = _currentBufferTag % 4;
|
_currentBufferTag = _currentBufferTag % 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
_session.SetVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
return _session.GetVolume();
|
||||||
|
}
|
||||||
|
|
||||||
public uint GetChannelCount()
|
public uint GetChannelCount()
|
||||||
{
|
{
|
||||||
return _channelCount;
|
return _channelCount;
|
||||||
|
|
|
@ -25,6 +25,18 @@ namespace Ryujinx.Audio.Integration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHardwareDevice : IDisposable
|
public interface IHardwareDevice : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the volume level for this device.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volume">The volume level to set.</param>
|
||||||
|
void SetVolume(float volume);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume level for this device.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The volume level of this device.</returns>
|
||||||
|
float GetVolume();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the supported sample rate of this device.
|
/// Get the supported sample rate of this device.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace Ryujinx.Audio.Integration
|
||||||
Output
|
Output
|
||||||
}
|
}
|
||||||
|
|
||||||
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
|
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
|
||||||
|
|
||||||
ManualResetEvent GetUpdateRequiredEvent();
|
ManualResetEvent GetUpdateRequiredEvent();
|
||||||
ManualResetEvent GetPauseEvent();
|
ManualResetEvent GetPauseEvent();
|
||||||
|
|
|
@ -208,13 +208,14 @@ namespace Ryujinx.Audio.Output
|
||||||
SampleFormat sampleFormat,
|
SampleFormat sampleFormat,
|
||||||
ref AudioInputConfiguration parameter,
|
ref AudioInputConfiguration parameter,
|
||||||
ulong appletResourceUserId,
|
ulong appletResourceUserId,
|
||||||
uint processHandle)
|
uint processHandle,
|
||||||
|
float volume)
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
_sessionsBufferEvents[sessionId].Clear();
|
_sessionsBufferEvents[sessionId].Clear();
|
||||||
|
|
||||||
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
|
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
|
||||||
|
|
||||||
AudioOutputSystem audioOut = new AudioOutputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
|
AudioOutputSystem audioOut = new AudioOutputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
|
||||||
|
|
||||||
|
@ -247,6 +248,41 @@ namespace Ryujinx.Audio.Output
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the volume for all output devices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volume">The volume to set.</param>
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
if (_sessions != null)
|
||||||
|
{
|
||||||
|
foreach (AudioOutputSystem session in _sessions)
|
||||||
|
{
|
||||||
|
session?.SetVolume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume for all output devices.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A float indicating the volume level.</returns>
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
if (_sessions != null)
|
||||||
|
{
|
||||||
|
foreach (AudioOutputSystem session in _sessions)
|
||||||
|
{
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
return session.GetVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||||
|
|
|
@ -78,7 +78,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(IHardwareDeviceDriver deviceDriver)
|
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
||||||
{
|
{
|
||||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
for (int i = 0; i < OutputDevices.Length; i++)
|
for (int i = 0; i < OutputDevices.Length; i++)
|
||||||
{
|
{
|
||||||
// TODO: Don't hardcode sample rate.
|
// TODO: Don't hardcode sample rate.
|
||||||
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
|
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mailbox = new Mailbox<MailboxMessage>();
|
_mailbox = new Mailbox<MailboxMessage>();
|
||||||
|
@ -245,6 +245,33 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
_mailbox.SendResponse(MailboxMessage.Stop);
|
_mailbox.SendResponse(MailboxMessage.Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
if (OutputDevices != null)
|
||||||
|
{
|
||||||
|
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||||
|
{
|
||||||
|
if (outputDevice != null)
|
||||||
|
{
|
||||||
|
return outputDevice.GetVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
if (OutputDevices != null)
|
||||||
|
{
|
||||||
|
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||||
|
{
|
||||||
|
outputDevice?.SetVolume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
|
|
@ -186,12 +186,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartLocked()
|
private void StartLocked(float volume)
|
||||||
{
|
{
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
|
|
||||||
// TODO: virtual device mapping (IAudioDevice)
|
// TODO: virtual device mapping (IAudioDevice)
|
||||||
Processor.Start(_deviceDriver);
|
Processor.Start(_deviceDriver, volume);
|
||||||
|
|
||||||
_workerThread = new Thread(SendCommands)
|
_workerThread = new Thread(SendCommands)
|
||||||
{
|
{
|
||||||
|
@ -263,7 +263,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||||
private void Register(AudioRenderSystem renderer)
|
private void Register(AudioRenderSystem renderer, float volume)
|
||||||
{
|
{
|
||||||
lock (_sessionLock)
|
lock (_sessionLock)
|
||||||
{
|
{
|
||||||
|
@ -274,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
if (!_isRunning)
|
if (!_isRunning)
|
||||||
{
|
{
|
||||||
StartLocked();
|
StartLocked(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,7 +314,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <param name="workBufferSize">The guest work buffer size.</param>
|
/// <param name="workBufferSize">The guest work buffer size.</param>
|
||||||
/// <param name="processHandle">The process handle of the application.</param>
|
/// <param name="processHandle">The process handle of the application.</param>
|
||||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
|
/// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
|
||||||
public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle)
|
public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle, float volume)
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
renderer = audioRenderer;
|
renderer = audioRenderer;
|
||||||
|
|
||||||
Register(renderer);
|
Register(renderer, volume);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -338,6 +338,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
if (Processor != null)
|
||||||
|
{
|
||||||
|
return Processor.GetVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
Processor?.SetVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||||
|
|
|
@ -76,6 +76,17 @@ namespace Ryujinx.Audio.Renderer.Utils
|
||||||
_stream.Flush();
|
_stream.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
// Do nothing, volume is not used for FileHardwareDevice at the moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
// FileHardwareDevice does not incorporate volume.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public uint GetChannelCount()
|
public uint GetChannelCount()
|
||||||
{
|
{
|
||||||
return _channelCount;
|
return _channelCount;
|
||||||
|
|
|
@ -37,6 +37,17 @@ namespace Ryujinx.Audio.Renderer.Utils
|
||||||
_secondaryDevice?.AppendBuffer(data, channelCount);
|
_secondaryDevice?.AppendBuffer(data, channelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
_baseDevice.SetVolume(volume);
|
||||||
|
_secondaryDevice.SetVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
return _baseDevice.GetVolume();
|
||||||
|
}
|
||||||
|
|
||||||
public uint GetChannelCount()
|
public uint GetChannelCount()
|
||||||
{
|
{
|
||||||
return _baseDevice.GetChannelCount();
|
return _baseDevice.GetChannelCount();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
public Key Screenshot { get; set; }
|
public Key Screenshot { get; set; }
|
||||||
public Key ShowUi { get; set; }
|
public Key ShowUi { get; set; }
|
||||||
public Key Pause { get; set; }
|
public Key Pause { get; set; }
|
||||||
|
public Key ToggleMute { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="5.0.1" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Management" Version="5.0.0" />
|
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace Ryujinx.Common.System
|
||||||
|
|
||||||
static public void Prevent()
|
static public void Prevent()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED);
|
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace Ryujinx.Common.System
|
||||||
|
|
||||||
static public void Restore()
|
static public void Restore()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Common.System
|
namespace Ryujinx.Common.System
|
||||||
{
|
{
|
||||||
|
@ -19,7 +20,7 @@ namespace Ryujinx.Common.System
|
||||||
public static void Windows()
|
public static void Windows()
|
||||||
{
|
{
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
// Make process DPI aware for proper window sizing on high-res screens.
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 6)
|
if (OperatingSystem.IsWindowsVersionAtLeast(6))
|
||||||
{
|
{
|
||||||
SetProcessDPIAware();
|
SetProcessDPIAware();
|
||||||
}
|
}
|
||||||
|
@ -27,16 +28,22 @@ namespace Ryujinx.Common.System
|
||||||
|
|
||||||
public static double GetWindowScaleFactor()
|
public static double GetWindowScaleFactor()
|
||||||
{
|
{
|
||||||
double userDpiScale;
|
double userDpiScale = 96.0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Linux support
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||||
userDpiScale = 96.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Common.System
|
namespace Ryujinx.Common.System
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handle Windows Multimedia timer resolution.
|
/// Handle Windows Multimedia timer resolution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
public class WindowsMultimediaTimerResolution : IDisposable
|
public class WindowsMultimediaTimerResolution : IDisposable
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Pools;
|
using Ryujinx.Common.Pools;
|
||||||
using Ryujinx.Graphics.Device;
|
using Ryujinx.Graphics.Device;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
|
@ -99,11 +100,32 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases a semaphore for a given LaunchDma method call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">The LaunchDma call argument</param>
|
||||||
|
private void ReleaseSemaphore(int argument)
|
||||||
|
{
|
||||||
|
LaunchDmaSemaphoreType type = (LaunchDmaSemaphoreType)((argument >> 3) & 0x3);
|
||||||
|
if (type != LaunchDmaSemaphoreType.None)
|
||||||
|
{
|
||||||
|
ulong address = ((ulong)_state.State.SetSemaphoreA << 32) | _state.State.SetSemaphoreB;
|
||||||
|
if (type == LaunchDmaSemaphoreType.ReleaseOneWordSemaphore)
|
||||||
|
{
|
||||||
|
_channel.MemoryManager.Write(address, _state.State.SetSemaphorePayload);
|
||||||
|
}
|
||||||
|
else /* if (type == LaunchDmaSemaphoreType.ReleaseFourWordSemaphore) */
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu, "DMA semaphore type ReleaseFourWordSemaphore was used, but is not currently implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a buffer to buffer, or buffer to texture copy.
|
/// Performs a buffer to buffer, or buffer to texture copy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argument">Method call argument</param>
|
/// <param name="argument">The LaunchDma call argument</param>
|
||||||
private void LaunchDma(int argument)
|
private void DmaCopy(int argument)
|
||||||
{
|
{
|
||||||
var memoryManager = _channel.MemoryManager;
|
var memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
|
@ -200,6 +222,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||||
target.Info.Height,
|
target.Info.Height,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
xCount * srcBpp,
|
||||||
srcStride,
|
srcStride,
|
||||||
target.Info.FormatInfo.BytesPerPixel,
|
target.Info.FormatInfo.BytesPerPixel,
|
||||||
srcSpan);
|
srcSpan);
|
||||||
|
@ -322,5 +345,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
private void LaunchDma(int argument)
|
||||||
|
{
|
||||||
|
DmaCopy(argument);
|
||||||
|
ReleaseSemaphore(argument);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,9 +99,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||||
_isLinear = (argument & 1) != 0;
|
_isLinear = (argument & 1) != 0;
|
||||||
|
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
_size = (int)(state.LineLengthIn * state.LineCount);
|
_size = (int)(BitUtils.AlignUp(state.LineLengthIn, 4) * state.LineCount);
|
||||||
|
|
||||||
int count = BitUtils.DivRoundUp(_size, 4);
|
int count = _size / 4;
|
||||||
|
|
||||||
if (_buffer == null || _buffer.Length < count)
|
if (_buffer == null || _buffer.Length < count)
|
||||||
{
|
{
|
||||||
|
@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||||
|
|
||||||
if (_isLinear && _lineCount == 1)
|
if (_isLinear && _lineCount == 1)
|
||||||
{
|
{
|
||||||
memoryManager.WriteTrackedResource(_dstGpuVa, data);
|
memoryManager.WriteTrackedResource(_dstGpuVa, data.Slice(0, _lineLengthIn));
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -224,6 +224,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||||
|
|
||||||
memoryManager.Write(dstAddress, data[srcOffset]);
|
memoryManager.Write(dstAddress, data[srcOffset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All lines must be aligned to 4 bytes, as the data is pushed one word at a time.
|
||||||
|
// If our copy length is not a multiple of 4, then we need to skip the padding bytes here.
|
||||||
|
int misalignment = _lineLengthIn & 3;
|
||||||
|
|
||||||
|
if (misalignment != 0)
|
||||||
|
{
|
||||||
|
srcOffset += 4 - misalignment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
|
|
|
@ -64,7 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
nameof(ThreedClassState.ShaderState)),
|
nameof(ThreedClassState.ShaderState)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||||
new StateUpdateCallbackEntry(UpdateScissorState, nameof(ThreedClassState.ScissorState)),
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||||
|
nameof(ThreedClassState.ScissorState),
|
||||||
|
nameof(ThreedClassState.ScreenScissorState)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
||||||
nameof(ThreedClassState.VertexBufferDrawState),
|
nameof(ThreedClassState.VertexBufferDrawState),
|
||||||
|
@ -426,6 +429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
int width = scissor.X2 - x;
|
int width = scissor.X2 - x;
|
||||||
int height = scissor.Y2 - y;
|
int height = scissor.Y2 - y;
|
||||||
|
|
||||||
|
if (_state.State.YControl.HasFlag(YControl.NegateY))
|
||||||
|
{
|
||||||
|
ref var screenScissor = ref _state.State.ScreenScissorState;
|
||||||
|
y = screenScissor.Height - height - y;
|
||||||
|
|
||||||
|
if (y < 0)
|
||||||
|
{
|
||||||
|
height += y;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float scale = _channel.TextureManager.RenderTargetScale;
|
float scale = _channel.TextureManager.RenderTargetScale;
|
||||||
if (scale != 1f)
|
if (scale != 1f)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||||
{ nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, null) },
|
{ nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, null) },
|
||||||
{ nameof(ThreedClassState.SyncpointAction), new RwCallback(IncrementSyncpoint, null) },
|
{ nameof(ThreedClassState.SyncpointAction), new RwCallback(IncrementSyncpoint, null) },
|
||||||
|
{ nameof(ThreedClassState.InvalidateSamplerCacheNoWfi), new RwCallback(InvalidateSamplerCacheNoWfi, null) },
|
||||||
|
{ nameof(ThreedClassState.InvalidateTextureHeaderCacheNoWfi), new RwCallback(InvalidateTextureHeaderCacheNoWfi, null) },
|
||||||
{ nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) },
|
{ nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) },
|
||||||
{ nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) },
|
{ nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) },
|
||||||
{ nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) },
|
{ nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) },
|
||||||
|
@ -227,6 +229,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalidates the cache with the sampler descriptors from the sampler pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument (unused)</param>
|
||||||
|
private void InvalidateSamplerCacheNoWfi(int argument)
|
||||||
|
{
|
||||||
|
_context.AdvanceSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalidates the cache with the texture descriptors from the texture pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument (unused)</param>
|
||||||
|
private void InvalidateTextureHeaderCacheNoWfi(int argument)
|
||||||
|
{
|
||||||
|
_context.AdvanceSequence();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Issues a texture barrier.
|
/// Issues a texture barrier.
|
||||||
/// This waits until previous texture writes from the GPU to finish, before
|
/// This waits until previous texture writes from the GPU to finish, before
|
||||||
|
|
|
@ -784,7 +784,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
public YControl YControl;
|
public YControl YControl;
|
||||||
public float LineWidthSmooth;
|
public float LineWidthSmooth;
|
||||||
public float LineWidthAliased;
|
public float LineWidthAliased;
|
||||||
public fixed uint Reserved13B8[31];
|
public fixed uint Reserved13B8[27];
|
||||||
|
public uint InvalidateSamplerCacheNoWfi;
|
||||||
|
public uint InvalidateTextureHeaderCacheNoWfi;
|
||||||
|
public fixed uint Reserved142C[2];
|
||||||
public uint FirstVertex;
|
public uint FirstVertex;
|
||||||
public uint FirstInstance;
|
public uint FirstInstance;
|
||||||
public fixed uint Reserved143C[53];
|
public fixed uint Reserved143C[53];
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
@ -9,8 +8,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Sampler : IDisposable
|
class Sampler : IDisposable
|
||||||
{
|
{
|
||||||
private const int MinLevelsForAnisotropic = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host sampler object.
|
/// Host sampler object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -96,26 +93,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <returns>A host sampler</returns>
|
/// <returns>A host sampler</returns>
|
||||||
public ISampler GetHostSampler(Texture texture)
|
public ISampler GetHostSampler(Texture texture)
|
||||||
{
|
{
|
||||||
return _anisoSampler != null && AllowForceAnisotropy(texture) ? _anisoSampler : _hostSampler;
|
return _anisoSampler != null && texture?.CanForceAnisotropy == true ? _anisoSampler : _hostSampler;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine if the given texture can have anisotropic filtering forced.
|
|
||||||
/// Filtered textures that we might want to force anisotropy on should have a lot of mip levels.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="texture">The texture</param>
|
|
||||||
/// <returns>True if anisotropic filtering can be forced, false otherwise</returns>
|
|
||||||
private static bool AllowForceAnisotropy(Texture texture)
|
|
||||||
{
|
|
||||||
if (texture == null || !(texture.Target == Target.Texture2D || texture.Target == Target.Texture2DArray))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxSize = Math.Max(texture.Info.Width, texture.Info.Height);
|
|
||||||
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
|
|
||||||
|
|
||||||
return texture.Info.Levels >= Math.Min(MinLevelsForAnisotropic, maxLevels);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
@ -24,6 +25,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
// This method uses much more memory so we want to avoid it if possible.
|
// This method uses much more memory so we want to avoid it if possible.
|
||||||
private const int ByteComparisonSwitchThreshold = 4;
|
private const int ByteComparisonSwitchThreshold = 4;
|
||||||
|
|
||||||
|
private const int MinLevelsForForceAnisotropy = 5;
|
||||||
|
|
||||||
private struct TexturePoolOwner
|
private struct TexturePoolOwner
|
||||||
{
|
{
|
||||||
public TexturePool Pool;
|
public TexturePool Pool;
|
||||||
|
@ -50,6 +53,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureInfo Info { get; private set; }
|
public TextureInfo Info { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set when anisotropic filtering can be forced on the given texture.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanForceAnisotropy { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host scale factor.
|
/// Host scale factor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -759,6 +767,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
Info.FormatInfo.BlockWidth,
|
Info.FormatInfo.BlockWidth,
|
||||||
Info.FormatInfo.BlockHeight,
|
Info.FormatInfo.BlockHeight,
|
||||||
Info.Stride,
|
Info.Stride,
|
||||||
|
Info.Stride,
|
||||||
Info.FormatInfo.BytesPerPixel,
|
Info.FormatInfo.BytesPerPixel,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
@ -1142,6 +1151,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if this texture can have anisotropic filtering forced.
|
||||||
|
/// Filtered textures that we might want to force anisotropy on should have a lot of mip levels.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if anisotropic filtering can be forced, false otherwise</returns>
|
||||||
|
private bool CanTextureForceAnisotropy()
|
||||||
|
{
|
||||||
|
if (!(Target == Target.Texture2D || Target == Target.Texture2DArray))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxSize = Math.Max(Info.Width, Info.Height);
|
||||||
|
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
|
||||||
|
|
||||||
|
return Info.Levels >= Math.Min(MinLevelsForForceAnisotropy, maxLevels);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if this texture and the specified target have the same number of dimensions.
|
/// Check if this texture and the specified target have the same number of dimensions.
|
||||||
/// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have
|
/// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have
|
||||||
|
@ -1232,6 +1259,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
Info = info;
|
Info = info;
|
||||||
Target = info.Target;
|
Target = info.Target;
|
||||||
|
CanForceAnisotropy = CanTextureForceAnisotropy();
|
||||||
|
|
||||||
_depth = info.GetDepth();
|
_depth = info.GetDepth();
|
||||||
_layers = info.GetLayers();
|
_layers = info.GetLayers();
|
||||||
|
|
|
@ -377,18 +377,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
|
|
||||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
|
||||||
{
|
|
||||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
|
||||||
{
|
|
||||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
|
||||||
}
|
|
||||||
|
|
||||||
_textureState[stageIndex][index].Texture = hostTexture;
|
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||||
{
|
{
|
||||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||||
|
@ -396,16 +384,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
Sampler sampler = samplerPool?.Get(samplerId);
|
|
||||||
|
|
||||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
|
||||||
|
|
||||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
|
||||||
{
|
{
|
||||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||||
|
{
|
||||||
|
if (UpdateScale(texture, bindingInfo, index, stage))
|
||||||
|
{
|
||||||
|
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
_textureState[stageIndex][index].Texture = hostTexture;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sampler sampler = samplerPool?.Get(samplerId);
|
||||||
|
|
||||||
|
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||||
|
|
||||||
|
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
||||||
|
{
|
||||||
|
_textureState[stageIndex][index].Sampler = hostSampler;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,28 +466,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||||
}
|
}
|
||||||
else if (isStore)
|
else
|
||||||
{
|
{
|
||||||
texture?.SignalModified();
|
if (isStore)
|
||||||
}
|
|
||||||
|
|
||||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
|
||||||
{
|
|
||||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
|
||||||
{
|
{
|
||||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
texture?.SignalModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageState[stageIndex][index].Texture = hostTexture;
|
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||||
|
|
||||||
Format format = bindingInfo.Format;
|
|
||||||
|
|
||||||
if (format == 0 && texture != null)
|
|
||||||
{
|
{
|
||||||
format = texture.Format;
|
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
||||||
}
|
{
|
||||||
|
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
_imageState[stageIndex][index].Texture = hostTexture;
|
||||||
|
|
||||||
|
Format format = bindingInfo.Format;
|
||||||
|
|
||||||
|
if (format == 0 && texture != null)
|
||||||
|
{
|
||||||
|
format = texture.Format;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -14,4 +14,8 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main storage of the cache collection.
|
/// Main storage of the cache collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ZipArchive _cacheArchive;
|
private ZipFile _cacheArchive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the cache collection supports modification.
|
/// Indicates if the cache collection supports modification.
|
||||||
|
@ -324,7 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
EnsureArchiveUpToDate();
|
EnsureArchiveUpToDate();
|
||||||
|
|
||||||
// Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
|
// Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
|
||||||
_cacheArchive = ZipFile.Open(GetArchivePath(), ZipArchiveMode.Read);
|
_cacheArchive = new ZipFile(File.OpenRead(GetArchivePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -336,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
// First close previous opened instance if found.
|
// First close previous opened instance if found.
|
||||||
if (_cacheArchive != null)
|
if (_cacheArchive != null)
|
||||||
{
|
{
|
||||||
_cacheArchive.Dispose();
|
_cacheArchive.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
string archivePath = GetArchivePath();
|
string archivePath = GetArchivePath();
|
||||||
|
@ -355,8 +355,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(archivePath))
|
||||||
|
{
|
||||||
|
using (ZipFile newZip = ZipFile.Create(archivePath))
|
||||||
|
{
|
||||||
|
// Workaround for SharpZipLib issue #395
|
||||||
|
newZip.BeginUpdate();
|
||||||
|
newZip.CommitUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open the zip in read/write.
|
// Open the zip in read/write.
|
||||||
_cacheArchive = ZipFile.Open(archivePath, ZipArchiveMode.Update);
|
_cacheArchive = new ZipFile(File.Open(archivePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
|
Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
|
||||||
|
|
||||||
|
@ -366,7 +376,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
||||||
|
|
||||||
// Close the instance to force a flush.
|
// Close the instance to force a flush.
|
||||||
_cacheArchive.Dispose();
|
_cacheArchive.Close();
|
||||||
_cacheArchive = null;
|
_cacheArchive = null;
|
||||||
|
|
||||||
string cacheTempDataPath = GetCacheTempDataPath();
|
string cacheTempDataPath = GetCacheTempDataPath();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
@ -9,7 +10,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
@ -192,19 +192,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <param name="entry">The given hash</param>
|
/// <param name="entry">The given hash</param>
|
||||||
/// <returns>The cached file if present or null</returns>
|
/// <returns>The cached file if present or null</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static byte[] ReadFromArchive(ZipArchive archive, Hash128 entry)
|
public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry archiveEntry = archive.GetEntry($"{entry}");
|
ZipEntry archiveEntry = archive.GetEntry($"{entry}");
|
||||||
|
|
||||||
if (archiveEntry != null)
|
if (archiveEntry != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] result = new byte[archiveEntry.Length];
|
byte[] result = new byte[archiveEntry.Size];
|
||||||
|
|
||||||
using (Stream archiveStream = archiveEntry.Open())
|
using (Stream archiveStream = archive.GetInputStream(archiveEntry))
|
||||||
{
|
{
|
||||||
archiveStream.Read(result);
|
archiveStream.Read(result);
|
||||||
|
|
||||||
|
@ -538,8 +538,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <param name="archive">The archive to use</param>
|
/// <param name="archive">The archive to use</param>
|
||||||
/// <param name="entries">The entries in the cache</param>
|
/// <param name="entries">The entries in the cache</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipArchive archive, HashSet<Hash128> entries)
|
public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
|
||||||
{
|
{
|
||||||
|
List<string> filesToDelete = new List<string>();
|
||||||
|
|
||||||
|
archive.BeginUpdate();
|
||||||
|
|
||||||
foreach (Hash128 hash in entries)
|
foreach (Hash128 hash in entries)
|
||||||
{
|
{
|
||||||
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
||||||
|
@ -548,15 +552,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
{
|
{
|
||||||
string cacheHash = $"{hash}";
|
string cacheHash = $"{hash}";
|
||||||
|
|
||||||
ZipArchiveEntry entry = archive.GetEntry(cacheHash);
|
ZipEntry entry = archive.GetEntry(cacheHash);
|
||||||
|
|
||||||
entry?.Delete();
|
if (entry != null)
|
||||||
|
{
|
||||||
|
archive.Delete(entry);
|
||||||
|
}
|
||||||
|
|
||||||
archive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
|
// We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
|
||||||
|
archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
|
||||||
File.Delete(cacheTempFilePath);
|
filesToDelete.Add(cacheTempFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
archive.CommitUpdate();
|
||||||
|
|
||||||
|
foreach (string filePath in filesToDelete)
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsArchiveReadOnly(string archivePath)
|
public static bool IsArchiveReadOnly(string archivePath)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
{
|
{
|
||||||
|
@ -35,27 +35,36 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StreamZipEntryDataSource : IStaticDataSource
|
||||||
|
{
|
||||||
|
private readonly ZipFile Archive;
|
||||||
|
private readonly ZipEntry Entry;
|
||||||
|
public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
|
||||||
|
{
|
||||||
|
Archive = archive;
|
||||||
|
Entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetSource()
|
||||||
|
{
|
||||||
|
return Archive.GetInputStream(Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move a file with the name of a given hash to another in the cache archive.
|
/// Move a file with the name of a given hash to another in the cache archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The archive in use</param>
|
/// <param name="archive">The archive in use</param>
|
||||||
/// <param name="oldKey">The old key</param>
|
/// <param name="oldKey">The old key</param>
|
||||||
/// <param name="newKey">The new key</param>
|
/// <param name="newKey">The new key</param>
|
||||||
private static void MoveEntry(ZipArchive archive, Hash128 oldKey, Hash128 newKey)
|
private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
||||||
|
|
||||||
if (oldGuestEntry != null)
|
if (oldGuestEntry != null)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry newGuestEntry = archive.CreateEntry($"{newKey}");
|
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
|
||||||
|
archive.Delete(oldGuestEntry);
|
||||||
using (Stream oldStream = oldGuestEntry.Open())
|
|
||||||
using (Stream newStream = newGuestEntry.Open())
|
|
||||||
{
|
|
||||||
oldStream.CopyTo(newStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldGuestEntry.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,8 +90,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||||
|
|
||||||
ZipArchive guestArchive = ZipFile.Open(guestArchivePath, ZipArchiveMode.Update);
|
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||||
ZipArchive hostArchive = ZipFile.Open(hostArchivePath, ZipArchiveMode.Update);
|
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||||
|
|
||||||
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
||||||
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
||||||
|
@ -129,8 +138,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
||||||
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
||||||
|
|
||||||
guestArchive.Dispose();
|
guestArchive.CommitUpdate();
|
||||||
hostArchive.Dispose();
|
hostArchive.CommitUpdate();
|
||||||
|
|
||||||
|
guestArchive.Close();
|
||||||
|
hostArchive.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the codegen (to be changed when codegen or guest format change).
|
/// Version of the codegen (to be changed when codegen or guest format change).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const ulong ShaderCodeGenVersion = 2816;
|
private const ulong ShaderCodeGenVersion = 2885;
|
||||||
|
|
||||||
// Progress reporting helpers
|
// Progress reporting helpers
|
||||||
private volatile int _shaderCount;
|
private volatile int _shaderCount;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
public void SetStorage(BufferRange buffer)
|
public void SetStorage(BufferRange buffer)
|
||||||
{
|
{
|
||||||
if (_buffer != BufferHandle.Null &&
|
if (_buffer != BufferHandle.Null &&
|
||||||
|
_buffer == buffer.Handle &&
|
||||||
buffer.Offset == _bufferOffset &&
|
buffer.Offset == _bufferOffset &&
|
||||||
buffer.Size == _bufferSize &&
|
buffer.Size == _bufferSize &&
|
||||||
_renderer.BufferCount == _bufferCount)
|
_renderer.BufferCount == _bufferCount)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
VariableType type = GetSrcVarType(operation.Inst, 0);
|
VariableType type = GetSrcVarType(operation.Inst, 0);
|
||||||
|
|
||||||
string srcExpr = GetSoureExpr(context, src, type);
|
string srcExpr = GetSoureExpr(context, src, type);
|
||||||
|
string zero;
|
||||||
|
|
||||||
NumberFormatter.TryFormat(0, type, out string zero);
|
if (type == VariableType.F64)
|
||||||
|
{
|
||||||
|
zero = "0.0";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NumberFormatter.TryFormat(0, type, out zero);
|
||||||
|
}
|
||||||
|
|
||||||
// Starting in the 496.13 NVIDIA driver, there's an issue with assigning variables to negated expressions.
|
// Starting in the 496.13 NVIDIA driver, there's an issue with assigning variables to negated expressions.
|
||||||
// (-expr) does not work, but (0.0 - expr) does. This should be removed once the issue is resolved.
|
// (-expr) does not work, but (0.0 - expr) does. This should be removed once the issue is resolved.
|
||||||
|
|
|
@ -71,8 +71,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
|
Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
|
||||||
Add(Instruction.FSIBegin, InstType.Special);
|
Add(Instruction.FSIBegin, InstType.Special);
|
||||||
Add(Instruction.FSIEnd, InstType.Special);
|
Add(Instruction.FSIEnd, InstType.Special);
|
||||||
Add(Instruction.FindFirstSetS32, InstType.CallUnary, "findMSB");
|
Add(Instruction.FindLSB, InstType.CallUnary, "findLSB");
|
||||||
Add(Instruction.FindFirstSetU32, InstType.CallUnary, "findMSB");
|
Add(Instruction.FindMSBS32, InstType.CallUnary, "findMSB");
|
||||||
|
Add(Instruction.FindMSBU32, InstType.CallUnary, "findMSB");
|
||||||
Add(Instruction.Floor, InstType.CallUnary, "floor");
|
Add(Instruction.Floor, InstType.CallUnary, "floor");
|
||||||
Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
|
Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
|
||||||
Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier");
|
Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier");
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
|
|
||||||
public static bool TryFormat(int value, VariableType dstType, out string formatted)
|
public static bool TryFormat(int value, VariableType dstType, out string formatted)
|
||||||
{
|
{
|
||||||
if (dstType == VariableType.F32 || dstType == VariableType.F64)
|
if (dstType == VariableType.F32)
|
||||||
{
|
{
|
||||||
return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted);
|
return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,69 +75,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
context.Config.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
|
context.Config.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DmnmxR(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDmnmxR op = context.GetOp<InstDmnmxR>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DmnmxR is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DmnmxI(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDmnmxI op = context.GetOp<InstDmnmxI>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DmnmxI is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DmnmxC(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDmnmxC op = context.GetOp<InstDmnmxC>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DmnmxC is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetR(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetR op = context.GetOp<InstDsetR>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetR is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetI(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetI op = context.GetOp<InstDsetI>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetI is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetC(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetC op = context.GetOp<InstDsetC>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetC is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetpR(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetpR op = context.GetOp<InstDsetpR>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetpR is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetpI(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetpI op = context.GetOp<InstDsetpI>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetpI is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DsetpC(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstDsetpC op = context.GetOp<InstDsetpC>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction DsetpC is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FchkR(EmitterContext context)
|
public static void FchkR(EmitterContext context)
|
||||||
{
|
{
|
||||||
InstFchkR op = context.GetOp<InstFchkR>();
|
InstFchkR op = context.GetOp<InstFchkR>();
|
||||||
|
|
|
@ -166,13 +166,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
Operand srcB = context.BitwiseNot(src, invert);
|
Operand srcB = context.BitwiseNot(src, invert);
|
||||||
|
|
||||||
Operand res = isSigned
|
Operand res;
|
||||||
? context.FindFirstSetS32(srcB)
|
|
||||||
: context.FindFirstSetU32(srcB);
|
|
||||||
|
|
||||||
if (sh)
|
if (sh)
|
||||||
{
|
{
|
||||||
res = context.BitwiseExclusiveOr(res, Const(31));
|
res = context.FindLSB(context.BitfieldReverse(srcB));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = isSigned
|
||||||
|
? context.FindMSBS32(srcB)
|
||||||
|
: context.FindMSBU32(srcB);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Copy(GetDest(rd), res);
|
context.Copy(GetDest(rd), res);
|
||||||
|
|
|
@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
var src = GetSrcReg(context, op.SrcB);
|
var src = GetSrcReg(context, op.SrcB);
|
||||||
|
|
||||||
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat);
|
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void I2iI(EmitterContext context)
|
public static void I2iI(EmitterContext context)
|
||||||
|
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
var src = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
var src = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
||||||
|
|
||||||
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat);
|
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void I2iC(EmitterContext context)
|
public static void I2iC(EmitterContext context)
|
||||||
|
@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
var src = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
var src = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||||
|
|
||||||
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat);
|
EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitF2F(
|
private static void EmitF2F(
|
||||||
|
@ -176,7 +176,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
if (dstType == IDstFmt.U64)
|
if (dstType == IDstFmt.U64)
|
||||||
{
|
{
|
||||||
context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
|
context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Instruction fpType = srcType.ToInstFPType();
|
Instruction fpType = srcType.ToInstFPType();
|
||||||
|
@ -198,7 +197,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
if (!isSignedInt)
|
if (!isSignedInt)
|
||||||
{
|
{
|
||||||
// Negative float to uint cast is undefined, so we clamp the value before conversion.
|
// Negative float to uint cast is undefined, so we clamp the value before conversion.
|
||||||
srcB = context.FPMaximum(srcB, ConstF(0), fpType);
|
Operand c0 = srcType == DstFmt.F64 ? context.PackDouble2x32(0.0) : ConstF(0);
|
||||||
|
|
||||||
|
srcB = context.FPMaximum(srcB, c0, fpType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (srcType == DstFmt.F64)
|
if (srcType == DstFmt.F64)
|
||||||
|
@ -292,7 +293,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
int rd,
|
int rd,
|
||||||
bool absolute,
|
bool absolute,
|
||||||
bool negate,
|
bool negate,
|
||||||
bool saturate)
|
bool saturate,
|
||||||
|
bool writeCC)
|
||||||
{
|
{
|
||||||
if ((srcType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32 || (dstType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32)
|
if ((srcType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32 || (dstType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32)
|
||||||
{
|
{
|
||||||
|
@ -337,7 +339,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
context.Copy(GetDest(rd), src);
|
context.Copy(GetDest(rd), src);
|
||||||
|
|
||||||
// TODO: CC.
|
SetZnFlags(context, src, writeCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operand UnpackReg(EmitterContext context, DstFmt floatType, bool h, int reg)
|
private static Operand UnpackReg(EmitterContext context, DstFmt floatType, bool h, int reg)
|
||||||
|
|
|
@ -528,18 +528,5 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
context.Copy(GetDest(rd), GetHalfPacked(context, swizzle, res, rd));
|
context.Copy(GetDest(rd), GetHalfPacked(context, swizzle, res, rd));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetDest(EmitterContext context, Operand value, int rd, bool isFP64)
|
|
||||||
{
|
|
||||||
if (isFP64)
|
|
||||||
{
|
|
||||||
context.Copy(GetDest(rd), context.UnpackDouble2x32Low(value));
|
|
||||||
context.Copy(GetDest2(rd), context.UnpackDouble2x32High(value));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Copy(GetDest(rd), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,6 +11,156 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
static partial class InstEmit
|
static partial class InstEmit
|
||||||
{
|
{
|
||||||
|
public static void DsetR(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetR op = context.GetOp<InstDsetR>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcReg(context, op.SrcB, isFP64: true);
|
||||||
|
|
||||||
|
EmitFset(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.Dest,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
op.BVal,
|
||||||
|
op.WriteCC,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DsetI(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetI op = context.GetOp<InstDsetI>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true);
|
||||||
|
|
||||||
|
EmitFset(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.Dest,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
op.BVal,
|
||||||
|
op.WriteCC,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DsetC(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetC op = context.GetOp<InstDsetC>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true);
|
||||||
|
|
||||||
|
EmitFset(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.Dest,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
op.BVal,
|
||||||
|
op.WriteCC,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DsetpR(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetpR op = context.GetOp<InstDsetpR>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcReg(context, op.SrcB, isFP64: true);
|
||||||
|
|
||||||
|
EmitFsetp(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.DestPred,
|
||||||
|
op.DestPredInv,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
writeCC: false,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DsetpI(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetpI op = context.GetOp<InstDsetpI>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true);
|
||||||
|
|
||||||
|
EmitFsetp(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.DestPred,
|
||||||
|
op.DestPredInv,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
writeCC: false,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DsetpC(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDsetpC op = context.GetOp<InstDsetpC>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true);
|
||||||
|
|
||||||
|
EmitFsetp(
|
||||||
|
context,
|
||||||
|
op.FComp,
|
||||||
|
op.Bop,
|
||||||
|
srcA,
|
||||||
|
srcB,
|
||||||
|
op.SrcPred,
|
||||||
|
op.SrcPredInv,
|
||||||
|
op.DestPred,
|
||||||
|
op.DestPredInv,
|
||||||
|
op.AbsA,
|
||||||
|
op.AbsB,
|
||||||
|
op.NegA,
|
||||||
|
op.NegB,
|
||||||
|
writeCC: false,
|
||||||
|
isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void FcmpR(EmitterContext context)
|
public static void FcmpR(EmitterContext context)
|
||||||
{
|
{
|
||||||
InstFcmpR op = context.GetOp<InstFcmpR>();
|
InstFcmpR op = context.GetOp<InstFcmpR>();
|
||||||
|
@ -240,12 +390,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
bool negateA,
|
bool negateA,
|
||||||
bool negateB,
|
bool negateB,
|
||||||
bool boolFloat,
|
bool boolFloat,
|
||||||
bool writeCC)
|
bool writeCC,
|
||||||
|
bool isFP64 = false)
|
||||||
{
|
{
|
||||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
|
||||||
|
|
||||||
Operand res = GetFPComparison(context, cmpOp, srcA, srcB);
|
srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType);
|
||||||
|
srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType);
|
||||||
|
|
||||||
|
Operand res = GetFPComparison(context, cmpOp, srcA, srcB, fpType);
|
||||||
Operand pred = GetPredicate(context, srcPred, srcPredInv);
|
Operand pred = GetPredicate(context, srcPred, srcPredInv);
|
||||||
|
|
||||||
res = GetPredLogicalOp(context, logicOp, res, pred);
|
res = GetPredLogicalOp(context, logicOp, res, pred);
|
||||||
|
@ -282,12 +435,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
bool absoluteB,
|
bool absoluteB,
|
||||||
bool negateA,
|
bool negateA,
|
||||||
bool negateB,
|
bool negateB,
|
||||||
bool writeCC)
|
bool writeCC,
|
||||||
|
bool isFP64 = false)
|
||||||
{
|
{
|
||||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
|
||||||
|
|
||||||
Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB);
|
srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType);
|
||||||
|
srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType);
|
||||||
|
|
||||||
|
Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB, fpType);
|
||||||
Operand p1Res = context.BitwiseNot(p0Res);
|
Operand p1Res = context.BitwiseNot(p0Res);
|
||||||
Operand pred = GetPredicate(context, srcPred, srcPredInv);
|
Operand pred = GetPredicate(context, srcPred, srcPredInv);
|
||||||
|
|
||||||
|
@ -367,7 +523,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
context.Copy(Register(destPredInv, RegisterType.Predicate), p1Res);
|
context.Copy(Register(destPredInv, RegisterType.Predicate), p1Res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operand GetFPComparison(EmitterContext context, FComp cond, Operand srcA, Operand srcB)
|
private static Operand GetFPComparison(EmitterContext context, FComp cond, Operand srcA, Operand srcB, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
Operand res;
|
Operand res;
|
||||||
|
|
||||||
|
@ -381,7 +537,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
}
|
}
|
||||||
else if (cond == FComp.Nan || cond == FComp.Num)
|
else if (cond == FComp.Nan || cond == FComp.Num)
|
||||||
{
|
{
|
||||||
res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB));
|
res = context.BitwiseOr(context.IsNan(srcA, fpType), context.IsNan(srcB, fpType));
|
||||||
|
|
||||||
if (cond == FComp.Num)
|
if (cond == FComp.Num)
|
||||||
{
|
{
|
||||||
|
@ -404,12 +560,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
default: throw new ArgumentException($"Unexpected condition \"{cond}\".");
|
default: throw new ArgumentException($"Unexpected condition \"{cond}\".");
|
||||||
}
|
}
|
||||||
|
|
||||||
res = context.Add(inst | Instruction.FP32, Local(), srcA, srcB);
|
res = context.Add(inst | fpType, Local(), srcA, srcB);
|
||||||
|
|
||||||
if ((cond & FComp.Nan) != 0)
|
if ((cond & FComp.Nan) != 0)
|
||||||
{
|
{
|
||||||
res = context.BitwiseOr(res, context.IsNan(srcA));
|
res = context.BitwiseOr(res, context.IsNan(srcA, fpType));
|
||||||
res = context.BitwiseOr(res, context.IsNan(srcB));
|
res = context.BitwiseOr(res, context.IsNan(srcB, fpType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
static partial class InstEmit
|
static partial class InstEmit
|
||||||
{
|
{
|
||||||
|
public static void DmnmxR(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDmnmxR op = context.GetOp<InstDmnmxR>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcReg(context, op.SrcB, isFP64: true);
|
||||||
|
var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||||
|
|
||||||
|
EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DmnmxI(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDmnmxI op = context.GetOp<InstDmnmxI>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true);
|
||||||
|
var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||||
|
|
||||||
|
EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DmnmxC(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstDmnmxC op = context.GetOp<InstDmnmxC>();
|
||||||
|
|
||||||
|
var srcA = GetSrcReg(context, op.SrcA, isFP64: true);
|
||||||
|
var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true);
|
||||||
|
var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||||
|
|
||||||
|
EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void FmnmxR(EmitterContext context)
|
public static void FmnmxR(EmitterContext context)
|
||||||
{
|
{
|
||||||
InstFmnmxR op = context.GetOp<InstFmnmxR>();
|
InstFmnmxR op = context.GetOp<InstFmnmxR>();
|
||||||
|
@ -52,19 +85,22 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
bool absoluteB,
|
bool absoluteB,
|
||||||
bool negateA,
|
bool negateA,
|
||||||
bool negateB,
|
bool negateB,
|
||||||
bool writeCC)
|
bool writeCC,
|
||||||
|
bool isFP64 = false)
|
||||||
{
|
{
|
||||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
|
||||||
|
|
||||||
Operand resMin = context.FPMinimum(srcA, srcB);
|
srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType);
|
||||||
Operand resMax = context.FPMaximum(srcA, srcB);
|
srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType);
|
||||||
|
|
||||||
Operand dest = GetDest(rd);
|
Operand resMin = context.FPMinimum(srcA, srcB, fpType);
|
||||||
|
Operand resMax = context.FPMaximum(srcA, srcB, fpType);
|
||||||
|
|
||||||
context.Copy(dest, context.ConditionalSelect(srcPred, resMin, resMax));
|
Operand res = context.ConditionalSelect(srcPred, resMin, resMax);
|
||||||
|
|
||||||
SetFPZnFlags(context, dest, writeCC);
|
SetDest(context, res, rd, isFP64);
|
||||||
|
|
||||||
|
SetFPZnFlags(context, res, writeCC, fpType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
if (isFP64)
|
if (isFP64)
|
||||||
{
|
{
|
||||||
return context.FP32ConvertToFP64(Const(imm));
|
return context.PackDouble2x32(Const(0), Const(imm));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -218,6 +218,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetDest(EmitterContext context, Operand value, int rd, bool isFP64)
|
||||||
|
{
|
||||||
|
if (isFP64)
|
||||||
|
{
|
||||||
|
context.Copy(GetDest(rd), context.UnpackDouble2x32Low(value));
|
||||||
|
context.Copy(GetDest2(rd), context.UnpackDouble2x32High(value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Copy(GetDest(rd), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int Imm16ToSInt(int imm16)
|
public static int Imm16ToSInt(int imm16)
|
||||||
{
|
{
|
||||||
return (short)imm16;
|
return (short)imm16;
|
||||||
|
|
|
@ -61,11 +61,23 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
res = context.FPReciprocalSquareRoot(res);
|
res = context.FPReciprocalSquareRoot(res);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MufuOp.Rcp64h:
|
||||||
|
res = context.PackDouble2x32(OperandHelper.Const(0), res);
|
||||||
|
res = context.UnpackDouble2x32High(context.FPReciprocal(res, Instruction.FP64));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MufuOp.Rsq64h:
|
||||||
|
res = context.PackDouble2x32(OperandHelper.Const(0), res);
|
||||||
|
res = context.UnpackDouble2x32High(context.FPReciprocalSquareRoot(res, Instruction.FP64));
|
||||||
|
break;
|
||||||
|
|
||||||
case MufuOp.Sqrt:
|
case MufuOp.Sqrt:
|
||||||
res = context.FPSquareRoot(res);
|
res = context.FPSquareRoot(res);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: /* TODO */ break;
|
default:
|
||||||
|
context.Config.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\".");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Copy(GetDest(op.Dest), context.FPSaturate(res, op.Sat));
|
context.Copy(GetDest(op.Dest), context.FPSaturate(res, op.Sat));
|
||||||
|
|
|
@ -219,9 +219,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
|
|
|
@ -306,9 +306,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (rdIndex > RegisterConsts.RegisterZeroIndex)
|
if (rdIndex >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(rdIndex++, RegisterType.Gpr);
|
return Register(rdIndex++, RegisterType.Gpr);
|
||||||
|
@ -322,6 +322,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
Operand dest = GetDest();
|
Operand dest = GetDest();
|
||||||
|
|
||||||
|
if (dest == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureOperation operation = context.CreateTextureOperation(
|
||||||
Instruction.TextureSample,
|
Instruction.TextureSample,
|
||||||
type,
|
type,
|
||||||
|
@ -795,9 +800,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
|
@ -809,13 +814,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
|
Operand destOperand = GetDest();
|
||||||
|
|
||||||
|
if (destOperand == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureOperation operation = context.CreateTextureOperation(
|
||||||
Instruction.TextureSample,
|
Instruction.TextureSample,
|
||||||
type,
|
type,
|
||||||
flags,
|
flags,
|
||||||
handle,
|
handle,
|
||||||
compIndex,
|
compIndex,
|
||||||
GetDest(),
|
destOperand,
|
||||||
sources);
|
sources);
|
||||||
|
|
||||||
context.Add(operation);
|
context.Add(operation);
|
||||||
|
@ -902,9 +914,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
|
@ -916,11 +928,18 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
|
Operand destOperand = GetDest();
|
||||||
|
|
||||||
|
if (destOperand == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Components z and w aren't standard, we return 0 in this case and add a comment.
|
// Components z and w aren't standard, we return 0 in this case and add a comment.
|
||||||
if (compIndex >= 2)
|
if (compIndex >= 2)
|
||||||
{
|
{
|
||||||
context.Add(new CommentNode("Unsupported component z or w found"));
|
context.Add(new CommentNode("Unsupported component z or w found"));
|
||||||
context.Copy(GetDest(), Const(0));
|
context.Copy(destOperand, Const(0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -941,7 +960,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand fixedPointValue = context.FP32ConvertToS32(tempDest);
|
Operand fixedPointValue = context.FP32ConvertToS32(tempDest);
|
||||||
|
|
||||||
context.Copy(GetDest(), fixedPointValue);
|
context.Copy(destOperand, fixedPointValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1055,9 +1074,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
|
@ -1069,13 +1088,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
|
Operand destOperand = GetDest();
|
||||||
|
|
||||||
|
if (destOperand == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureOperation operation = context.CreateTextureOperation(
|
||||||
Instruction.TextureSample,
|
Instruction.TextureSample,
|
||||||
type,
|
type,
|
||||||
flags,
|
flags,
|
||||||
handle,
|
handle,
|
||||||
compIndex,
|
compIndex,
|
||||||
GetDest(),
|
destOperand,
|
||||||
sources);
|
sources);
|
||||||
|
|
||||||
context.Add(operation);
|
context.Add(operation);
|
||||||
|
@ -1126,9 +1152,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
Operand GetDest()
|
Operand GetDest()
|
||||||
{
|
{
|
||||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||||
{
|
{
|
||||||
return Const(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
|
@ -1149,13 +1175,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
|
Operand destOperand = GetDest();
|
||||||
|
|
||||||
|
if (destOperand == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureOperation operation = context.CreateTextureOperation(
|
||||||
inst,
|
inst,
|
||||||
type,
|
type,
|
||||||
flags,
|
flags,
|
||||||
imm,
|
imm,
|
||||||
compIndex,
|
compIndex,
|
||||||
GetDest(),
|
destOperand,
|
||||||
sources);
|
sources);
|
||||||
|
|
||||||
context.Add(operation);
|
context.Add(operation);
|
||||||
|
|
|
@ -68,8 +68,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
ExponentB2,
|
ExponentB2,
|
||||||
FSIBegin,
|
FSIBegin,
|
||||||
FSIEnd,
|
FSIEnd,
|
||||||
FindFirstSetS32,
|
FindLSB,
|
||||||
FindFirstSetU32,
|
FindMSBS32,
|
||||||
|
FindMSBU32,
|
||||||
Floor,
|
Floor,
|
||||||
FusedMultiplyAdd,
|
FusedMultiplyAdd,
|
||||||
GroupMemoryBarrier,
|
GroupMemoryBarrier,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -79,14 +79,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
Add(Instruction.Ddy, VariableType.F32, VariableType.F32);
|
Add(Instruction.Ddy, VariableType.F32, VariableType.F32);
|
||||||
Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
||||||
Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
|
Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
|
||||||
Add(Instruction.FindFirstSetS32, VariableType.S32, VariableType.S32);
|
Add(Instruction.FindLSB, VariableType.Int, VariableType.Int);
|
||||||
Add(Instruction.FindFirstSetU32, VariableType.S32, VariableType.U32);
|
Add(Instruction.FindMSBS32, VariableType.S32, VariableType.S32);
|
||||||
|
Add(Instruction.FindMSBU32, VariableType.S32, VariableType.U32);
|
||||||
Add(Instruction.Floor, VariableType.Scalar, VariableType.Scalar);
|
Add(Instruction.Floor, VariableType.Scalar, VariableType.Scalar);
|
||||||
Add(Instruction.FusedMultiplyAdd, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
Add(Instruction.FusedMultiplyAdd, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
||||||
Add(Instruction.ImageLoad, VariableType.F32);
|
Add(Instruction.ImageLoad, VariableType.F32);
|
||||||
Add(Instruction.ImageStore, VariableType.None);
|
Add(Instruction.ImageStore, VariableType.None);
|
||||||
Add(Instruction.ImageAtomic, VariableType.S32);
|
Add(Instruction.ImageAtomic, VariableType.S32);
|
||||||
Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
|
Add(Instruction.IsNan, VariableType.Bool, VariableType.Scalar);
|
||||||
Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32, VariableType.S32);
|
Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32, VariableType.S32);
|
||||||
Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
|
Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
|
||||||
Add(Instruction.LoadGlobal, VariableType.U32, VariableType.S32, VariableType.S32);
|
Add(Instruction.LoadGlobal, VariableType.U32, VariableType.S32, VariableType.S32);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
|
@ -181,14 +182,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(Instruction.EndPrimitive);
|
return context.Add(Instruction.EndPrimitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FindFirstSetS32(this EmitterContext context, Operand a)
|
public static Operand FindLSB(this EmitterContext context, Operand a)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.FindFirstSetS32, Local(), a);
|
return context.Add(Instruction.FindLSB, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FindFirstSetU32(this EmitterContext context, Operand a)
|
public static Operand FindMSBS32(this EmitterContext context, Operand a)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.FindFirstSetU32, Local(), a);
|
return context.Add(Instruction.FindMSBS32, Local(), a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand FindMSBU32(this EmitterContext context, Operand a)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.FindMSBU32, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FP32ConvertToFP64(this EmitterContext context, Operand a)
|
public static Operand FP32ConvertToFP64(this EmitterContext context, Operand a)
|
||||||
|
@ -266,9 +272,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(Instruction.FP32 | Instruction.Cosine, Local(), a);
|
return context.Add(Instruction.FP32 | Instruction.Cosine, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPDivide(this EmitterContext context, Operand a, Operand b)
|
public static Operand FPDivide(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.FP32 | Instruction.Divide, Local(), a, b);
|
return context.Add(fpType | Instruction.Divide, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPExponentB2(this EmitterContext context, Operand a)
|
public static Operand FPExponentB2(this EmitterContext context, Operand a)
|
||||||
|
@ -296,9 +302,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(fpType | Instruction.Maximum, Local(), a, b);
|
return context.Add(fpType | Instruction.Maximum, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b)
|
public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.FP32 | Instruction.Minimum, Local(), a, b);
|
return context.Add(fpType | Instruction.Minimum, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||||
|
@ -321,14 +327,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(fpType | Instruction.Negate, Local(), a);
|
return context.Add(fpType | Instruction.Negate, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPReciprocal(this EmitterContext context, Operand a)
|
public static Operand FPReciprocal(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.FPDivide(ConstF(1), a);
|
return context.FPDivide(fpType == Instruction.FP64 ? context.PackDouble2x32(1.0) : ConstF(1), a, fpType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a)
|
public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.FP32 | Instruction.ReciprocalSquareRoot, Local(), a);
|
return context.Add(fpType | Instruction.ReciprocalSquareRoot, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPRound(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
public static Operand FPRound(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
|
@ -348,7 +354,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
public static Operand FPSaturate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
public static Operand FPSaturate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(fpType | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1));
|
return fpType == Instruction.FP64
|
||||||
|
? context.Add(fpType | Instruction.Clamp, Local(), a, context.PackDouble2x32(0.0), context.PackDouble2x32(1.0))
|
||||||
|
: context.Add(fpType | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand FPSine(this EmitterContext context, Operand a)
|
public static Operand FPSine(this EmitterContext context, Operand a)
|
||||||
|
@ -536,9 +544,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(Instruction.Subtract, Local(), a, b);
|
return context.Add(Instruction.Subtract, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand IsNan(this EmitterContext context, Operand a)
|
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.IsNan, Local(), a);
|
return context.Add(fpType | Instruction.IsNan, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b, Operand c)
|
public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b, Operand c)
|
||||||
|
@ -590,6 +598,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(Instruction.MultiplyHighU32, Local(), a, b);
|
return context.Add(Instruction.MultiplyHighU32, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand PackDouble2x32(this EmitterContext context, double value)
|
||||||
|
{
|
||||||
|
long valueAsLong = BitConverter.DoubleToInt64Bits(value);
|
||||||
|
|
||||||
|
return context.Add(Instruction.PackDouble2x32, Local(), Const((int)valueAsLong), Const((int)(valueAsLong >> 32)));
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand PackDouble2x32(this EmitterContext context, Operand a, Operand b)
|
public static Operand PackDouble2x32(this EmitterContext context, Operand a, Operand b)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.PackDouble2x32, Local(), a, b);
|
return context.Add(Instruction.PackDouble2x32, Local(), a, b);
|
||||||
|
|
|
@ -250,6 +250,7 @@ namespace Ryujinx.Graphics.Texture
|
||||||
int height,
|
int height,
|
||||||
int blockWidth,
|
int blockWidth,
|
||||||
int blockHeight,
|
int blockHeight,
|
||||||
|
int lineSize,
|
||||||
int stride,
|
int stride,
|
||||||
int bytesPerPixel,
|
int bytesPerPixel,
|
||||||
ReadOnlySpan<byte> data)
|
ReadOnlySpan<byte> data)
|
||||||
|
@ -258,7 +259,7 @@ namespace Ryujinx.Graphics.Texture
|
||||||
int h = BitUtils.DivRoundUp(height, blockHeight);
|
int h = BitUtils.DivRoundUp(height, blockHeight);
|
||||||
|
|
||||||
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
|
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
|
||||||
int lineSize = Math.Min(stride, outStride);
|
lineSize = Math.Min(lineSize, outStride);
|
||||||
|
|
||||||
PooledBuffer<byte> outputBuffer = BufferPool<byte>.Rent(h * outStride);
|
PooledBuffer<byte> outputBuffer = BufferPool<byte>.Rent(h * outStride);
|
||||||
Span<byte> output = outputBuffer.AsSpan;
|
Span<byte> output = outputBuffer.AsSpan;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -14,6 +14,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem.Content
|
namespace Ryujinx.HLE.FileSystem.Content
|
||||||
{
|
{
|
||||||
|
@ -203,40 +204,37 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
|
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
|
||||||
{
|
{
|
||||||
fs.OpenFile(out IFile ncaFile, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
using (ncaFile)
|
|
||||||
|
fs.OpenFile(ref ncaFile.Ref(), ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
||||||
|
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
|
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||||
{
|
{
|
||||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file");
|
||||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file");
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||||
|
using var cnmtFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
pfs0.OpenFile(out IFile cnmtFile, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
pfs0.OpenFile(ref cnmtFile.Ref(), pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
using (cnmtFile)
|
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
||||||
{
|
|
||||||
var cnmt = new Cnmt(cnmtFile.AsStream());
|
|
||||||
|
|
||||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
|
string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
|
||||||
if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
|
if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
|
Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
|
Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,24 +270,24 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled)
|
if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled)
|
||||||
{
|
{
|
||||||
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
PartitionFileSystem pfs;
|
PartitionFileSystem pfs;
|
||||||
IFile ncaFile;
|
|
||||||
|
|
||||||
switch (Path.GetExtension(aoc.ContainerPath))
|
switch (Path.GetExtension(aoc.ContainerPath))
|
||||||
{
|
{
|
||||||
case ".xci":
|
case ".xci":
|
||||||
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||||
break;
|
break;
|
||||||
case ".nsp":
|
case ".nsp":
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false; // Print error?
|
return false; // Print error?
|
||||||
}
|
}
|
||||||
|
|
||||||
aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage()).OpenStorage(NcaSectionType.Data, integrityCheckLevel);
|
aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()).OpenStorage(NcaSectionType.Data, integrityCheckLevel);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -625,18 +623,18 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode)
|
private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode)
|
||||||
{
|
{
|
||||||
IFile file;
|
using var file = new UniqueRef<IFile>();
|
||||||
|
|
||||||
if (filesystem.FileExists($"{path}/00"))
|
if (filesystem.FileExists($"{path}/00"))
|
||||||
{
|
{
|
||||||
filesystem.OpenFile(out file, $"{path}/00".ToU8Span(), mode);
|
filesystem.OpenFile(ref file.Ref(), $"{path}/00".ToU8Span(), mode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filesystem.OpenFile(out file, path.ToU8Span(), mode);
|
filesystem.OpenFile(ref file.Ref(), path.ToU8Span(), mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return file;
|
return file.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream GetZipStream(ZipArchiveEntry entry)
|
private Stream GetZipStream(ZipArchiveEntry entry)
|
||||||
|
@ -753,9 +751,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
||||||
|
|
||||||
if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
using var metaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
var meta = new Cnmt(metaFile.AsStream());
|
var meta = new Cnmt(metaFile.Get.AsStream());
|
||||||
|
|
||||||
if (meta.Type == ContentMetaType.SystemUpdate)
|
if (meta.Type == ContentMetaType.SystemUpdate)
|
||||||
{
|
{
|
||||||
|
@ -781,9 +781,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
using var systemVersionFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
systemVersion = new SystemVersion(systemVersionFile.AsStream());
|
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -818,9 +820,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
||||||
|
|
||||||
if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
using var metaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
var meta = new Cnmt(metaFile.AsStream());
|
var meta = new Cnmt(metaFile.Get.AsStream());
|
||||||
|
|
||||||
IStorage contentStorage = contentNcaStream.AsStorage();
|
IStorage contentStorage = contentNcaStream.AsStorage();
|
||||||
if (contentStorage.GetSize(out long size).IsSuccess())
|
if (contentStorage.GetSize(out long size).IsSuccess())
|
||||||
|
@ -887,9 +891,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
||||||
|
|
||||||
if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
using var metaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
var meta = new Cnmt(metaFile.AsStream());
|
var meta = new Cnmt(metaFile.Get.AsStream());
|
||||||
|
|
||||||
if (meta.Type == ContentMetaType.SystemUpdate)
|
if (meta.Type == ContentMetaType.SystemUpdate)
|
||||||
{
|
{
|
||||||
|
@ -903,9 +909,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
{
|
{
|
||||||
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
using var systemVersionFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
systemVersion = new SystemVersion(systemVersionFile.AsStream());
|
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -952,9 +960,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
|
||||||
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
|
||||||
|
|
||||||
if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
using var metaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
var meta = new Cnmt(metaFile.AsStream());
|
var meta = new Cnmt(metaFile.Get.AsStream());
|
||||||
|
|
||||||
if (contentStorage.GetSize(out long size).IsSuccess())
|
if (contentStorage.GetSize(out long size).IsSuccess())
|
||||||
{
|
{
|
||||||
|
@ -1020,9 +1030,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
{
|
{
|
||||||
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
using var systemVersionFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
||||||
{
|
{
|
||||||
return new SystemVersion(systemVersionFile.AsStream());
|
return new SystemVersion(systemVersionFile.Get.AsStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,23 @@ using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSrv.FsCreator;
|
using LibHac.FsSrv.FsCreator;
|
||||||
using LibHac.FsSystem;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
|
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
|
||||||
{
|
{
|
||||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem, ReferenceCountedDisposable<IFileSystem> baseFileSystem,
|
|
||||||
EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed)
|
|
||||||
{
|
|
||||||
UnsafeHelpers.SkipParamInit(out encryptedFileSystem);
|
|
||||||
|
|
||||||
if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage)
|
public Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem,
|
||||||
|
ref SharedRef<IFileSystem> baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex,
|
||||||
|
in EncryptionSeed encryptionSeed)
|
||||||
|
{
|
||||||
|
if (idIndex < IEncryptedFileSystemCreator.KeyId.Save || idIndex > IEncryptedFileSystemCreator.KeyId.CustomStorage)
|
||||||
{
|
{
|
||||||
return ResultFs.InvalidArgument.Log();
|
return ResultFs.InvalidArgument.Log();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force all-zero keys for now since people can open the emulator with different keys or sd seeds sometimes
|
// Todo: Reenable when AesXtsFileSystem is fixed
|
||||||
var fs = new AesXtsFileSystem(baseFileSystem, new byte[0x32], 0x4000);
|
outEncryptedFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem);
|
||||||
encryptedFileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
|
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ using System.Buffers.Text;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using Path = System.IO.Path;
|
||||||
using RightsId = LibHac.Fs.RightsId;
|
using RightsId = LibHac.Fs.RightsId;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
|
@ -240,11 +242,13 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik"))
|
foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik"))
|
||||||
{
|
{
|
||||||
Result result = fs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
using var ticketFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
Result result = fs.OpenFile(ref ticketFile.Ref(), ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
if (result.IsSuccess())
|
if (result.IsSuccess())
|
||||||
{
|
{
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
Ticket ticket = new Ticket(ticketFile.Get.AsStream());
|
||||||
|
|
||||||
if (ticket.TitleKeyType == TitleKeyType.Common)
|
if (ticket.TitleKeyType == TitleKeyType.Common)
|
||||||
{
|
{
|
||||||
|
@ -280,12 +284,14 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
Span<SaveDataInfo> info = stackalloc SaveDataInfo[8];
|
Span<SaveDataInfo> info = stackalloc SaveDataInfo[8];
|
||||||
|
|
||||||
Result rc = hos.Fs.OpenSaveDataIterator(out var iterator, spaceId);
|
using var iterator = new UniqueRef<SaveDataIterator>();
|
||||||
|
|
||||||
|
Result rc = hos.Fs.OpenSaveDataIterator(ref iterator.Ref(), spaceId);
|
||||||
if (rc.IsFailure()) return rc;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
rc = iterator.ReadSaveDataInfo(out long count, info);
|
rc = iterator.Get.ReadSaveDataInfo(out long count, info);
|
||||||
if (rc.IsFailure()) return rc;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
|
|
|
@ -98,6 +98,11 @@ namespace Ryujinx.HLE
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnablePtc;
|
internal readonly bool EnablePtc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Control if the guest application should be told that there is a Internet connection available.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly bool EnableInternetAccess;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control LibHac's integrity check level.
|
/// Control LibHac's integrity check level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -122,8 +127,8 @@ namespace Ryujinx.HLE
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly string TimeZone;
|
internal readonly string TimeZone;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Type of the memory manager used on CPU emulation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MemoryManagerMode MemoryManagerMode { internal get; set; }
|
public MemoryManagerMode MemoryManagerMode { internal get; set; }
|
||||||
|
|
||||||
|
@ -139,6 +144,11 @@ namespace Ryujinx.HLE
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AspectRatio AspectRatio { get; set; }
|
public AspectRatio AspectRatio { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The audio volume level.
|
||||||
|
/// </summary>
|
||||||
|
public float AudioVolume { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -158,13 +168,15 @@ namespace Ryujinx.HLE
|
||||||
bool enableVsync,
|
bool enableVsync,
|
||||||
bool enableDockedMode,
|
bool enableDockedMode,
|
||||||
bool enablePtc,
|
bool enablePtc,
|
||||||
|
bool enableInternetAccess,
|
||||||
IntegrityCheckLevel fsIntegrityCheckLevel,
|
IntegrityCheckLevel fsIntegrityCheckLevel,
|
||||||
int fsGlobalAccessLogMode,
|
int fsGlobalAccessLogMode,
|
||||||
long systemTimeOffset,
|
long systemTimeOffset,
|
||||||
string timeZone,
|
string timeZone,
|
||||||
MemoryManagerMode memoryManagerMode,
|
MemoryManagerMode memoryManagerMode,
|
||||||
bool ignoreMissingServices,
|
bool ignoreMissingServices,
|
||||||
AspectRatio aspectRatio)
|
AspectRatio aspectRatio,
|
||||||
|
float audioVolume)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
LibHacHorizonManager = libHacHorizonManager;
|
LibHacHorizonManager = libHacHorizonManager;
|
||||||
|
@ -180,6 +192,7 @@ namespace Ryujinx.HLE
|
||||||
EnableVsync = enableVsync;
|
EnableVsync = enableVsync;
|
||||||
EnableDockedMode = enableDockedMode;
|
EnableDockedMode = enableDockedMode;
|
||||||
EnablePtc = enablePtc;
|
EnablePtc = enablePtc;
|
||||||
|
EnableInternetAccess = enableInternetAccess;
|
||||||
FsIntegrityCheckLevel = fsIntegrityCheckLevel;
|
FsIntegrityCheckLevel = fsIntegrityCheckLevel;
|
||||||
FsGlobalAccessLogMode = fsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = fsGlobalAccessLogMode;
|
||||||
SystemTimeOffset = systemTimeOffset;
|
SystemTimeOffset = systemTimeOffset;
|
||||||
|
@ -187,6 +200,7 @@ namespace Ryujinx.HLE
|
||||||
MemoryManagerMode = memoryManagerMode;
|
MemoryManagerMode = memoryManagerMode;
|
||||||
IgnoreMissingServices = ignoreMissingServices;
|
IgnoreMissingServices = ignoreMissingServices;
|
||||||
AspectRatio = aspectRatio;
|
AspectRatio = aspectRatio;
|
||||||
|
AudioVolume = audioVolume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -116,8 +116,10 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
|
|
||||||
if (romfs.FileExists(filePath))
|
if (romfs.FileExists(filePath))
|
||||||
{
|
{
|
||||||
romfs.OpenFile(out IFile binaryFile, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
using var binaryFile = new UniqueRef<IFile>();
|
||||||
StreamReader reader = new StreamReader(binaryFile.AsStream(), Encoding.Unicode);
|
|
||||||
|
romfs.OpenFile(ref binaryFile.Ref(), filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
StreamReader reader = new StreamReader(binaryFile.Get.AsStream(), Encoding.Unicode);
|
||||||
|
|
||||||
return CleanText(reader.ReadToEnd());
|
return CleanText(reader.ReadToEnd());
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
_npads?.Update();
|
_npads?.Update();
|
||||||
|
|
||||||
return _keyboardRenderer?.DrawTo(surfaceInfo, destination, position) ?? false;
|
_keyboardRenderer?.SetSurfaceInfo(surfaceInfo);
|
||||||
|
|
||||||
|
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteForegroundKeyboard()
|
private void ExecuteForegroundKeyboard()
|
||||||
|
|
|
@ -1,717 +1,164 @@
|
||||||
using Ryujinx.HLE.Ui;
|
using Ryujinx.HLE.Ui;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Drawing2D;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Drawing.Text;
|
|
||||||
using System.IO;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class that generates the graphics for the software keyboard applet during inline mode.
|
/// Class that manages the renderer base class and its state in a multithreaded context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SoftwareKeyboardRenderer : IDisposable
|
internal class SoftwareKeyboardRenderer : IDisposable
|
||||||
{
|
{
|
||||||
const int TextBoxBlinkThreshold = 8;
|
private const int TextBoxBlinkSleepMilliseconds = 100;
|
||||||
const int TextBoxBlinkSleepMilliseconds = 100;
|
private const int RendererWaitTimeoutMilliseconds = 100;
|
||||||
const int TextBoxBlinkJoinWaitMilliseconds = 1000;
|
|
||||||
|
|
||||||
const string MessageText = "Please use the keyboard to input text";
|
private readonly object _stateLock = new object();
|
||||||
const string AcceptText = "Accept";
|
|
||||||
const string CancelText = "Cancel";
|
|
||||||
const string ControllerToggleText = "Toggle input";
|
|
||||||
|
|
||||||
private RenderingSurfaceInfo _surfaceInfo;
|
private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState();
|
||||||
private Bitmap _surface = null;
|
private SoftwareKeyboardRendererBase _renderer;
|
||||||
private object _renderLock = new object();
|
|
||||||
|
|
||||||
private string _inputText = "";
|
|
||||||
private int _cursorStart = 0;
|
|
||||||
private int _cursorEnd = 0;
|
|
||||||
private bool _acceptPressed = false;
|
|
||||||
private bool _cancelPressed = false;
|
|
||||||
private bool _overwriteMode = false;
|
|
||||||
private bool _typingEnabled = true;
|
|
||||||
private bool _controllerEnabled = true;
|
|
||||||
|
|
||||||
private Image _ryujinxLogo = null;
|
|
||||||
private Image _padAcceptIcon = null;
|
|
||||||
private Image _padCancelIcon = null;
|
|
||||||
private Image _keyModeIcon = null;
|
|
||||||
|
|
||||||
private float _textBoxOutlineWidth;
|
|
||||||
private float _padPressedPenWidth;
|
|
||||||
|
|
||||||
private Brush _panelBrush;
|
|
||||||
private Brush _disabledBrush;
|
|
||||||
private Brush _textNormalBrush;
|
|
||||||
private Brush _textSelectedBrush;
|
|
||||||
private Brush _textOverCursorBrush;
|
|
||||||
private Brush _cursorBrush;
|
|
||||||
private Brush _selectionBoxBrush;
|
|
||||||
private Brush _keyCapBrush;
|
|
||||||
private Brush _keyProgressBrush;
|
|
||||||
|
|
||||||
private Pen _gridSeparatorPen;
|
|
||||||
private Pen _textBoxOutlinePen;
|
|
||||||
private Pen _cursorPen;
|
|
||||||
private Pen _selectionBoxPen;
|
|
||||||
private Pen _padPressedPen;
|
|
||||||
|
|
||||||
private int _inputTextFontSize;
|
|
||||||
private int _padButtonFontSize;
|
|
||||||
private Font _messageFont;
|
|
||||||
private Font _inputTextFont;
|
|
||||||
private Font _labelsTextFont;
|
|
||||||
private Font _padSymbolFont;
|
|
||||||
private Font _keyCapFont;
|
|
||||||
|
|
||||||
private float _inputTextCalibrationHeight;
|
|
||||||
private float _panelPositionY;
|
|
||||||
private RectangleF _panelRectangle;
|
|
||||||
private PointF _logoPosition;
|
|
||||||
private float _messagePositionY;
|
|
||||||
|
|
||||||
private TRef<int> _textBoxBlinkCounter = new TRef<int>(0);
|
|
||||||
private TimedAction _textBoxBlinkTimedAction = new TimedAction();
|
private TimedAction _textBoxBlinkTimedAction = new TimedAction();
|
||||||
|
private TimedAction _renderAction = new TimedAction();
|
||||||
|
|
||||||
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
|
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
|
||||||
{
|
{
|
||||||
_surfaceInfo = new RenderingSurfaceInfo(0, 0, 0, 0, 0);
|
_renderer = new SoftwareKeyboardRendererBase(uiTheme);
|
||||||
|
|
||||||
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
|
StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
|
||||||
int ryujinxLogoSize = 32;
|
StartRenderer(_renderAction, _renderer, _state, _stateLock);
|
||||||
|
|
||||||
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
|
|
||||||
|
|
||||||
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
|
||||||
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
|
||||||
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
|
||||||
|
|
||||||
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
|
||||||
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
|
||||||
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
|
||||||
|
|
||||||
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
|
||||||
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
|
||||||
Color normalTextColor = ToColor(uiTheme.DefaultForegroundColor);
|
|
||||||
Color invertedTextColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
|
||||||
Color selectedTextColor = ToColor(uiTheme.SelectionForegroundColor);
|
|
||||||
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
|
||||||
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
|
||||||
Color gridSeparatorColor = Color.FromArgb(180, 255, 255, 255);
|
|
||||||
|
|
||||||
float cursorWidth = 2;
|
|
||||||
|
|
||||||
_textBoxOutlineWidth = 2;
|
|
||||||
_padPressedPenWidth = 2;
|
|
||||||
|
|
||||||
_panelBrush = new SolidBrush(panelColor);
|
|
||||||
_disabledBrush = new SolidBrush(panelTransparentColor);
|
|
||||||
_textNormalBrush = new SolidBrush(normalTextColor);
|
|
||||||
_textSelectedBrush = new SolidBrush(selectedTextColor);
|
|
||||||
_textOverCursorBrush = new SolidBrush(invertedTextColor);
|
|
||||||
_cursorBrush = new SolidBrush(normalTextColor);
|
|
||||||
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
|
||||||
_keyCapBrush = Brushes.White;
|
|
||||||
_keyProgressBrush = new SolidBrush(borderColor);
|
|
||||||
|
|
||||||
_gridSeparatorPen = new Pen(gridSeparatorColor, 2);
|
|
||||||
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
|
||||||
_cursorPen = new Pen(normalTextColor, cursorWidth);
|
|
||||||
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
|
||||||
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
|
||||||
|
|
||||||
_inputTextFontSize = 20;
|
|
||||||
_padButtonFontSize = 24;
|
|
||||||
|
|
||||||
string font = uiTheme.FontFamily;
|
|
||||||
|
|
||||||
_messageFont = new Font(font, 26, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_inputTextFont = new Font(font, _inputTextFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_labelsTextFont = new Font(font, 24, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_padSymbolFont = new Font(font, _padButtonFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_keyCapFont = new Font(font, 15, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
|
|
||||||
// System.Drawing has serious problems measuring strings, so it requires a per-pixel calibration
|
|
||||||
// to ensure we are rendering text inside the proper region
|
|
||||||
_inputTextCalibrationHeight = CalibrateTextHeight(_inputTextFont);
|
|
||||||
|
|
||||||
StartTextBoxBlinker(_textBoxBlinkTimedAction, _textBoxBlinkCounter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartTextBoxBlinker(TimedAction timedAction, TRef<int> blinkerCounter)
|
private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
|
||||||
{
|
{
|
||||||
timedAction.Reset(() =>
|
timedAction.Reset(() =>
|
||||||
{
|
{
|
||||||
// The blinker is on falf of the time and events such as input
|
lock (stateLock)
|
||||||
// changes can reset the blinker.
|
{
|
||||||
var value = Volatile.Read(ref blinkerCounter.Value);
|
// The blinker is on half of the time and events such as input
|
||||||
value = (value + 1) % (2 * TextBoxBlinkThreshold);
|
// changes can reset the blinker.
|
||||||
Volatile.Write(ref blinkerCounter.Value, value);
|
state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(stateLock);
|
||||||
|
}
|
||||||
}, TextBoxBlinkSleepMilliseconds);
|
}, TextBoxBlinkSleepMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock)
|
||||||
{
|
{
|
||||||
var a = (byte)(color.A * 255);
|
SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState();
|
||||||
var r = (byte)(color.R * 255);
|
|
||||||
var g = (byte)(color.G * 255);
|
|
||||||
var b = (byte)(color.B * 255);
|
|
||||||
|
|
||||||
if (flipRgb)
|
bool canCreateSurface = false;
|
||||||
|
bool needsUpdate = true;
|
||||||
|
|
||||||
|
timedAction.Reset(() =>
|
||||||
{
|
{
|
||||||
r = (byte)(255 - r);
|
lock (stateLock)
|
||||||
g = (byte)(255 - g);
|
{
|
||||||
b = (byte)(255 - b);
|
if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return Color.FromArgb(overrideAlpha.GetValueOrDefault(a), r, g, b);
|
needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
|
||||||
|
|
||||||
|
canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
|
||||||
|
|
||||||
|
if (canCreateSurface)
|
||||||
|
{
|
||||||
|
internalState.SurfaceInfo = state.SurfaceInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canCreateSurface)
|
||||||
|
{
|
||||||
|
renderer.CreateSurface(internalState.SurfaceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
renderer.DrawMutableElements(internalState);
|
||||||
|
renderer.CopyImageToBuffer();
|
||||||
|
needsUpdate = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
|
||||||
{
|
{
|
||||||
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
if (!source.Equals(destination))
|
||||||
|
|
||||||
Debug.Assert(resourceStream != null);
|
|
||||||
|
|
||||||
var originalImage = Image.FromStream(resourceStream);
|
|
||||||
|
|
||||||
if (newHeight == 0 || newWidth == 0)
|
|
||||||
{
|
{
|
||||||
return originalImage;
|
destination = source;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSize = new Rectangle(0, 0, newWidth, newHeight);
|
return false;
|
||||||
var newImage = new Bitmap(newWidth, newHeight);
|
|
||||||
|
|
||||||
using (var graphics = System.Drawing.Graphics.FromImage(newImage))
|
|
||||||
using (var wrapMode = new ImageAttributes())
|
|
||||||
{
|
|
||||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
||||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
|
||||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
|
||||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
||||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
|
||||||
|
|
||||||
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
|
|
||||||
graphics.DrawImage(originalImage, newSize, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8632
|
#pragma warning disable CS8632
|
||||||
public void UpdateTextState(string? inputText, int? cursorStart, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
|
public void UpdateTextState(string? inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
|
||||||
#pragma warning restore CS8632
|
#pragma warning restore CS8632
|
||||||
{
|
{
|
||||||
lock (_renderLock)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
// Update the parameters that were provided.
|
// Update the parameters that were provided.
|
||||||
_inputText = inputText != null ? inputText : _inputText;
|
_state.InputText = inputText != null ? inputText : _state.InputText;
|
||||||
_cursorStart = cursorStart.GetValueOrDefault(_cursorStart);
|
_state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
|
||||||
_cursorEnd = cursorEnd.GetValueOrDefault(_cursorEnd);
|
_state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
|
||||||
_overwriteMode = overwriteMode.GetValueOrDefault(_overwriteMode);
|
_state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
|
||||||
_typingEnabled = typingEnabled.GetValueOrDefault(_typingEnabled);
|
_state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
|
||||||
|
|
||||||
// Reset the cursor blink.
|
// Reset the cursor blink.
|
||||||
Volatile.Write(ref _textBoxBlinkCounter.Value, 0);
|
_state.TextBoxBlinkCounter = 0;
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(_stateLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
|
public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
|
||||||
{
|
{
|
||||||
lock (_renderLock)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
// Update the parameters that were provided.
|
// Update the parameters that were provided.
|
||||||
_acceptPressed = acceptPressed.GetValueOrDefault(_acceptPressed);
|
_state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
|
||||||
_cancelPressed = cancelPressed.GetValueOrDefault(_cancelPressed);
|
_state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
|
||||||
_controllerEnabled = controllerEnabled.GetValueOrDefault(_controllerEnabled);
|
_state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled);
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(_stateLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Redraw()
|
public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo)
|
||||||
{
|
{
|
||||||
if (_surface == null)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
return;
|
_state.SurfaceInfo = surfaceInfo;
|
||||||
}
|
|
||||||
|
|
||||||
using (var graphics = CreateGraphics())
|
// Tell the render thread there is something new to render.
|
||||||
{
|
Monitor.PulseAll(_stateLock);
|
||||||
var messageRectangle = MeasureString(graphics, MessageText, _messageFont);
|
|
||||||
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
|
||||||
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
|
||||||
PointF messagePosition = new PointF(messagePositionX, messagePositionY);
|
|
||||||
|
|
||||||
graphics.Clear(Color.Transparent);
|
|
||||||
graphics.TranslateTransform(0, _panelPositionY);
|
|
||||||
graphics.FillRectangle(_panelBrush, _panelRectangle);
|
|
||||||
graphics.DrawImage(_ryujinxLogo, _logoPosition);
|
|
||||||
|
|
||||||
DrawString(graphics, MessageText, _messageFont, _textNormalBrush, messagePosition);
|
|
||||||
|
|
||||||
if (!_typingEnabled)
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillRectangle(_disabledBrush, messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawTextBox(graphics);
|
|
||||||
|
|
||||||
float halfWidth = _panelRectangle.Width / 2;
|
|
||||||
|
|
||||||
PointF acceptButtonPosition = new PointF(halfWidth - 180, 185);
|
|
||||||
PointF cancelButtonPosition = new PointF(halfWidth , 185);
|
|
||||||
PointF disableButtonPosition = new PointF(halfWidth + 180, 185);
|
|
||||||
|
|
||||||
DrawPadButton (graphics, acceptButtonPosition , _padAcceptIcon, AcceptText, _acceptPressed, _controllerEnabled);
|
|
||||||
DrawPadButton (graphics, cancelButtonPosition , _padCancelIcon, CancelText, _cancelPressed, _controllerEnabled);
|
|
||||||
DrawControllerToggle(graphics, disableButtonPosition, _controllerEnabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecreateSurface()
|
internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
|
||||||
{
|
{
|
||||||
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
return _renderer.WriteBufferToMemory(destination, position);
|
||||||
|
|
||||||
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
|
||||||
// image if the pitch is different.
|
|
||||||
uint totalWidth = _surfaceInfo.Pitch / 4;
|
|
||||||
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
|
||||||
|
|
||||||
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
|
||||||
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
|
||||||
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
|
||||||
|
|
||||||
_surface = new Bitmap((int)totalWidth, (int)totalHeight, PixelFormat.Format32bppArgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RecomputeConstants()
|
|
||||||
{
|
|
||||||
float totalWidth = _surfaceInfo.Width;
|
|
||||||
float totalHeight = _surfaceInfo.Height;
|
|
||||||
|
|
||||||
float panelHeight = 240;
|
|
||||||
|
|
||||||
_panelPositionY = totalHeight - panelHeight;
|
|
||||||
_panelRectangle = new RectangleF(0, 0, totalWidth, panelHeight);
|
|
||||||
|
|
||||||
_messagePositionY = 60;
|
|
||||||
|
|
||||||
float logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
|
||||||
float logoPositionY = 18;
|
|
||||||
|
|
||||||
_logoPosition = new PointF(logoPositionX, logoPositionY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringFormat CreateStringFormat(string text)
|
|
||||||
{
|
|
||||||
StringFormat format = new StringFormat(StringFormat.GenericTypographic);
|
|
||||||
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
|
|
||||||
format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
|
|
||||||
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RectangleF MeasureString(System.Drawing.Graphics graphics, string text, System.Drawing.Font font)
|
|
||||||
{
|
|
||||||
bool isEmpty = false;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
isEmpty = true;
|
|
||||||
text = " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
var format = CreateStringFormat(text);
|
|
||||||
var rectangle = new RectangleF(0, 0, float.PositiveInfinity, float.PositiveInfinity);
|
|
||||||
var regions = graphics.MeasureCharacterRanges(text, font, rectangle, format);
|
|
||||||
|
|
||||||
Debug.Assert(regions.Length == 1);
|
|
||||||
|
|
||||||
rectangle = regions[0].GetBounds(graphics);
|
|
||||||
|
|
||||||
if (isEmpty)
|
|
||||||
{
|
|
||||||
rectangle.Width = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rectangle.Width += 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rectangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float CalibrateTextHeight(Font font)
|
|
||||||
{
|
|
||||||
// This is a pixel-wise calibration that tests the offset of a reference character because Windows text measurement
|
|
||||||
// is horrible when compared to other frameworks like Cairo and diverge across systems and fonts.
|
|
||||||
|
|
||||||
Debug.Assert(font.Unit == GraphicsUnit.Pixel);
|
|
||||||
|
|
||||||
var surfaceSize = (int)Math.Ceiling(2 * font.Size);
|
|
||||||
|
|
||||||
string calibrationText = "|";
|
|
||||||
|
|
||||||
using (var surface = new Bitmap(surfaceSize, surfaceSize, PixelFormat.Format32bppArgb))
|
|
||||||
using (var graphics = CreateGraphics(surface))
|
|
||||||
{
|
|
||||||
var measuredRectangle = MeasureString(graphics, calibrationText, font);
|
|
||||||
|
|
||||||
Debug.Assert(measuredRectangle.Right <= surfaceSize);
|
|
||||||
Debug.Assert(measuredRectangle.Bottom <= surfaceSize);
|
|
||||||
|
|
||||||
var textPosition = new PointF(0, 0);
|
|
||||||
|
|
||||||
graphics.Clear(Color.Transparent);
|
|
||||||
DrawString(graphics, calibrationText, font, Brushes.White, textPosition);
|
|
||||||
|
|
||||||
var lockRectangle = new Rectangle(0, 0, surface.Width, surface.Height);
|
|
||||||
var surfaceData = surface.LockBits(lockRectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
|
||||||
var surfaceBytes = new byte[surfaceData.Stride * surfaceData.Height];
|
|
||||||
|
|
||||||
Marshal.Copy(surfaceData.Scan0, surfaceBytes, 0, surfaceBytes.Length);
|
|
||||||
|
|
||||||
Point topLeft = new Point();
|
|
||||||
Point bottomLeft = new Point();
|
|
||||||
|
|
||||||
bool foundTopLeft = false;
|
|
||||||
|
|
||||||
for (int y = 0; y < surfaceData.Height; y++)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < surfaceData.Stride; x += 4)
|
|
||||||
{
|
|
||||||
int position = y * surfaceData.Stride + x;
|
|
||||||
|
|
||||||
if (surfaceBytes[position] != 0)
|
|
||||||
{
|
|
||||||
if (!foundTopLeft)
|
|
||||||
{
|
|
||||||
topLeft.X = x;
|
|
||||||
topLeft.Y = y;
|
|
||||||
foundTopLeft = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bottomLeft.X = x;
|
|
||||||
bottomLeft.Y = y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bottomLeft.Y - topLeft.Y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawString(System.Drawing.Graphics graphics, string text, Font font, Brush brush, PointF point)
|
|
||||||
{
|
|
||||||
var format = CreateStringFormat(text);
|
|
||||||
graphics.DrawString(text, font, brush, point, format);
|
|
||||||
}
|
|
||||||
|
|
||||||
private System.Drawing.Graphics CreateGraphics()
|
|
||||||
{
|
|
||||||
return CreateGraphics(_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
private System.Drawing.Graphics CreateGraphics(Image surface)
|
|
||||||
{
|
|
||||||
var graphics = System.Drawing.Graphics.FromImage(surface);
|
|
||||||
|
|
||||||
graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
|
|
||||||
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
|
|
||||||
graphics.CompositingQuality = CompositingQuality.HighSpeed;
|
|
||||||
graphics.CompositingMode = CompositingMode.SourceOver;
|
|
||||||
graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
|
|
||||||
graphics.SmoothingMode = SmoothingMode.HighSpeed;
|
|
||||||
|
|
||||||
return graphics;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawTextBox(System.Drawing.Graphics graphics)
|
|
||||||
{
|
|
||||||
var inputTextRectangle = MeasureString(graphics, _inputText, _inputTextFont);
|
|
||||||
|
|
||||||
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
|
||||||
float boxHeight = 32;
|
|
||||||
float boxY = 110;
|
|
||||||
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
|
||||||
|
|
||||||
graphics.DrawRectangle(_textBoxOutlinePen, boxX, boxY, boxWidth, boxHeight);
|
|
||||||
|
|
||||||
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
|
||||||
float inputTextY = boxY + boxHeight - inputTextRectangle.Bottom - 5;
|
|
||||||
|
|
||||||
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
|
||||||
|
|
||||||
DrawString(graphics, _inputText, _inputTextFont, _textNormalBrush, inputTextPosition);
|
|
||||||
|
|
||||||
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
|
||||||
|
|
||||||
Brush cursorTextBrush;
|
|
||||||
Brush cursorBrush;
|
|
||||||
Pen cursorPen;
|
|
||||||
|
|
||||||
float cursorPositionYBottom = inputTextY + inputTextRectangle.Bottom;
|
|
||||||
float cursorPositionYTop = cursorPositionYBottom - _inputTextCalibrationHeight - 2;
|
|
||||||
float cursorPositionXLeft;
|
|
||||||
float cursorPositionXRight;
|
|
||||||
|
|
||||||
bool cursorVisible = false;
|
|
||||||
|
|
||||||
if (_cursorStart != _cursorEnd)
|
|
||||||
{
|
|
||||||
cursorTextBrush = _textSelectedBrush;
|
|
||||||
cursorBrush = _selectionBoxBrush;
|
|
||||||
cursorPen = _selectionBoxPen;
|
|
||||||
|
|
||||||
string textUntilBegin = _inputText.Substring(0, _cursorStart);
|
|
||||||
string textUntilEnd = _inputText.Substring(0, _cursorEnd);
|
|
||||||
|
|
||||||
RectangleF selectionBeginRectangle = MeasureString(graphics, textUntilBegin, _inputTextFont);
|
|
||||||
RectangleF selectionEndRectangle = MeasureString(graphics, textUntilEnd , _inputTextFont);
|
|
||||||
|
|
||||||
cursorVisible = true;
|
|
||||||
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
|
||||||
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorTextBrush = _textOverCursorBrush;
|
|
||||||
cursorBrush = _cursorBrush;
|
|
||||||
cursorPen = _cursorPen;
|
|
||||||
|
|
||||||
if (Volatile.Read(ref _textBoxBlinkCounter.Value) < TextBoxBlinkThreshold)
|
|
||||||
{
|
|
||||||
// Show the blinking cursor.
|
|
||||||
|
|
||||||
int cursorStart = Math.Min(_inputText.Length, _cursorStart);
|
|
||||||
string textUntilCursor = _inputText.Substring(0, cursorStart);
|
|
||||||
RectangleF cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
|
|
||||||
|
|
||||||
cursorVisible = true;
|
|
||||||
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
||||||
|
|
||||||
if (_overwriteMode)
|
|
||||||
{
|
|
||||||
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
|
||||||
|
|
||||||
if (_cursorStart < _inputText.Length)
|
|
||||||
{
|
|
||||||
textUntilCursor = _inputText.Substring(0, cursorStart + 1);
|
|
||||||
cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
|
|
||||||
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The blinking cursor is in insert mode so it is only a line.
|
|
||||||
cursorPositionXRight = cursorPositionXLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorPositionXLeft = inputTextX;
|
|
||||||
cursorPositionXRight = inputTextX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_typingEnabled && cursorVisible)
|
|
||||||
{
|
|
||||||
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
|
||||||
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
|
||||||
|
|
||||||
if (cursorWidth == 0)
|
|
||||||
{
|
|
||||||
graphics.DrawLine(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorPositionXLeft, cursorPositionYBottom);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
graphics.DrawRectangle(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
graphics.FillRectangle(cursorBrush, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
|
|
||||||
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
|
|
||||||
var oldClip = graphics.Clip;
|
|
||||||
graphics.Clip = new Region(cursorRectangle);
|
|
||||||
|
|
||||||
DrawString(graphics, _inputText, _inputTextFont, cursorTextBrush, inputTextPosition);
|
|
||||||
|
|
||||||
graphics.Clip = oldClip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!_typingEnabled)
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillRectangle(_disabledBrush, boxX - _textBoxOutlineWidth, boxY - _textBoxOutlineWidth,
|
|
||||||
boxWidth + 2* _textBoxOutlineWidth, boxHeight + 2* _textBoxOutlineWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawPadButton(System.Drawing.Graphics graphics, PointF point, Image icon, string label, bool pressed, bool enabled)
|
|
||||||
{
|
|
||||||
// Use relative positions so we can center the the entire drawing later.
|
|
||||||
|
|
||||||
float iconX = 0;
|
|
||||||
float iconY = 0;
|
|
||||||
float iconWidth = icon.Width;
|
|
||||||
float iconHeight = icon.Height;
|
|
||||||
|
|
||||||
var labelRectangle = MeasureString(graphics, label, _labelsTextFont);
|
|
||||||
|
|
||||||
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
|
||||||
float labelPositionY = (iconHeight - labelRectangle.Height) / 2 - labelRectangle.Y - 1;
|
|
||||||
|
|
||||||
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
|
||||||
float fullHeight = iconHeight;
|
|
||||||
|
|
||||||
// Convert all relative positions into absolute.
|
|
||||||
|
|
||||||
float originX = (int)(point.X - fullWidth / 2);
|
|
||||||
float originY = (int)(point.Y - fullHeight / 2);
|
|
||||||
|
|
||||||
iconX += originX;
|
|
||||||
iconY += originY;
|
|
||||||
|
|
||||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
||||||
|
|
||||||
graphics.DrawImageUnscaled(icon, (int)iconX, (int)iconY);
|
|
||||||
|
|
||||||
DrawString(graphics, label, _labelsTextFont, _textNormalBrush, labelPosition);
|
|
||||||
|
|
||||||
GraphicsPath frame = new GraphicsPath();
|
|
||||||
frame.AddRectangle(new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
|
||||||
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth));
|
|
||||||
|
|
||||||
if (enabled)
|
|
||||||
{
|
|
||||||
if (pressed)
|
|
||||||
{
|
|
||||||
graphics.DrawPath(_padPressedPen, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillPath(_disabledBrush, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawControllerToggle(System.Drawing.Graphics graphics, PointF point, bool enabled)
|
|
||||||
{
|
|
||||||
var labelRectangle = MeasureString(graphics, ControllerToggleText, _labelsTextFont);
|
|
||||||
|
|
||||||
// Use relative positions so we can center the the entire drawing later.
|
|
||||||
|
|
||||||
float keyWidth = _keyModeIcon.Width;
|
|
||||||
float keyHeight = _keyModeIcon.Height;
|
|
||||||
|
|
||||||
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
|
||||||
float labelPositionY = -labelRectangle.Y - 1;
|
|
||||||
|
|
||||||
float keyX = 0;
|
|
||||||
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
|
||||||
|
|
||||||
float fullWidth = labelPositionX + labelRectangle.Width;
|
|
||||||
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
|
||||||
|
|
||||||
// Convert all relative positions into absolute.
|
|
||||||
|
|
||||||
float originX = (int)(point.X - fullWidth / 2);
|
|
||||||
float originY = (int)(point.Y - fullHeight / 2);
|
|
||||||
|
|
||||||
keyX += originX;
|
|
||||||
keyY += originY;
|
|
||||||
|
|
||||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
||||||
var overlayPosition = new Point((int)keyX, (int)keyY);
|
|
||||||
|
|
||||||
graphics.DrawImageUnscaled(_keyModeIcon, overlayPosition);
|
|
||||||
|
|
||||||
DrawString(graphics, ControllerToggleText, _labelsTextFont, _textNormalBrush, labelPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryCopyTo(IVirtualMemoryManager destination, ulong position)
|
|
||||||
{
|
|
||||||
if (_surface == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle lockRectangle = new Rectangle(0, 0, _surface.Width, _surface.Height);
|
|
||||||
BitmapData surfaceData = _surface.LockBits(lockRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
|
||||||
|
|
||||||
Debug.Assert(surfaceData.Stride == _surfaceInfo.Pitch);
|
|
||||||
Debug.Assert(surfaceData.Stride * surfaceData.Height == _surfaceInfo.Size);
|
|
||||||
|
|
||||||
// Convert the pixel format used in System.Drawing to the one required by a Switch Surface.
|
|
||||||
int dataLength = surfaceData.Stride * surfaceData.Height;
|
|
||||||
|
|
||||||
byte[] data = new byte[dataLength];
|
|
||||||
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(data);
|
|
||||||
|
|
||||||
Marshal.Copy(surfaceData.Scan0, data, 0, dataLength);
|
|
||||||
|
|
||||||
for (int i = 0; i < dataConvert.Length; i++)
|
|
||||||
{
|
|
||||||
dataConvert[i] = BitOperations.RotateRight(BinaryPrimitives.ReverseEndianness(dataConvert[i]), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
destination.Write(position, data);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_surface.UnlockBits(surfaceData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
|
|
||||||
{
|
|
||||||
lock (_renderLock)
|
|
||||||
{
|
|
||||||
if (!_surfaceInfo.Equals(surfaceInfo))
|
|
||||||
{
|
|
||||||
_surfaceInfo = surfaceInfo;
|
|
||||||
RecreateSurface();
|
|
||||||
RecomputeConstants();
|
|
||||||
}
|
|
||||||
|
|
||||||
Redraw();
|
|
||||||
|
|
||||||
return TryCopyTo(destination, position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_textBoxBlinkTimedAction.RequestCancel();
|
_textBoxBlinkTimedAction.RequestCancel();
|
||||||
|
_renderAction.RequestCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,585 @@
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class that generates the graphics for the software keyboard applet during inline mode.
|
||||||
|
/// </summary>
|
||||||
|
internal class SoftwareKeyboardRendererBase
|
||||||
|
{
|
||||||
|
public const int TextBoxBlinkThreshold = 8;
|
||||||
|
|
||||||
|
const string MessageText = "Please use the keyboard to input text";
|
||||||
|
const string AcceptText = "Accept";
|
||||||
|
const string CancelText = "Cancel";
|
||||||
|
const string ControllerToggleText = "Toggle input";
|
||||||
|
|
||||||
|
private readonly object _bufferLock = new object();
|
||||||
|
|
||||||
|
private RenderingSurfaceInfo _surfaceInfo = null;
|
||||||
|
private Image<Argb32> _surface = null;
|
||||||
|
private byte[] _bufferData = null;
|
||||||
|
|
||||||
|
private Image _ryujinxLogo = null;
|
||||||
|
private Image _padAcceptIcon = null;
|
||||||
|
private Image _padCancelIcon = null;
|
||||||
|
private Image _keyModeIcon = null;
|
||||||
|
|
||||||
|
private float _textBoxOutlineWidth;
|
||||||
|
private float _padPressedPenWidth;
|
||||||
|
|
||||||
|
private Color _textNormalColor;
|
||||||
|
private Color _textSelectedColor;
|
||||||
|
private Color _textOverCursorColor;
|
||||||
|
|
||||||
|
private IBrush _panelBrush;
|
||||||
|
private IBrush _disabledBrush;
|
||||||
|
private IBrush _cursorBrush;
|
||||||
|
private IBrush _selectionBoxBrush;
|
||||||
|
|
||||||
|
private Pen _textBoxOutlinePen;
|
||||||
|
private Pen _cursorPen;
|
||||||
|
private Pen _selectionBoxPen;
|
||||||
|
private Pen _padPressedPen;
|
||||||
|
|
||||||
|
private int _inputTextFontSize;
|
||||||
|
private Font _messageFont;
|
||||||
|
private Font _inputTextFont;
|
||||||
|
private Font _labelsTextFont;
|
||||||
|
|
||||||
|
private RectangleF _panelRectangle;
|
||||||
|
private Point _logoPosition;
|
||||||
|
private float _messagePositionY;
|
||||||
|
|
||||||
|
public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
|
||||||
|
{
|
||||||
|
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
|
||||||
|
int ryujinxLogoSize = 32;
|
||||||
|
|
||||||
|
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
|
||||||
|
|
||||||
|
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
||||||
|
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
||||||
|
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
||||||
|
|
||||||
|
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
||||||
|
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
||||||
|
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
||||||
|
|
||||||
|
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
||||||
|
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
||||||
|
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
||||||
|
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
||||||
|
|
||||||
|
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
|
||||||
|
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
|
||||||
|
_textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
||||||
|
|
||||||
|
float cursorWidth = 2;
|
||||||
|
|
||||||
|
_textBoxOutlineWidth = 2;
|
||||||
|
_padPressedPenWidth = 2;
|
||||||
|
|
||||||
|
_panelBrush = new SolidBrush(panelColor);
|
||||||
|
_disabledBrush = new SolidBrush(panelTransparentColor);
|
||||||
|
_cursorBrush = new SolidBrush(_textNormalColor);
|
||||||
|
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
||||||
|
|
||||||
|
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
||||||
|
_cursorPen = new Pen(_textNormalColor, cursorWidth);
|
||||||
|
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
||||||
|
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
||||||
|
|
||||||
|
_inputTextFontSize = 20;
|
||||||
|
|
||||||
|
CreateFonts(uiTheme.FontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateFonts(string uiThemeFontFamily)
|
||||||
|
{
|
||||||
|
// Try a list of fonts in case any of them is not available in the system.
|
||||||
|
|
||||||
|
string[] availableFonts = new string[]
|
||||||
|
{
|
||||||
|
uiThemeFontFamily,
|
||||||
|
"Liberation Sans",
|
||||||
|
"FreeSans",
|
||||||
|
"DejaVu Sans"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string fontFamily in availableFonts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
|
||||||
|
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
|
||||||
|
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
||||||
|
{
|
||||||
|
var a = (byte)(color.A * 255);
|
||||||
|
var r = (byte)(color.R * 255);
|
||||||
|
var g = (byte)(color.G * 255);
|
||||||
|
var b = (byte)(color.B * 255);
|
||||||
|
|
||||||
|
if (flipRgb)
|
||||||
|
{
|
||||||
|
r = (byte)(255 - r);
|
||||||
|
g = (byte)(255 - g);
|
||||||
|
b = (byte)(255 - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
||||||
|
{
|
||||||
|
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
||||||
|
|
||||||
|
Debug.Assert(resourceStream != null);
|
||||||
|
|
||||||
|
var image = Image.Load(resourceStream);
|
||||||
|
|
||||||
|
if (newHeight != 0 && newWidth != 0)
|
||||||
|
{
|
||||||
|
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGraphicsOptions(IImageProcessingContext context)
|
||||||
|
{
|
||||||
|
context.GetGraphicsOptions().Antialias = true;
|
||||||
|
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawImmutableElements()
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface.Mutate(context =>
|
||||||
|
{
|
||||||
|
SetGraphicsOptions(context);
|
||||||
|
|
||||||
|
context.Clear(Color.Transparent);
|
||||||
|
context.Fill(_panelBrush, _panelRectangle);
|
||||||
|
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
|
||||||
|
|
||||||
|
float halfWidth = _panelRectangle.Width / 2;
|
||||||
|
float buttonsY = _panelRectangle.Y + 185;
|
||||||
|
|
||||||
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||||
|
|
||||||
|
DrawControllerToggle(context, disableButtonPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawMutableElements(SoftwareKeyboardUiState state)
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface.Mutate(context =>
|
||||||
|
{
|
||||||
|
var messageRectangle = MeasureString(MessageText, _messageFont);
|
||||||
|
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
||||||
|
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
||||||
|
var messagePosition = new PointF(messagePositionX, messagePositionY);
|
||||||
|
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
||||||
|
|
||||||
|
SetGraphicsOptions(context);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, messageBoundRectangle);
|
||||||
|
|
||||||
|
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
|
||||||
|
|
||||||
|
if (!state.TypingEnabled)
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, messageBoundRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTextBox(context, state);
|
||||||
|
|
||||||
|
float halfWidth = _panelRectangle.Width / 2;
|
||||||
|
float buttonsY = _panelRectangle.Y + 185;
|
||||||
|
|
||||||
|
PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
|
||||||
|
PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
|
||||||
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||||
|
|
||||||
|
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
|
||||||
|
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
|
||||||
|
{
|
||||||
|
if (_surfaceInfo != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surfaceInfo = surfaceInfo;
|
||||||
|
|
||||||
|
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
||||||
|
|
||||||
|
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
||||||
|
// image if the pitch is different.
|
||||||
|
uint totalWidth = _surfaceInfo.Pitch / 4;
|
||||||
|
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
||||||
|
|
||||||
|
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
||||||
|
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
||||||
|
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
||||||
|
|
||||||
|
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
|
||||||
|
|
||||||
|
ComputeConstants();
|
||||||
|
DrawImmutableElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeConstants()
|
||||||
|
{
|
||||||
|
int totalWidth = (int)_surfaceInfo.Width;
|
||||||
|
int totalHeight = (int)_surfaceInfo.Height;
|
||||||
|
|
||||||
|
int panelHeight = 240;
|
||||||
|
int panelPositionY = totalHeight - panelHeight;
|
||||||
|
|
||||||
|
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
|
||||||
|
|
||||||
|
_messagePositionY = panelPositionY + 60;
|
||||||
|
|
||||||
|
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
||||||
|
int logoPositionY = panelPositionY + 18;
|
||||||
|
|
||||||
|
_logoPosition = new Point(logoPositionX, logoPositionY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectangleF MeasureString(string text, Font font)
|
||||||
|
{
|
||||||
|
RendererOptions options = new RendererOptions(font);
|
||||||
|
FontRectangle rectangle = TextMeasurer.Measure(text == "" ? " " : text, options);
|
||||||
|
|
||||||
|
if (text == "")
|
||||||
|
{
|
||||||
|
return new RectangleF(0, rectangle.Y, 0, rectangle.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
|
||||||
|
{
|
||||||
|
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
|
||||||
|
|
||||||
|
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
||||||
|
float boxHeight = 32;
|
||||||
|
float boxY = _panelRectangle.Y + 110;
|
||||||
|
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
||||||
|
|
||||||
|
RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
|
||||||
|
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, boundRectangle);
|
||||||
|
|
||||||
|
context.Draw(_textBoxOutlinePen, boxRectangle);
|
||||||
|
|
||||||
|
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
||||||
|
float inputTextY = boxY + 5;
|
||||||
|
|
||||||
|
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
||||||
|
|
||||||
|
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
|
||||||
|
|
||||||
|
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
||||||
|
|
||||||
|
Color cursorTextColor;
|
||||||
|
IBrush cursorBrush;
|
||||||
|
Pen cursorPen;
|
||||||
|
|
||||||
|
float cursorPositionYTop = inputTextY + 1;
|
||||||
|
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
|
||||||
|
float cursorPositionXLeft;
|
||||||
|
float cursorPositionXRight;
|
||||||
|
|
||||||
|
bool cursorVisible = false;
|
||||||
|
|
||||||
|
if (state.CursorBegin != state.CursorEnd)
|
||||||
|
{
|
||||||
|
Debug.Assert(state.InputText.Length > 0);
|
||||||
|
|
||||||
|
cursorTextColor = _textSelectedColor;
|
||||||
|
cursorBrush = _selectionBoxBrush;
|
||||||
|
cursorPen = _selectionBoxPen;
|
||||||
|
|
||||||
|
string textUntilBegin = state.InputText.Substring(0, state.CursorBegin);
|
||||||
|
string textUntilEnd = state.InputText.Substring(0, state.CursorEnd);
|
||||||
|
|
||||||
|
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
|
||||||
|
var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
|
||||||
|
|
||||||
|
cursorVisible = true;
|
||||||
|
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
||||||
|
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorTextColor = _textOverCursorColor;
|
||||||
|
cursorBrush = _cursorBrush;
|
||||||
|
cursorPen = _cursorPen;
|
||||||
|
|
||||||
|
if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
|
||||||
|
{
|
||||||
|
// Show the blinking cursor.
|
||||||
|
|
||||||
|
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
|
||||||
|
string textUntilCursor = state.InputText.Substring(0, cursorBegin);
|
||||||
|
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||||
|
|
||||||
|
cursorVisible = true;
|
||||||
|
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||||
|
|
||||||
|
if (state.OverwriteMode)
|
||||||
|
{
|
||||||
|
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
||||||
|
|
||||||
|
if (state.CursorBegin < state.InputText.Length)
|
||||||
|
{
|
||||||
|
textUntilCursor = state.InputText.Substring(0, cursorBegin + 1);
|
||||||
|
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||||
|
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The blinking cursor is in insert mode so it is only a line.
|
||||||
|
cursorPositionXRight = cursorPositionXLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorPositionXLeft = inputTextX;
|
||||||
|
cursorPositionXRight = inputTextX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.TypingEnabled && cursorVisible)
|
||||||
|
{
|
||||||
|
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
||||||
|
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
||||||
|
|
||||||
|
if (cursorWidth == 0)
|
||||||
|
{
|
||||||
|
PointF[] points = new PointF[]
|
||||||
|
{
|
||||||
|
new PointF(cursorPositionXLeft, cursorPositionYTop),
|
||||||
|
new PointF(cursorPositionXLeft, cursorPositionYBottom),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.DrawLines(cursorPen, points);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
||||||
|
|
||||||
|
context.Draw(cursorPen , cursorRectangle);
|
||||||
|
context.Fill(cursorBrush, cursorRectangle);
|
||||||
|
|
||||||
|
Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
|
||||||
|
textOverCursor.Mutate(context =>
|
||||||
|
{
|
||||||
|
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
|
||||||
|
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
|
||||||
|
});
|
||||||
|
|
||||||
|
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
|
||||||
|
context.DrawImage(textOverCursor, cursorPosition, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!state.TypingEnabled)
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, boundRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
|
||||||
|
{
|
||||||
|
// Use relative positions so we can center the the entire drawing later.
|
||||||
|
|
||||||
|
float iconX = 0;
|
||||||
|
float iconY = 0;
|
||||||
|
float iconWidth = icon.Width;
|
||||||
|
float iconHeight = icon.Height;
|
||||||
|
|
||||||
|
var labelRectangle = MeasureString(label, _labelsTextFont);
|
||||||
|
|
||||||
|
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
||||||
|
float labelPositionY = 3;
|
||||||
|
|
||||||
|
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
||||||
|
float fullHeight = iconHeight;
|
||||||
|
|
||||||
|
// Convert all relative positions into absolute.
|
||||||
|
|
||||||
|
float originX = (int)(point.X - fullWidth / 2);
|
||||||
|
float originY = (int)(point.Y - fullHeight / 2);
|
||||||
|
|
||||||
|
iconX += originX;
|
||||||
|
iconY += originY;
|
||||||
|
|
||||||
|
var iconPosition = new Point((int)iconX, (int)iconY);
|
||||||
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||||
|
|
||||||
|
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
||||||
|
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
|
||||||
|
|
||||||
|
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
|
||||||
|
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, boundRectangle);
|
||||||
|
context.DrawImage(icon, iconPosition, 1);
|
||||||
|
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
if (pressed)
|
||||||
|
{
|
||||||
|
context.Draw(_padPressedPen, selectedRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, boundRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
|
||||||
|
{
|
||||||
|
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
|
||||||
|
|
||||||
|
// Use relative positions so we can center the the entire drawing later.
|
||||||
|
|
||||||
|
float keyWidth = _keyModeIcon.Width;
|
||||||
|
float keyHeight = _keyModeIcon.Height;
|
||||||
|
|
||||||
|
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
||||||
|
float labelPositionY = -labelRectangle.Y - 1;
|
||||||
|
|
||||||
|
float keyX = 0;
|
||||||
|
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
||||||
|
|
||||||
|
float fullWidth = labelPositionX + labelRectangle.Width;
|
||||||
|
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
||||||
|
|
||||||
|
// Convert all relative positions into absolute.
|
||||||
|
|
||||||
|
float originX = (int)(point.X - fullWidth / 2);
|
||||||
|
float originY = (int)(point.Y - fullHeight / 2);
|
||||||
|
|
||||||
|
keyX += originX;
|
||||||
|
keyY += originY;
|
||||||
|
|
||||||
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||||
|
var overlayPosition = new Point((int)keyX, (int)keyY);
|
||||||
|
|
||||||
|
context.DrawImage(_keyModeIcon, overlayPosition, 1);
|
||||||
|
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyImageToBuffer()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the pixel format used in the image to the one used in the Switch surface.
|
||||||
|
|
||||||
|
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
|
||||||
|
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
||||||
|
|
||||||
|
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
||||||
|
|
||||||
|
for (int i = 0; i < dataConvert.Length; i++)
|
||||||
|
{
|
||||||
|
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_bufferData == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
destination.Write(position, _bufferData);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TODO
|
||||||
|
/// </summary>
|
||||||
|
internal class SoftwareKeyboardUiState
|
||||||
|
{
|
||||||
|
public string InputText = "";
|
||||||
|
public int CursorBegin = 0;
|
||||||
|
public int CursorEnd = 0;
|
||||||
|
public bool AcceptPressed = false;
|
||||||
|
public bool CancelPressed = false;
|
||||||
|
public bool OverwriteMode = false;
|
||||||
|
public bool TypingEnabled = true;
|
||||||
|
public bool ControllerEnabled = true;
|
||||||
|
public int TextBoxBlinkCounter = 0;
|
||||||
|
|
||||||
|
public RenderingSurfaceInfo SurfaceInfo = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,6 +144,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
}), cancelled);
|
}), cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset(Action action)
|
||||||
|
{
|
||||||
|
// Create a dedicated cancel token for each task.
|
||||||
|
var cancelled = new TRef<bool>(false);
|
||||||
|
|
||||||
|
Reset(new Thread(() =>
|
||||||
|
{
|
||||||
|
while (!Volatile.Read(ref cancelled.Value))
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}), cancelled);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
|
private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < substepData.SleepCount; i++)
|
for (int i = 0; i < substepData.SleepCount; i++)
|
||||||
|
|
|
@ -25,6 +25,7 @@ using System.Reflection;
|
||||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||||
using static Ryujinx.HLE.HOS.ModLoader;
|
using static Ryujinx.HLE.HOS.ModLoader;
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS
|
namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
|
@ -101,9 +102,11 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage());
|
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
|
||||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
{
|
{
|
||||||
patchNca = nca;
|
patchNca = nca;
|
||||||
}
|
}
|
||||||
|
@ -143,9 +146,11 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage());
|
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
|
||||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||||
|
|
||||||
|
@ -335,7 +340,14 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
||||||
{
|
{
|
||||||
_device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled);
|
if (File.Exists(dlcContainer.Path))
|
||||||
|
{
|
||||||
|
_device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {dlcContainer.Path}. It may have been moved or renamed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,7 +434,9 @@ namespace Ryujinx.HLE.HOS
|
||||||
// Sets TitleId, so be sure to call before using it
|
// Sets TitleId, so be sure to call before using it
|
||||||
private MetaLoader ReadNpdm(IFileSystem fs)
|
private MetaLoader ReadNpdm(IFileSystem fs)
|
||||||
{
|
{
|
||||||
Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
|
using var npdmFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
Result result = fs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
MetaLoader metaData;
|
MetaLoader metaData;
|
||||||
|
|
||||||
|
@ -434,10 +448,10 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
npdmFile.GetSize(out long fileSize).ThrowIfFailure();
|
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||||
|
|
||||||
var npdmBuffer = new byte[fileSize];
|
var npdmBuffer = new byte[fileSize];
|
||||||
npdmFile.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||||
|
|
||||||
metaData = new MetaLoader();
|
metaData = new MetaLoader();
|
||||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||||
|
@ -454,12 +468,14 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
||||||
{
|
{
|
||||||
|
using var controlFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
||||||
Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
|
Result result = controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
if (result.IsSuccess())
|
if (result.IsSuccess())
|
||||||
{
|
{
|
||||||
result = controlFile.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
||||||
|
|
||||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||||
{
|
{
|
||||||
|
@ -501,9 +517,11 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||||
|
|
||||||
codeFs.OpenFile(out IFile nsoFile, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
using var nsoFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
nsos[i] = new NsoExecutable(nsoFile.AsStorage(), name);
|
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExeFs file replacements
|
// ExeFs file replacements
|
||||||
|
@ -673,9 +691,11 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage());
|
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Program)
|
if (nca.Header.ContentType != NcaContentType.Program)
|
||||||
{
|
{
|
||||||
|
@ -684,7 +704,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using LibHac.Common;
|
||||||
using LibHac.Common.Keys;
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
|
@ -243,6 +244,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
AudioOutputManager = new AudioOutputManager();
|
AudioOutputManager = new AudioOutputManager();
|
||||||
AudioInputManager = new AudioInputManager();
|
AudioInputManager = new AudioInputManager();
|
||||||
AudioRendererManager = new AudioRendererManager();
|
AudioRendererManager = new AudioRendererManager();
|
||||||
|
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
|
||||||
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
|
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
|
||||||
|
|
||||||
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
|
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
|
||||||
|
@ -255,6 +257,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
|
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
|
||||||
|
AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
|
||||||
|
|
||||||
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
|
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
|
||||||
|
|
||||||
|
@ -304,9 +307,9 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
public void LoadKip(string kipPath)
|
public void LoadKip(string kipPath)
|
||||||
{
|
{
|
||||||
using IStorage kipFile = new LocalStorage(kipPath, FileAccess.Read);
|
using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
|
||||||
|
|
||||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile));
|
ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeDockedModeState(bool newState)
|
public void ChangeDockedModeState(bool newState)
|
||||||
|
@ -326,6 +329,17 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume)
|
||||||
|
{
|
||||||
|
AudioOutputManager.SetVolume(volume);
|
||||||
|
AudioRendererManager.SetVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetVolume()
|
||||||
|
{
|
||||||
|
return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume();
|
||||||
|
}
|
||||||
|
|
||||||
public void ReturnFocus()
|
public void ReturnFocus()
|
||||||
{
|
{
|
||||||
AppletState.SetFocus(true);
|
AppletState.SetFocus(true);
|
||||||
|
@ -404,7 +418,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
lock (KernelContext.Processes)
|
lock (KernelContext.Processes)
|
||||||
{
|
{
|
||||||
// Terminate application.
|
// Terminate application.
|
||||||
foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.Flags.HasFlag(ProcessCreationFlags.IsApplication)))
|
foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.IsApplication))
|
||||||
{
|
{
|
||||||
process.Terminate();
|
process.Terminate();
|
||||||
process.DecrementReferenceCount();
|
process.DecrementReferenceCount();
|
||||||
|
@ -415,7 +429,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
// Terminate HLE services (must be done after the application is already terminated,
|
// Terminate HLE services (must be done after the application is already terminated,
|
||||||
// otherwise the application will receive errors due to service termination).
|
// otherwise the application will receive errors due to service termination).
|
||||||
foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.Flags.HasFlag(ProcessCreationFlags.IsApplication)))
|
foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.IsApplication))
|
||||||
{
|
{
|
||||||
process.Terminate();
|
process.Terminate();
|
||||||
process.DecrementReferenceCount();
|
process.DecrementReferenceCount();
|
||||||
|
@ -461,7 +475,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
foreach (KProcess process in KernelContext.Processes.Values)
|
foreach (KProcess process in KernelContext.Processes.Values)
|
||||||
{
|
{
|
||||||
if (process.Flags.HasFlag(ProcessCreationFlags.IsApplication))
|
if (process.IsApplication)
|
||||||
{
|
{
|
||||||
// Only game process should be paused.
|
// Only game process should be paused.
|
||||||
process.SetActivity(pause);
|
process.SetActivity(pause);
|
||||||
|
|
|
@ -58,6 +58,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
return DramMemoryMap.DramBase + GetDramSize(size);
|
return DramMemoryMap.DramBase + GetDramSize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ulong GenerateRandom()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static ulong GetDramSize(MemorySize size)
|
public static ulong GetDramSize(MemorySize size)
|
||||||
{
|
{
|
||||||
return size switch
|
return size switch
|
||||||
|
|
|
@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
{
|
{
|
||||||
class KTimeManager : IDisposable
|
class KTimeManager : IDisposable
|
||||||
{
|
{
|
||||||
|
public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
|
||||||
|
|
||||||
private class WaitingObject
|
private class WaitingObject
|
||||||
{
|
{
|
||||||
public IKFutureSchedulerObject Object { get; }
|
public IKFutureSchedulerObject Object { get; }
|
||||||
|
@ -24,6 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
private readonly List<WaitingObject> _waitingObjects;
|
private readonly List<WaitingObject> _waitingObjects;
|
||||||
private AutoResetEvent _waitEvent;
|
private AutoResetEvent _waitEvent;
|
||||||
private bool _keepRunning;
|
private bool _keepRunning;
|
||||||
|
private long _enforceWakeupFromSpinWait;
|
||||||
|
|
||||||
public KTimeManager(KernelContext context)
|
public KTimeManager(KernelContext context)
|
||||||
{
|
{
|
||||||
|
@ -41,11 +44,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
|
|
||||||
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
|
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
|
||||||
{
|
{
|
||||||
long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout);
|
long timePoint = PerformanceCounter.ElapsedTicks + ConvertNanosecondsToHostTicks(timeout);
|
||||||
|
|
||||||
lock (_context.CriticalSection.Lock)
|
lock (_context.CriticalSection.Lock)
|
||||||
{
|
{
|
||||||
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
||||||
|
|
||||||
|
if (timeout < 1000000)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitEvent.Set();
|
_waitEvent.Set();
|
||||||
|
@ -61,27 +69,51 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
|
|
||||||
private void WaitAndCheckScheduledObjects()
|
private void WaitAndCheckScheduledObjects()
|
||||||
{
|
{
|
||||||
|
SpinWait spinWait = new SpinWait();
|
||||||
|
WaitingObject next;
|
||||||
|
|
||||||
using (_waitEvent = new AutoResetEvent(false))
|
using (_waitEvent = new AutoResetEvent(false))
|
||||||
{
|
{
|
||||||
while (_keepRunning)
|
while (_keepRunning)
|
||||||
{
|
{
|
||||||
WaitingObject next;
|
|
||||||
|
|
||||||
lock (_context.CriticalSection.Lock)
|
lock (_context.CriticalSection.Lock)
|
||||||
{
|
{
|
||||||
|
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
|
||||||
|
|
||||||
next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
|
next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next != null)
|
if (next != null)
|
||||||
{
|
{
|
||||||
long timePoint = PerformanceCounter.ElapsedMilliseconds;
|
long timePoint = PerformanceCounter.ElapsedTicks;
|
||||||
|
|
||||||
if (next.TimePoint > timePoint)
|
if (next.TimePoint > timePoint)
|
||||||
{
|
{
|
||||||
_waitEvent.WaitOne((int)(next.TimePoint - timePoint));
|
long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue);
|
||||||
|
|
||||||
|
if (ms > 0)
|
||||||
|
{
|
||||||
|
_waitEvent.WaitOne((int)ms);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint)
|
||||||
|
{
|
||||||
|
if (spinWait.NextSpinWillYield)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
|
||||||
|
spinWait.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
spinWait.SpinOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
spinWait.Reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint;
|
bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint;
|
||||||
|
|
||||||
if (timeUp)
|
if (timeUp)
|
||||||
{
|
{
|
||||||
|
@ -119,6 +151,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
return time * 1000000;
|
return time * 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long ConvertNanosecondsToHostTicks(long ns)
|
||||||
|
{
|
||||||
|
long nsDiv = ns / 1000000000;
|
||||||
|
long nsMod = ns % 1000000000;
|
||||||
|
long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000;
|
||||||
|
long tickMod = PerformanceCounter.TicksPerSecond % 1000000000;
|
||||||
|
|
||||||
|
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000;
|
||||||
|
return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long ConvertGuestTicksToNanoseconds(long ticks)
|
||||||
|
{
|
||||||
|
return (long)Math.Ceiling(ticks * (1000000000.0 / 19200000.0));
|
||||||
|
}
|
||||||
|
|
||||||
public static long ConvertHostTicksToTicks(long time)
|
public static long ConvertHostTicksToTicks(long time)
|
||||||
{
|
{
|
||||||
return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);
|
return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);
|
||||||
|
|
|
@ -60,6 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
public KProcessCapabilities Capabilities { get; private set; }
|
public KProcessCapabilities Capabilities { get; private set; }
|
||||||
|
|
||||||
public ulong TitleId { get; private set; }
|
public ulong TitleId { get; private set; }
|
||||||
|
public bool IsApplication { get; private set; }
|
||||||
public long Pid { get; private set; }
|
public long Pid { get; private set; }
|
||||||
|
|
||||||
private long _creationTimestamp;
|
private long _creationTimestamp;
|
||||||
|
@ -193,6 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
_memRegion = memRegion;
|
_memRegion = memRegion;
|
||||||
_contextFactory = contextFactory ?? new ProcessContextFactory();
|
_contextFactory = contextFactory ?? new ProcessContextFactory();
|
||||||
_customThreadStart = customThreadStart;
|
_customThreadStart = customThreadStart;
|
||||||
|
IsApplication = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication);
|
||||||
|
|
||||||
ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
|
ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
|
||||||
|
|
||||||
|
|
33
Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
Normal file
33
Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
{
|
||||||
|
enum InfoType : uint
|
||||||
|
{
|
||||||
|
CoreMask,
|
||||||
|
PriorityMask,
|
||||||
|
AliasRegionAddress,
|
||||||
|
AliasRegionSize,
|
||||||
|
HeapRegionAddress,
|
||||||
|
HeapRegionSize,
|
||||||
|
TotalMemorySize,
|
||||||
|
UsedMemorySize,
|
||||||
|
DebuggerAttached,
|
||||||
|
ResourceLimit,
|
||||||
|
IdleTickCount,
|
||||||
|
RandomEntropy,
|
||||||
|
AslrRegionAddress,
|
||||||
|
AslrRegionSize,
|
||||||
|
StackRegionAddress,
|
||||||
|
StackRegionSize,
|
||||||
|
SystemResourceSizeTotal,
|
||||||
|
SystemResourceSizeUsed,
|
||||||
|
ProgramId,
|
||||||
|
// NOTE: Added in 4.0.0, removed in 5.0.0.
|
||||||
|
InitialProcessIdRange,
|
||||||
|
UserExceptionContextAddress,
|
||||||
|
TotalNonSystemMemorySize,
|
||||||
|
UsedNonSystemMemorySize,
|
||||||
|
IsApplication,
|
||||||
|
FreeThreadCount,
|
||||||
|
ThreadTickCount
|
||||||
|
}
|
||||||
|
}
|
|
@ -506,6 +506,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
return KernelResult.UserCopyFailed;
|
return KernelResult.UserCopyFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
return ReplyAndReceive(handles, replyTargetHandle, timeout, out handleIndex);
|
return ReplyAndReceive(handles, replyTargetHandle, timeout, out handleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,6 +552,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
if (result == KernelResult.Success)
|
if (result == KernelResult.Success)
|
||||||
{
|
{
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
|
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
|
||||||
{
|
{
|
||||||
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
|
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
|
||||||
|
@ -644,6 +654,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
if (result == KernelResult.Success)
|
if (result == KernelResult.Success)
|
||||||
{
|
{
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
|
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
|
||||||
{
|
{
|
||||||
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
|
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
|
||||||
|
@ -1560,30 +1575,32 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
Logger.Warning?.Print(LogClass.KernelSvc, str);
|
Logger.Warning?.Print(LogClass.KernelSvc, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KernelResult GetInfo(uint id, int handle, long subId, out long value)
|
public KernelResult GetInfo(InfoType id, int handle, long subId, out long value)
|
||||||
{
|
{
|
||||||
value = 0;
|
value = 0;
|
||||||
|
|
||||||
switch (id)
|
switch (id)
|
||||||
{
|
{
|
||||||
case 0:
|
case InfoType.CoreMask:
|
||||||
case 1:
|
case InfoType.PriorityMask:
|
||||||
case 2:
|
case InfoType.AliasRegionAddress:
|
||||||
case 3:
|
case InfoType.AliasRegionSize:
|
||||||
case 4:
|
case InfoType.HeapRegionAddress:
|
||||||
case 5:
|
case InfoType.HeapRegionSize:
|
||||||
case 6:
|
case InfoType.TotalMemorySize:
|
||||||
case 7:
|
case InfoType.UsedMemorySize:
|
||||||
case 12:
|
case InfoType.AslrRegionAddress:
|
||||||
case 13:
|
case InfoType.AslrRegionSize:
|
||||||
case 14:
|
case InfoType.StackRegionAddress:
|
||||||
case 15:
|
case InfoType.StackRegionSize:
|
||||||
case 16:
|
case InfoType.SystemResourceSizeTotal:
|
||||||
case 17:
|
case InfoType.SystemResourceSizeUsed:
|
||||||
case 18:
|
case InfoType.ProgramId:
|
||||||
case 20:
|
case InfoType.UserExceptionContextAddress:
|
||||||
case 21:
|
case InfoType.TotalNonSystemMemorySize:
|
||||||
case 22:
|
case InfoType.UsedNonSystemMemorySize:
|
||||||
|
case InfoType.IsApplication:
|
||||||
|
case InfoType.FreeThreadCount:
|
||||||
{
|
{
|
||||||
if (subId != 0)
|
if (subId != 0)
|
||||||
{
|
{
|
||||||
|
@ -1601,35 +1618,35 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
switch (id)
|
switch (id)
|
||||||
{
|
{
|
||||||
case 0: value = process.Capabilities.AllowedCpuCoresMask; break;
|
case InfoType.CoreMask: value = process.Capabilities.AllowedCpuCoresMask; break;
|
||||||
case 1: value = process.Capabilities.AllowedThreadPriosMask; break;
|
case InfoType.PriorityMask: value = process.Capabilities.AllowedThreadPriosMask; break;
|
||||||
|
|
||||||
case 2: value = (long)process.MemoryManager.AliasRegionStart; break;
|
case InfoType.AliasRegionAddress: value = (long)process.MemoryManager.AliasRegionStart; break;
|
||||||
case 3:
|
case InfoType.AliasRegionSize:
|
||||||
value = (long)(process.MemoryManager.AliasRegionEnd -
|
value = (long)(process.MemoryManager.AliasRegionEnd -
|
||||||
process.MemoryManager.AliasRegionStart); break;
|
process.MemoryManager.AliasRegionStart); break;
|
||||||
|
|
||||||
case 4: value = (long)process.MemoryManager.HeapRegionStart; break;
|
case InfoType.HeapRegionAddress: value = (long)process.MemoryManager.HeapRegionStart; break;
|
||||||
case 5:
|
case InfoType.HeapRegionSize:
|
||||||
value = (long)(process.MemoryManager.HeapRegionEnd -
|
value = (long)(process.MemoryManager.HeapRegionEnd -
|
||||||
process.MemoryManager.HeapRegionStart); break;
|
process.MemoryManager.HeapRegionStart); break;
|
||||||
|
|
||||||
case 6: value = (long)process.GetMemoryCapacity(); break;
|
case InfoType.TotalMemorySize: value = (long)process.GetMemoryCapacity(); break;
|
||||||
|
|
||||||
case 7: value = (long)process.GetMemoryUsage(); break;
|
case InfoType.UsedMemorySize: value = (long)process.GetMemoryUsage(); break;
|
||||||
|
|
||||||
case 12: value = (long)process.MemoryManager.GetAddrSpaceBaseAddr(); break;
|
case InfoType.AslrRegionAddress: value = (long)process.MemoryManager.GetAddrSpaceBaseAddr(); break;
|
||||||
|
|
||||||
case 13: value = (long)process.MemoryManager.GetAddrSpaceSize(); break;
|
case InfoType.AslrRegionSize: value = (long)process.MemoryManager.GetAddrSpaceSize(); break;
|
||||||
|
|
||||||
case 14: value = (long)process.MemoryManager.StackRegionStart; break;
|
case InfoType.StackRegionAddress: value = (long)process.MemoryManager.StackRegionStart; break;
|
||||||
case 15:
|
case InfoType.StackRegionSize:
|
||||||
value = (long)(process.MemoryManager.StackRegionEnd -
|
value = (long)(process.MemoryManager.StackRegionEnd -
|
||||||
process.MemoryManager.StackRegionStart); break;
|
process.MemoryManager.StackRegionStart); break;
|
||||||
|
|
||||||
case 16: value = (long)process.PersonalMmHeapPagesCount * KPageTableBase.PageSize; break;
|
case InfoType.SystemResourceSizeTotal: value = (long)process.PersonalMmHeapPagesCount * KPageTableBase.PageSize; break;
|
||||||
|
|
||||||
case 17:
|
case InfoType.SystemResourceSizeUsed:
|
||||||
if (process.PersonalMmHeapPagesCount != 0)
|
if (process.PersonalMmHeapPagesCount != 0)
|
||||||
{
|
{
|
||||||
value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize;
|
value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize;
|
||||||
|
@ -1637,19 +1654,33 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 18: value = (long)process.TitleId; break;
|
case InfoType.ProgramId: value = (long)process.TitleId; break;
|
||||||
|
|
||||||
case 20: value = (long)process.UserExceptionContextAddress; break;
|
case InfoType.UserExceptionContextAddress: value = (long)process.UserExceptionContextAddress; break;
|
||||||
|
|
||||||
case 21: value = (long)process.GetMemoryCapacityWithoutPersonalMmHeap(); break;
|
case InfoType.TotalNonSystemMemorySize: value = (long)process.GetMemoryCapacityWithoutPersonalMmHeap(); break;
|
||||||
|
|
||||||
case 22: value = (long)process.GetMemoryUsageWithoutPersonalMmHeap(); break;
|
case InfoType.UsedNonSystemMemorySize: value = (long)process.GetMemoryUsageWithoutPersonalMmHeap(); break;
|
||||||
|
|
||||||
|
case InfoType.IsApplication: value = process.IsApplication ? 1 : 0; break;
|
||||||
|
|
||||||
|
case InfoType.FreeThreadCount:
|
||||||
|
if (process.ResourceLimit != null)
|
||||||
|
{
|
||||||
|
value = process.ResourceLimit.GetLimitValue(LimitableResource.Thread) - process.ResourceLimit.GetCurrentValue(LimitableResource.Thread);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 8:
|
case InfoType.DebuggerAttached:
|
||||||
{
|
{
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -1666,7 +1697,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 9:
|
case InfoType.ResourceLimit:
|
||||||
{
|
{
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -1698,7 +1729,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 10:
|
case InfoType.IdleTickCount:
|
||||||
{
|
{
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -1717,7 +1748,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 11:
|
case InfoType.RandomEntropy:
|
||||||
{
|
{
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -1736,7 +1767,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xf0000002u:
|
case InfoType.ThreadTickCount:
|
||||||
{
|
{
|
||||||
if (subId < -1 || subId > 3)
|
if (subId < -1 || subId > 3)
|
||||||
{
|
{
|
||||||
|
@ -2117,7 +2148,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
KernelStatic.GetCurrentThread().Sleep(timeout);
|
KernelStatic.GetCurrentThread().Sleep(timeout + KTimeManager.DefaultTimeIncrementNanoseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2458,6 +2489,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
KernelResult result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex);
|
KernelResult result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex);
|
||||||
|
|
||||||
if (result == KernelResult.PortRemoteClosed)
|
if (result == KernelResult.PortRemoteClosed)
|
||||||
|
@ -2541,6 +2577,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||||
|
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic(
|
return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic(
|
||||||
mutexAddress,
|
mutexAddress,
|
||||||
condVarAddress,
|
condVarAddress,
|
||||||
|
@ -2571,6 +2612,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||||
|
|
||||||
|
if (timeout > 0)
|
||||||
|
{
|
||||||
|
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
ArbitrationType.WaitIfLessThan
|
ArbitrationType.WaitIfLessThan
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue