merge from master

This commit is contained in:
Logan Stromberg 2021-12-30 16:12:22 -08:00
commit 03f87b3967
195 changed files with 3266 additions and 3507 deletions

View file

@ -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

View file

@ -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>

View file

@ -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;
} }

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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)

View file

@ -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;
} }
} }
} }

View file

@ -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));
} }

View file

@ -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;
} }

View file

@ -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)**

View file

@ -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);

View file

@ -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()

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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;
// Zero the dest buffer
streamSpan.Fill(0);
// Apply volume to written data // Apply volume to written data
SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
}
ulong sampleCount = GetSampleCount(samples.Length); ulong sampleCount = GetSampleCount(samples.Length);

View file

@ -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>

View file

@ -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);

View file

@ -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();

View file

@ -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>

View file

@ -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)
{ {

View file

@ -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
{ {

View file

@ -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;
} }

View file

@ -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++;

View file

@ -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;

View file

@ -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>

View file

@ -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();

View file

@ -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)

View file

@ -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);

View file

@ -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)

View file

@ -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;

View file

@ -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();

View file

@ -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>

View file

@ -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; }
} }
} }

View file

@ -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>

View file

@ -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);
} }

View file

@ -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
{
if (OperatingSystem.IsWindows())
{ {
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX; 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);

View file

@ -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)]

View file

@ -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>

View file

@ -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>

View file

@ -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'">

View file

@ -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);
}
} }
} }

View file

@ -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();

View file

@ -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)
{ {

View file

@ -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

View file

@ -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];

View file

@ -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>

View file

@ -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();

View file

@ -377,6 +377,15 @@ namespace Ryujinx.Graphics.Gpu.Image
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// 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);
}
else
{
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
{ {
if (UpdateScale(texture, bindingInfo, index, stage)) if (UpdateScale(texture, bindingInfo, index, stage))
@ -389,14 +398,6 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture); _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
} }
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// 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);
}
Sampler sampler = samplerPool?.Get(samplerId); Sampler sampler = samplerPool?.Get(samplerId);
ISampler hostSampler = sampler?.GetHostSampler(texture); ISampler hostSampler = sampler?.GetHostSampler(texture);
@ -409,6 +410,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
} }
}
/// <summary> /// <summary>
/// Ensures that the image bindings are visible to the host GPU. /// Ensures that the image bindings are visible to the host GPU.
@ -464,7 +466,9 @@ 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
{
if (isStore)
{ {
texture?.SignalModified(); texture?.SignalModified();
} }
@ -489,6 +493,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
} }
}
/// <summary> /// <summary>
/// Gets the texture descriptor for a given texture handle. /// Gets the texture descriptor for a given texture handle.

View file

@ -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>

View file

@ -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();

View file

@ -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,14 +552,24 @@ 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.CreateEntryFromFile(cacheTempFilePath, cacheHash); archive.Delete(entry);
File.Delete(cacheTempFilePath);
} }
// 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);
filesToDelete.Add(cacheTempFilePath);
}
}
archive.CommitUpdate();
foreach (string filePath in filesToDelete)
{
File.Delete(filePath);
} }
} }

View file

@ -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();
} }
} }

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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)

View file

@ -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>

View file

@ -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.

View file

@ -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");

View file

@ -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);
} }

View file

@ -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>();

View file

@ -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);

View file

@ -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)

View file

@ -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);
}
}
} }
} }

View file

@ -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));
} }
} }

View file

@ -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);
} }
} }
} }

View file

@ -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;

View file

@ -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));

View file

@ -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);

View file

@ -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);

View file

@ -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,

View file

@ -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>

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,10 +204,10 @@ 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.AsStorage()); var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType != NcaContentType.Meta) if (nca.Header.ContentType != NcaContentType.Meta)
{ {
Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file"); Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file");
@ -215,12 +216,11 @@ namespace Ryujinx.HLE.FileSystem.Content
} }
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)
{ {
@ -238,8 +238,6 @@ namespace Ryujinx.HLE.FileSystem.Content
} }
} }
} }
}
}
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled) public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled)
{ {
@ -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());
} }
} }

View file

@ -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;
} }

View file

@ -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)

View file

@ -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;
} }
} }
} }

View file

@ -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());
} }

View file

@ -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()

View file

@ -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)
{
// The blinker is on half of the time and events such as input
// changes can reset the blinker. // changes can reset the blinker.
var value = Volatile.Read(ref blinkerCounter.Value); state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
value = (value + 1) % (2 * TextBoxBlinkThreshold);
Volatile.Write(ref blinkerCounter.Value, value);
// 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;
}
} }
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) if (canCreateSurface)
{ {
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath); renderer.CreateSurface(internalState.SurfaceInfo);
Debug.Assert(resourceStream != null);
var originalImage = Image.FromStream(resourceStream);
if (newHeight == 0 || newWidth == 0)
{
return originalImage;
} }
var newSize = new Rectangle(0, 0, newWidth, newHeight); if (needsUpdate)
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = System.Drawing.Graphics.FromImage(newImage))
using (var wrapMode = new ImageAttributes())
{ {
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; renderer.DrawMutableElements(internalState);
graphics.CompositingQuality = CompositingQuality.HighQuality; renderer.CopyImageToBuffer();
graphics.CompositingMode = CompositingMode.SourceCopy; needsUpdate = false;
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; private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
{
if (!source.Equals(destination))
{
destination = source;
return true;
}
return false;
} }
#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();
} }
} }
} }

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}

View file

@ -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++)

View file

@ -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);
@ -334,9 +339,16 @@ namespace Ryujinx.HLE.HOS
foreach (DlcContainer dlcContainer in dlcContainerList) foreach (DlcContainer dlcContainer in dlcContainerList)
{ {
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
{
if (File.Exists(dlcContainer.Path))
{ {
_device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled); _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;
} }

View file

@ -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);

View file

@ -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

View file

@ -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();
} }
bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; spinWait.SpinOnce();
}
spinWait.Reset();
}
}
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);

View file

@ -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);

View 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
}
}

View file

@ -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;
} }
case 8: break;
}
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