mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-01-30 14:20:32 +00:00
merge from master
This commit is contained in:
commit
03f87b3967
195 changed files with 3266 additions and 3507 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -50,7 +50,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.x
|
||||
dotnet-version: 6.0.x
|
||||
- name: Ensure NuGet Source
|
||||
uses: fabriciomurta/ensure-nuget-source@v1
|
||||
- name: Get git short hash
|
||||
|
@ -63,10 +63,10 @@ jobs:
|
|||
- name: Test
|
||||
run: dotnet test -c "${{ matrix.configuration }}"
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.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'
|
||||
- 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'
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -151,7 +151,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||
|
||||
public static CallConvName GetCurrentCallConv()
|
||||
{
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
return OperatingSystem.IsWindows()
|
||||
? CallConvName.Windows
|
||||
: CallConvName.SystemV;
|
||||
}
|
||||
|
|
|
@ -661,6 +661,7 @@ namespace ARMeilleure.Decoders
|
|||
SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create);
|
||||
SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, 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("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.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("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.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("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
||||
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
||||
|
|
|
@ -472,6 +472,24 @@ namespace ARMeilleure.Instructions
|
|||
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)
|
||||
{
|
||||
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
||||
|
|
|
@ -16,6 +16,11 @@ namespace ARMeilleure.Instructions
|
|||
EmitClearExclusive(context);
|
||||
}
|
||||
|
||||
public static void Csdb(ArmEmitterContext context)
|
||||
{
|
||||
// Execute as no-op.
|
||||
}
|
||||
|
||||
public static void Dmb(ArmEmitterContext context) => EmitBarrier(context);
|
||||
|
||||
public static void Dsb(ArmEmitterContext context) => EmitBarrier(context);
|
||||
|
|
|
@ -99,7 +99,7 @@ namespace ARMeilleure.Instructions
|
|||
EmitLoadSimd(context, address, GetVecA32(dreg >> 1), dreg >> 1, rIndex++, op.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitLoadSimd(context, address, GetVecA32(d >> 1), d >> 1, index, op.Size);
|
||||
|
@ -120,13 +120,13 @@ namespace ARMeilleure.Instructions
|
|||
{
|
||||
Operand m = GetIntA32(context, op.Rm);
|
||||
SetIntA32(context, op.Rn, context.Add(n, m));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIntA32(context, op.Rn, context.Add(n, Const(count * eBytes)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OpCode32SimdMemPair op = (OpCode32SimdMemPair)context.CurrOp;
|
||||
|
@ -161,7 +161,7 @@ namespace ARMeilleure.Instructions
|
|||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
if (load)
|
||||
{
|
||||
EmitLoadSimd(context, address, GetVecA32(elemD >> 1), elemD >> 1, index, op.Size);
|
||||
|
@ -213,7 +213,7 @@ namespace ARMeilleure.Instructions
|
|||
int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd;
|
||||
int offset = 0;
|
||||
int byteSize = 4;
|
||||
|
||||
|
||||
for (int num = 0; num < range; num++, sReg++)
|
||||
{
|
||||
Operand address = context.Add(baseAddress, Const(offset));
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace ARMeilleure.Instructions
|
|||
Crc32ch,
|
||||
Crc32cw,
|
||||
Crc32cx,
|
||||
Csdb,
|
||||
Csel,
|
||||
Csinc,
|
||||
Csinv,
|
||||
|
@ -541,6 +542,7 @@ namespace ARMeilleure.Instructions
|
|||
Trap,
|
||||
Tst,
|
||||
Ubfx,
|
||||
Uhadd8,
|
||||
Umaal,
|
||||
Umlal,
|
||||
Umull,
|
||||
|
|
|
@ -95,7 +95,7 @@ namespace ARMeilleure.Signal
|
|||
{
|
||||
if (_initialized) return;
|
||||
|
||||
bool unix = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
bool unix = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
|
||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||
|
||||
if (unix)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Mono.Unix.Native;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Signal
|
||||
|
@ -21,6 +20,7 @@ namespace ARMeilleure.Signal
|
|||
|
||||
static class UnixSignalHandlerRegistration
|
||||
{
|
||||
private const int SIGSEGV = 11;
|
||||
private const int SA_SIGINFO = 0x00000004;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
|
@ -39,7 +39,7 @@ namespace ARMeilleure.Signal
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -51,7 +51,7 @@ namespace ARMeilleure.Signal
|
|||
|
||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||
{
|
||||
return sigaction((int)Signum.SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace ARMeilleure.Translation.Cache
|
|||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(PageSize));
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string 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 BackupDir = "1";
|
||||
|
@ -960,10 +960,10 @@ namespace ARMeilleure.Translation.PTC
|
|||
{
|
||||
uint osPlatform = 0u;
|
||||
|
||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ? 1u : 0u) << 0;
|
||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 1u : 0u) << 1;
|
||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1u : 0u) << 2;
|
||||
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1u : 0u) << 3;
|
||||
osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0;
|
||||
osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1;
|
||||
osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2;
|
||||
osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3;
|
||||
|
||||
return osPlatform;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
|
|||
|
||||
If you wish to build the emulator yourself you will need to:
|
||||
|
||||
**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):**
|
||||
**(Variant one)**
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||
}
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
|
@ -73,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||
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);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||
|
||||
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;
|
||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||
|
@ -27,6 +27,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||
_targetFormat = GetALFormat();
|
||||
_isActive = false;
|
||||
_playedSampleCount = 0;
|
||||
SetVolume(requestedVolume);
|
||||
}
|
||||
|
||||
private ALFormat GetALFormat()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
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)
|
||||
{
|
||||
|
@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
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);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
private float _volume;
|
||||
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;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
|
@ -37,16 +37,18 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||
_sampleCount = uint.MaxValue;
|
||||
_started = false;
|
||||
_volume = 1.0f;
|
||||
_volume = requestedVolume;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_sampleCount = Math.Max(Constants.TargetSampleCount, (uint)GetSampleCount(buffer));
|
||||
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
|
||||
|
||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
|
@ -82,7 +84,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
|
||||
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);
|
||||
|
||||
return;
|
||||
|
@ -92,11 +94,16 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
samples.AsSpan().CopyTo(streamSpan);
|
||||
streamSpan.Slice(samples.Length).Fill(0);
|
||||
fixed (byte* p = samples)
|
||||
{
|
||||
IntPtr pStreamSrc = (IntPtr)p;
|
||||
|
||||
// Apply volume to written data
|
||||
SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
||||
// Zero the dest buffer
|
||||
streamSpan.Fill(0);
|
||||
|
||||
// Apply volume to written data
|
||||
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
||||
}
|
||||
|
||||
ulong sampleCount = GetSampleCount(samples.Length);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||
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)
|
||||
{
|
||||
|
@ -142,12 +142,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
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);
|
||||
|
||||
|
|
|
@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||
private ManualResetEvent _updateRequiredEvent;
|
||||
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;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
|
||||
SetupOutputStream();
|
||||
SetupOutputStream(requestedVolume);
|
||||
}
|
||||
|
||||
private void SetupOutputStream()
|
||||
private void SetupOutputStream(float requestedVolume)
|
||||
{
|
||||
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
||||
_outputStream.WriteCallback += Update;
|
||||
|
||||
_outputStream.Volume = requestedVolume;
|
||||
// TODO: Setup other callbacks (errors, ect).
|
||||
|
||||
_outputStream.Open();
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dylib">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.so">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||
};
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
|
@ -80,6 +80,8 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (!_realDriver.SupportsDirection(direction))
|
||||
{
|
||||
if (direction == Direction.Input)
|
||||
|
@ -94,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||
_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)
|
||||
{
|
||||
|
@ -49,7 +49,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||
|
||||
if (direction == Direction.Output)
|
||||
{
|
||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace Ryujinx.Audio.Common
|
|||
_bufferAppendedCount = 0;
|
||||
_bufferRegisteredCount = 0;
|
||||
_bufferReleasedCount = 0;
|
||||
_volume = 1.0f;
|
||||
_volume = deviceSession.GetVolume();
|
||||
_state = AudioDeviceState.Stopped;
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ namespace Ryujinx.Audio.Common
|
|||
|
||||
for (int i = 0; i < buffersToFlush.Length; i++)
|
||||
{
|
||||
buffersToFlush[i] = _buffers[_hardwareBufferIndex];
|
||||
buffersToFlush[i] = _buffers[hardwareBufferIndex];
|
||||
|
||||
_bufferAppendedCount--;
|
||||
_bufferRegisteredCount++;
|
||||
|
|
|
@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Integration
|
|||
|
||||
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;
|
||||
_sampleRate = sampleRate;
|
||||
_currentBufferTag = 0;
|
||||
|
@ -56,6 +56,16 @@ namespace Ryujinx.Audio.Integration
|
|||
_currentBufferTag = _currentBufferTag % 4;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
_session.SetVolume(volume);
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
return _session.GetVolume();
|
||||
}
|
||||
|
||||
public uint GetChannelCount()
|
||||
{
|
||||
return _channelCount;
|
||||
|
|
|
@ -25,6 +25,18 @@ namespace Ryujinx.Audio.Integration
|
|||
/// </summary>
|
||||
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>
|
||||
/// Get the supported sample rate of this device.
|
||||
/// </summary>
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Ryujinx.Audio.Integration
|
|||
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 GetPauseEvent();
|
||||
|
|
|
@ -208,13 +208,14 @@ namespace Ryujinx.Audio.Output
|
|||
SampleFormat sampleFormat,
|
||||
ref AudioInputConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
uint processHandle)
|
||||
uint processHandle,
|
||||
float volume)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
_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]);
|
||||
|
||||
|
@ -247,6 +248,41 @@ namespace Ryujinx.Audio.Output
|
|||
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()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
}
|
||||
}
|
||||
|
||||
public void Start(IHardwareDeviceDriver deviceDriver)
|
||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
||||
{
|
||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||
|
||||
|
@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
for (int i = 0; i < OutputDevices.Length; i++)
|
||||
{
|
||||
// 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>();
|
||||
|
@ -245,6 +245,33 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
_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()
|
||||
{
|
||||
Dispose(true);
|
||||
|
|
|
@ -186,12 +186,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <summary>
|
||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
private void StartLocked()
|
||||
private void StartLocked(float volume)
|
||||
{
|
||||
_isRunning = true;
|
||||
|
||||
// TODO: virtual device mapping (IAudioDevice)
|
||||
Processor.Start(_deviceDriver);
|
||||
Processor.Start(_deviceDriver, volume);
|
||||
|
||||
_workerThread = new Thread(SendCommands)
|
||||
{
|
||||
|
@ -263,7 +263,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||
private void Register(AudioRenderSystem renderer)
|
||||
private void Register(AudioRenderSystem renderer, float volume)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
|
@ -274,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
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="processHandle">The process handle of the application.</param>
|
||||
/// <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();
|
||||
|
||||
|
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
renderer = audioRenderer;
|
||||
|
||||
Register(renderer);
|
||||
Register(renderer, volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -338,6 +338,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
if (Processor != null)
|
||||
{
|
||||
return Processor.GetVolume();
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
Processor?.SetVolume(volume);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||
|
|
|
@ -76,6 +76,17 @@ namespace Ryujinx.Audio.Renderer.Utils
|
|||
_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()
|
||||
{
|
||||
return _channelCount;
|
||||
|
|
|
@ -37,6 +37,17 @@ namespace Ryujinx.Audio.Renderer.Utils
|
|||
_secondaryDevice?.AppendBuffer(data, channelCount);
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
_baseDevice.SetVolume(volume);
|
||||
_secondaryDevice.SetVolume(volume);
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
return _baseDevice.GetVolume();
|
||||
}
|
||||
|
||||
public uint GetChannelCount()
|
||||
{
|
||||
return _baseDevice.GetChannelCount();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
public Key Screenshot { get; set; }
|
||||
public Key ShowUi { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
public Key ToggleMute { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.1" />
|
||||
<PackageReference Include="System.Management" Version="5.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Ryujinx.Common.System
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace Ryujinx.Common.System
|
|||
|
||||
static public void Restore()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.System
|
||||
{
|
||||
|
@ -19,7 +20,7 @@ namespace Ryujinx.Common.System
|
|||
public static void Windows()
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
|
@ -27,16 +28,22 @@ namespace Ryujinx.Common.System
|
|||
|
||||
public static double GetWindowScaleFactor()
|
||||
{
|
||||
double userDpiScale;
|
||||
double userDpiScale = 96.0;
|
||||
|
||||
try
|
||||
{
|
||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Linux support
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||
userDpiScale = 96.0;
|
||||
}
|
||||
|
||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle Windows Multimedia timer resolution.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class WindowsMultimediaTimerResolution : IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Graphics.Device;
|
||||
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>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
/// <param name="argument">The LaunchDma call argument</param>
|
||||
private void DmaCopy(int argument)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
|
@ -200,6 +222,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||
target.Info.Height,
|
||||
1,
|
||||
1,
|
||||
xCount * srcBpp,
|
||||
srcStride,
|
||||
target.Info.FormatInfo.BytesPerPixel,
|
||||
srcSpan);
|
||||
|
@ -322,5 +345,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
{
|
||||
DmaCopy(argument);
|
||||
ReleaseSemaphore(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,9 +99,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
|||
_isLinear = (argument & 1) != 0;
|
||||
|
||||
_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)
|
||||
{
|
||||
|
@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
|||
|
||||
if (_isLinear && _lineCount == 1)
|
||||
{
|
||||
memoryManager.WriteTrackedResource(_dstGpuVa, data);
|
||||
memoryManager.WriteTrackedResource(_dstGpuVa, data.Slice(0, _lineLengthIn));
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
else
|
||||
|
@ -224,6 +224,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
|||
|
||||
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();
|
||||
|
|
|
@ -64,7 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
nameof(ThreedClassState.ShaderState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||
new StateUpdateCallbackEntry(UpdateScissorState, nameof(ThreedClassState.ScissorState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||
nameof(ThreedClassState.ScissorState),
|
||||
nameof(ThreedClassState.ScreenScissorState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
||||
nameof(ThreedClassState.VertexBufferDrawState),
|
||||
|
@ -426,6 +429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
int width = scissor.X2 - x;
|
||||
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;
|
||||
if (scale != 1f)
|
||||
{
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||
{ nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, 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.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) },
|
||||
{ nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) },
|
||||
|
@ -227,6 +229,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
_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>
|
||||
/// Issues a texture barrier.
|
||||
/// This waits until previous texture writes from the GPU to finish, before
|
||||
|
|
|
@ -784,7 +784,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
public YControl YControl;
|
||||
public float LineWidthSmooth;
|
||||
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 FirstInstance;
|
||||
public fixed uint Reserved143C[53];
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -9,8 +8,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
class Sampler : IDisposable
|
||||
{
|
||||
private const int MinLevelsForAnisotropic = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Host sampler object.
|
||||
/// </summary>
|
||||
|
@ -96,26 +93,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <returns>A host sampler</returns>
|
||||
public ISampler GetHostSampler(Texture texture)
|
||||
{
|
||||
return _anisoSampler != null && AllowForceAnisotropy(texture) ? _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);
|
||||
return _anisoSampler != null && texture?.CanForceAnisotropy == true ? _anisoSampler : _hostSampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -11,6 +11,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
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.
|
||||
private const int ByteComparisonSwitchThreshold = 4;
|
||||
|
||||
private const int MinLevelsForForceAnisotropy = 5;
|
||||
|
||||
private struct TexturePoolOwner
|
||||
{
|
||||
public TexturePool Pool;
|
||||
|
@ -50,6 +53,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
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>
|
||||
/// Host scale factor.
|
||||
/// </summary>
|
||||
|
@ -759,6 +767,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.Stride,
|
||||
Info.Stride,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
data);
|
||||
}
|
||||
|
@ -1142,6 +1151,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// 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
|
||||
|
@ -1232,6 +1259,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
Info = info;
|
||||
Target = info.Target;
|
||||
CanForceAnisotropy = CanTextureForceAnisotropy();
|
||||
|
||||
_depth = info.GetDepth();
|
||||
_layers = info.GetLayers();
|
||||
|
|
|
@ -377,18 +377,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
_textureState[stageIndex][index].Texture = hostTexture;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||
}
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
|
@ -396,16 +384,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
}
|
||||
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
||||
else
|
||||
{
|
||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||
_textureState[stageIndex][index].Texture = hostTexture;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||
}
|
||||
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
||||
{
|
||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
||||
|
||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,28 +466,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||
}
|
||||
else if (isStore)
|
||||
else
|
||||
{
|
||||
texture?.SignalModified();
|
||||
}
|
||||
|
||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
||||
if (isStore)
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
texture?.SignalModified();
|
||||
}
|
||||
|
||||
_imageState[stageIndex][index].Texture = hostTexture;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
if (format == 0 && texture != null)
|
||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
{
|
||||
format = texture.Format;
|
||||
}
|
||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
_imageState[stageIndex][index].Texture = hostTexture;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
if (format == 0 && texture != null)
|
||||
{
|
||||
format = texture.Format;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -14,4 +14,8 @@
|
|||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using Ryujinx.Common;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <summary>
|
||||
/// Main storage of the cache collection.
|
||||
/// </summary>
|
||||
private ZipArchive _cacheArchive;
|
||||
private ZipFile _cacheArchive;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the cache collection supports modification.
|
||||
|
@ -324,7 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
EnsureArchiveUpToDate();
|
||||
|
||||
// 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>
|
||||
|
@ -336,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
// First close previous opened instance if found.
|
||||
if (_cacheArchive != null)
|
||||
{
|
||||
_cacheArchive.Dispose();
|
||||
_cacheArchive.Close();
|
||||
}
|
||||
|
||||
string archivePath = GetArchivePath();
|
||||
|
@ -355,8 +355,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
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.
|
||||
_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}...");
|
||||
|
||||
|
@ -366,7 +376,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
||||
|
||||
// Close the instance to force a flush.
|
||||
_cacheArchive.Dispose();
|
||||
_cacheArchive.Close();
|
||||
_cacheArchive = null;
|
||||
|
||||
string cacheTempDataPath = GetCacheTempDataPath();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
@ -9,7 +10,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -192,19 +192,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <param name="entry">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte[] ReadFromArchive(ZipArchive archive, Hash128 entry)
|
||||
public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
|
||||
{
|
||||
if (archive != null)
|
||||
{
|
||||
ZipArchiveEntry archiveEntry = archive.GetEntry($"{entry}");
|
||||
ZipEntry archiveEntry = archive.GetEntry($"{entry}");
|
||||
|
||||
if (archiveEntry != null)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -538,8 +538,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <param name="archive">The archive to use</param>
|
||||
/// <param name="entries">The entries in the cache</param>
|
||||
[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)
|
||||
{
|
||||
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
||||
|
@ -548,15 +552,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
{
|
||||
string cacheHash = $"{hash}";
|
||||
|
||||
ZipArchiveEntry entry = archive.GetEntry(cacheHash);
|
||||
ZipEntry entry = archive.GetEntry(cacheHash);
|
||||
|
||||
entry?.Delete();
|
||||
if (entry != null)
|
||||
{
|
||||
archive.Delete(entry);
|
||||
}
|
||||
|
||||
archive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsArchiveReadOnly(string archivePath)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using Ryujinx.Common;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
|
@ -35,27 +35,36 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
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>
|
||||
/// Move a file with the name of a given hash to another in the cache archive.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive in use</param>
|
||||
/// <param name="oldKey">The old 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)
|
||||
{
|
||||
ZipArchiveEntry newGuestEntry = archive.CreateEntry($"{newKey}");
|
||||
|
||||
using (Stream oldStream = oldGuestEntry.Open())
|
||||
using (Stream newStream = newGuestEntry.Open())
|
||||
{
|
||||
oldStream.CopyTo(newStream);
|
||||
}
|
||||
|
||||
oldGuestEntry.Delete();
|
||||
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
|
||||
archive.Delete(oldGuestEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,8 +90,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||
|
||||
ZipArchive guestArchive = ZipFile.Open(guestArchivePath, ZipArchiveMode.Update);
|
||||
ZipArchive hostArchive = ZipFile.Open(hostArchivePath, ZipArchiveMode.Update);
|
||||
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
|
||||
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
||||
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
||||
|
@ -129,8 +138,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
||||
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
||||
|
||||
guestArchive.Dispose();
|
||||
hostArchive.Dispose();
|
||||
guestArchive.CommitUpdate();
|
||||
hostArchive.CommitUpdate();
|
||||
|
||||
guestArchive.Close();
|
||||
hostArchive.Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <summary>
|
||||
/// Version of the codegen (to be changed when codegen or guest format change).
|
||||
/// </summary>
|
||||
private const ulong ShaderCodeGenVersion = 2816;
|
||||
private const ulong ShaderCodeGenVersion = 2885;
|
||||
|
||||
// Progress reporting helpers
|
||||
private volatile int _shaderCount;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
public void SetStorage(BufferRange buffer)
|
||||
{
|
||||
if (_buffer != BufferHandle.Null &&
|
||||
_buffer == buffer.Handle &&
|
||||
buffer.Offset == _bufferOffset &&
|
||||
buffer.Size == _bufferSize &&
|
||||
_renderer.BufferCount == _bufferCount)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -35,8 +35,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
VariableType type = GetSrcVarType(operation.Inst, 0);
|
||||
|
||||
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.
|
||||
// (-expr) does not work, but (0.0 - expr) does. This should be removed once the issue is resolved.
|
||||
|
|
|
@ -71,8 +71,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
|
||||
Add(Instruction.FSIBegin, InstType.Special);
|
||||
Add(Instruction.FSIEnd, InstType.Special);
|
||||
Add(Instruction.FindFirstSetS32, InstType.CallUnary, "findMSB");
|
||||
Add(Instruction.FindFirstSetU32, InstType.CallUnary, "findMSB");
|
||||
Add(Instruction.FindLSB, InstType.CallUnary, "findLSB");
|
||||
Add(Instruction.FindMSBS32, InstType.CallUnary, "findMSB");
|
||||
Add(Instruction.FindMSBU32, InstType.CallUnary, "findMSB");
|
||||
Add(Instruction.Floor, InstType.CallUnary, "floor");
|
||||
Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
|
||||
Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier");
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
|
||||
public static bool TryFormat(int value, VariableType dstType, out string formatted)
|
||||
{
|
||||
if (dstType == VariableType.F32 || dstType == VariableType.F64)
|
||||
if (dstType == VariableType.F32)
|
||||
{
|
||||
return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted);
|
||||
}
|
||||
|
|
|
@ -75,69 +75,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
context.Config.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
InstFchkR op = context.GetOp<InstFchkR>();
|
||||
|
|
|
@ -166,13 +166,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
Operand srcB = context.BitwiseNot(src, invert);
|
||||
|
||||
Operand res = isSigned
|
||||
? context.FindFirstSetS32(srcB)
|
||||
: context.FindFirstSetU32(srcB);
|
||||
Operand res;
|
||||
|
||||
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);
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
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)
|
||||
|
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
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)
|
||||
|
@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
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(
|
||||
|
@ -176,7 +176,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
if (dstType == IDstFmt.U64)
|
||||
{
|
||||
context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
|
||||
return;
|
||||
}
|
||||
|
||||
Instruction fpType = srcType.ToInstFPType();
|
||||
|
@ -198,7 +197,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
if (!isSignedInt)
|
||||
{
|
||||
// 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)
|
||||
|
@ -292,7 +293,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
int rd,
|
||||
bool absolute,
|
||||
bool negate,
|
||||
bool saturate)
|
||||
bool saturate,
|
||||
bool writeCC)
|
||||
{
|
||||
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);
|
||||
|
||||
// TODO: CC.
|
||||
SetZnFlags(context, src, writeCC);
|
||||
}
|
||||
|
||||
private static Operand UnpackReg(EmitterContext context, DstFmt floatType, bool h, int reg)
|
||||
|
|
|
@ -528,18 +528,5 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
context.Copy(GetDest(rd), GetHalfPacked(context, swizzle, res, rd));
|
||||
}
|
||||
|
||||
private static void SetDest(EmitterContext context, Operand value, int rd, bool isFP64)
|
||||
{
|
||||
if (isFP64)
|
||||
{
|
||||
context.Copy(GetDest(rd), context.UnpackDouble2x32Low(value));
|
||||
context.Copy(GetDest2(rd), context.UnpackDouble2x32High(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Copy(GetDest(rd), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,156 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
static partial class InstEmit
|
||||
{
|
||||
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)
|
||||
{
|
||||
InstFcmpR op = context.GetOp<InstFcmpR>();
|
||||
|
@ -240,12 +390,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
bool negateA,
|
||||
bool negateB,
|
||||
bool boolFloat,
|
||||
bool writeCC)
|
||||
bool writeCC,
|
||||
bool isFP64 = false)
|
||||
{
|
||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
||||
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||
|
||||
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);
|
||||
|
||||
res = GetPredLogicalOp(context, logicOp, res, pred);
|
||||
|
@ -282,12 +435,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
bool absoluteB,
|
||||
bool negateA,
|
||||
bool negateB,
|
||||
bool writeCC)
|
||||
bool writeCC,
|
||||
bool isFP64 = false)
|
||||
{
|
||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
||||
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||
|
||||
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 pred = GetPredicate(context, srcPred, srcPredInv);
|
||||
|
||||
|
@ -367,7 +523,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
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;
|
||||
|
||||
|
@ -381,7 +537,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
}
|
||||
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)
|
||||
{
|
||||
|
@ -404,12 +560,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
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)
|
||||
{
|
||||
res = context.BitwiseOr(res, context.IsNan(srcA));
|
||||
res = context.BitwiseOr(res, context.IsNan(srcB));
|
||||
res = context.BitwiseOr(res, context.IsNan(srcA, fpType));
|
||||
res = context.BitwiseOr(res, context.IsNan(srcB, fpType));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
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)
|
||||
{
|
||||
InstFmnmxR op = context.GetOp<InstFmnmxR>();
|
||||
|
@ -52,19 +85,22 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
bool absoluteB,
|
||||
bool negateA,
|
||||
bool negateB,
|
||||
bool writeCC)
|
||||
bool writeCC,
|
||||
bool isFP64 = false)
|
||||
{
|
||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA);
|
||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
|
||||
Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32;
|
||||
|
||||
Operand resMin = context.FPMinimum(srcA, srcB);
|
||||
Operand resMax = context.FPMaximum(srcA, srcB);
|
||||
srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType);
|
||||
srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType);
|
||||
|
||||
Operand dest = GetDest(rd);
|
||||
Operand resMin = context.FPMinimum(srcA, srcB, fpType);
|
||||
Operand resMax = context.FPMaximum(srcA, srcB, fpType);
|
||||
|
||||
context.Copy(dest, context.ConditionalSelect(srcPred, resMin, resMax));
|
||||
Operand res = context.ConditionalSelect(srcPred, resMin, resMax);
|
||||
|
||||
SetFPZnFlags(context, dest, writeCC);
|
||||
SetDest(context, res, rd, isFP64);
|
||||
|
||||
SetFPZnFlags(context, res, writeCC, fpType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
if (isFP64)
|
||||
{
|
||||
return context.FP32ConvertToFP64(Const(imm));
|
||||
return context.PackDouble2x32(Const(0), Const(imm));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -218,6 +218,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
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)
|
||||
{
|
||||
return (short)imm16;
|
||||
|
|
|
@ -61,11 +61,23 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
res = context.FPReciprocalSquareRoot(res);
|
||||
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:
|
||||
res = context.FPSquareRoot(res);
|
||||
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));
|
||||
|
|
|
@ -219,9 +219,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
||||
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(dest++, RegisterType.Gpr);
|
||||
|
|
|
@ -306,9 +306,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (rdIndex > RegisterConsts.RegisterZeroIndex)
|
||||
if (rdIndex >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(rdIndex++, RegisterType.Gpr);
|
||||
|
@ -322,6 +322,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
Operand dest = GetDest();
|
||||
|
||||
if (dest == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TextureOperation operation = context.CreateTextureOperation(
|
||||
Instruction.TextureSample,
|
||||
type,
|
||||
|
@ -795,9 +800,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
||||
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(dest++, RegisterType.Gpr);
|
||||
|
@ -809,13 +814,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
if ((compMask & 1) != 0)
|
||||
{
|
||||
Operand destOperand = GetDest();
|
||||
|
||||
if (destOperand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TextureOperation operation = context.CreateTextureOperation(
|
||||
Instruction.TextureSample,
|
||||
type,
|
||||
flags,
|
||||
handle,
|
||||
compIndex,
|
||||
GetDest(),
|
||||
destOperand,
|
||||
sources);
|
||||
|
||||
context.Add(operation);
|
||||
|
@ -902,9 +914,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
||||
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(dest++, RegisterType.Gpr);
|
||||
|
@ -916,11 +928,18 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
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.
|
||||
if (compIndex >= 2)
|
||||
{
|
||||
context.Add(new CommentNode("Unsupported component z or w found"));
|
||||
context.Copy(GetDest(), Const(0));
|
||||
context.Copy(destOperand, Const(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -941,7 +960,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand fixedPointValue = context.FP32ConvertToS32(tempDest);
|
||||
|
||||
context.Copy(GetDest(), fixedPointValue);
|
||||
context.Copy(destOperand, fixedPointValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1055,9 +1074,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
||||
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(dest++, RegisterType.Gpr);
|
||||
|
@ -1069,13 +1088,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
if ((compMask & 1) != 0)
|
||||
{
|
||||
Operand destOperand = GetDest();
|
||||
|
||||
if (destOperand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TextureOperation operation = context.CreateTextureOperation(
|
||||
Instruction.TextureSample,
|
||||
type,
|
||||
flags,
|
||||
handle,
|
||||
compIndex,
|
||||
GetDest(),
|
||||
destOperand,
|
||||
sources);
|
||||
|
||||
context.Add(operation);
|
||||
|
@ -1126,9 +1152,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand GetDest()
|
||||
{
|
||||
if (dest > RegisterConsts.RegisterZeroIndex)
|
||||
if (dest >= RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
return Const(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Register(dest++, RegisterType.Gpr);
|
||||
|
@ -1149,13 +1175,20 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
if ((compMask & 1) != 0)
|
||||
{
|
||||
Operand destOperand = GetDest();
|
||||
|
||||
if (destOperand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TextureOperation operation = context.CreateTextureOperation(
|
||||
inst,
|
||||
type,
|
||||
flags,
|
||||
imm,
|
||||
compIndex,
|
||||
GetDest(),
|
||||
destOperand,
|
||||
sources);
|
||||
|
||||
context.Add(operation);
|
||||
|
|
|
@ -68,8 +68,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
ExponentB2,
|
||||
FSIBegin,
|
||||
FSIEnd,
|
||||
FindFirstSetS32,
|
||||
FindFirstSetU32,
|
||||
FindLSB,
|
||||
FindMSBS32,
|
||||
FindMSBU32,
|
||||
Floor,
|
||||
FusedMultiplyAdd,
|
||||
GroupMemoryBarrier,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -79,14 +79,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||
Add(Instruction.Ddy, VariableType.F32, VariableType.F32);
|
||||
Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
||||
Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
|
||||
Add(Instruction.FindFirstSetS32, VariableType.S32, VariableType.S32);
|
||||
Add(Instruction.FindFirstSetU32, VariableType.S32, VariableType.U32);
|
||||
Add(Instruction.FindLSB, VariableType.Int, VariableType.Int);
|
||||
Add(Instruction.FindMSBS32, VariableType.S32, VariableType.S32);
|
||||
Add(Instruction.FindMSBU32, VariableType.S32, VariableType.U32);
|
||||
Add(Instruction.Floor, VariableType.Scalar, VariableType.Scalar);
|
||||
Add(Instruction.FusedMultiplyAdd, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
|
||||
Add(Instruction.ImageLoad, VariableType.F32);
|
||||
Add(Instruction.ImageStore, VariableType.None);
|
||||
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.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
|
||||
Add(Instruction.LoadGlobal, VariableType.U32, VariableType.S32, VariableType.S32);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
|
@ -181,14 +182,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
|
@ -266,9 +272,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
|
@ -296,9 +302,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
|
@ -321,14 +327,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
|
@ -348,7 +354,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
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)
|
||||
|
@ -536,9 +544,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
|
@ -590,6 +598,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
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)
|
||||
{
|
||||
return context.Add(Instruction.PackDouble2x32, Local(), a, b);
|
||||
|
|
|
@ -250,6 +250,7 @@ namespace Ryujinx.Graphics.Texture
|
|||
int height,
|
||||
int blockWidth,
|
||||
int blockHeight,
|
||||
int lineSize,
|
||||
int stride,
|
||||
int bytesPerPixel,
|
||||
ReadOnlySpan<byte> data)
|
||||
|
@ -258,7 +259,7 @@ namespace Ryujinx.Graphics.Texture
|
|||
int h = BitUtils.DivRoundUp(height, blockHeight);
|
||||
|
||||
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);
|
||||
Span<byte> output = outputBuffer.AsSpan;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -14,6 +14,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
|
@ -203,40 +204,37 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
|
||||
{
|
||||
fs.OpenFile(out IFile ncaFile, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
||||
using (ncaFile)
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
fs.OpenFile(ref ncaFile.Ref(), ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||
{
|
||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
||||
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");
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||
using var cnmtFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs0.OpenFile(out IFile cnmtFile, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
||||
pfs0.OpenFile(ref cnmtFile.Ref(), pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
||||
|
||||
using (cnmtFile)
|
||||
{
|
||||
var cnmt = new Cnmt(cnmtFile.AsStream());
|
||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
||||
|
||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
|
||||
if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
|
||||
}
|
||||
}
|
||||
string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
|
||||
if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,24 +270,24 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled)
|
||||
{
|
||||
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
PartitionFileSystem pfs;
|
||||
IFile ncaFile;
|
||||
|
||||
switch (Path.GetExtension(aoc.ContainerPath))
|
||||
{
|
||||
case ".xci":
|
||||
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;
|
||||
case ".nsp":
|
||||
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;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
|
@ -625,18 +623,18 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode)
|
||||
{
|
||||
IFile file;
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
if (filesystem.FileExists($"{path}/00"))
|
||||
{
|
||||
filesystem.OpenFile(out file, $"{path}/00".ToU8Span(), mode);
|
||||
filesystem.OpenFile(ref file.Ref(), $"{path}/00".ToU8Span(), mode);
|
||||
}
|
||||
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)
|
||||
|
@ -753,9 +751,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -781,9 +781,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
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;
|
||||
|
||||
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();
|
||||
if (contentStorage.GetSize(out long size).IsSuccess())
|
||||
|
@ -887,9 +891,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -903,9 +909,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
{
|
||||
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;
|
||||
|
||||
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())
|
||||
{
|
||||
|
@ -1020,9 +1030,11 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
{
|
||||
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
|
||||
if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
||||
using var systemVersionFile = new UniqueRef<IFile>();
|
||||
|
||||
if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess())
|
||||
{
|
||||
return new SystemVersion(systemVersionFile.AsStream());
|
||||
return new SystemVersion(systemVersionFile.Get.AsStream());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,25 +3,23 @@ using LibHac.Common;
|
|||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSrv.FsCreator;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
// Force all-zero keys for now since people can open the emulator with different keys or sd seeds sometimes
|
||||
var fs = new AesXtsFileSystem(baseFileSystem, new byte[0x32], 0x4000);
|
||||
encryptedFileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
|
||||
// Todo: Reenable when AesXtsFileSystem is fixed
|
||||
outEncryptedFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ using System.Buffers.Text;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Path = System.IO.Path;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
|
@ -240,11 +242,13 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
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())
|
||||
{
|
||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
||||
Ticket ticket = new Ticket(ticketFile.Get.AsStream());
|
||||
|
||||
if (ticket.TitleKeyType == TitleKeyType.Common)
|
||||
{
|
||||
|
@ -280,12 +284,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
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;
|
||||
|
||||
while (true)
|
||||
{
|
||||
rc = iterator.ReadSaveDataInfo(out long count, info);
|
||||
rc = iterator.Get.ReadSaveDataInfo(out long count, info);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (count == 0)
|
||||
|
|
|
@ -98,6 +98,11 @@ namespace Ryujinx.HLE
|
|||
/// </summary>
|
||||
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>
|
||||
/// Control LibHac's integrity check level.
|
||||
/// </summary>
|
||||
|
@ -122,8 +127,8 @@ namespace Ryujinx.HLE
|
|||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||
internal readonly string TimeZone;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Type of the memory manager used on CPU emulation.
|
||||
/// </summary>
|
||||
public MemoryManagerMode MemoryManagerMode { internal get; set; }
|
||||
|
||||
|
@ -139,6 +144,11 @@ namespace Ryujinx.HLE
|
|||
/// </summary>
|
||||
public AspectRatio AspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio volume level.
|
||||
/// </summary>
|
||||
public float AudioVolume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||
/// </summary>
|
||||
|
@ -158,13 +168,15 @@ namespace Ryujinx.HLE
|
|||
bool enableVsync,
|
||||
bool enableDockedMode,
|
||||
bool enablePtc,
|
||||
bool enableInternetAccess,
|
||||
IntegrityCheckLevel fsIntegrityCheckLevel,
|
||||
int fsGlobalAccessLogMode,
|
||||
long systemTimeOffset,
|
||||
string timeZone,
|
||||
MemoryManagerMode memoryManagerMode,
|
||||
bool ignoreMissingServices,
|
||||
AspectRatio aspectRatio)
|
||||
AspectRatio aspectRatio,
|
||||
float audioVolume)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
LibHacHorizonManager = libHacHorizonManager;
|
||||
|
@ -180,6 +192,7 @@ namespace Ryujinx.HLE
|
|||
EnableVsync = enableVsync;
|
||||
EnableDockedMode = enableDockedMode;
|
||||
EnablePtc = enablePtc;
|
||||
EnableInternetAccess = enableInternetAccess;
|
||||
FsIntegrityCheckLevel = fsIntegrityCheckLevel;
|
||||
FsGlobalAccessLogMode = fsGlobalAccessLogMode;
|
||||
SystemTimeOffset = systemTimeOffset;
|
||||
|
@ -187,6 +200,7 @@ namespace Ryujinx.HLE
|
|||
MemoryManagerMode = memoryManagerMode;
|
||||
IgnoreMissingServices = ignoreMissingServices;
|
||||
AspectRatio = aspectRatio;
|
||||
AudioVolume = audioVolume;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -116,8 +116,10 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
|||
|
||||
if (romfs.FileExists(filePath))
|
||||
{
|
||||
romfs.OpenFile(out IFile binaryFile, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
StreamReader reader = new StreamReader(binaryFile.AsStream(), Encoding.Unicode);
|
||||
using var binaryFile = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref binaryFile.Ref(), filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
StreamReader reader = new StreamReader(binaryFile.Get.AsStream(), Encoding.Unicode);
|
||||
|
||||
return CleanText(reader.ReadToEnd());
|
||||
}
|
||||
|
|
|
@ -170,7 +170,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
{
|
||||
_npads?.Update();
|
||||
|
||||
return _keyboardRenderer?.DrawTo(surfaceInfo, destination, position) ?? false;
|
||||
_keyboardRenderer?.SetSurfaceInfo(surfaceInfo);
|
||||
|
||||
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
||||
}
|
||||
|
||||
private void ExecuteForegroundKeyboard()
|
||||
|
|
|
@ -1,717 +1,164 @@
|
|||
using Ryujinx.HLE.Ui;
|
||||
using Ryujinx.Memory;
|
||||
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;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
/// <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>
|
||||
internal class SoftwareKeyboardRenderer : IDisposable
|
||||
{
|
||||
const int TextBoxBlinkThreshold = 8;
|
||||
const int TextBoxBlinkSleepMilliseconds = 100;
|
||||
const int TextBoxBlinkJoinWaitMilliseconds = 1000;
|
||||
private const int TextBoxBlinkSleepMilliseconds = 100;
|
||||
private const int RendererWaitTimeoutMilliseconds = 100;
|
||||
|
||||
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 _stateLock = new object();
|
||||
|
||||
private RenderingSurfaceInfo _surfaceInfo;
|
||||
private Bitmap _surface = null;
|
||||
private object _renderLock = new object();
|
||||
private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState();
|
||||
private SoftwareKeyboardRendererBase _renderer;
|
||||
|
||||
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 _renderAction = new TimedAction();
|
||||
|
||||
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
|
||||
{
|
||||
_surfaceInfo = new RenderingSurfaceInfo(0, 0, 0, 0, 0);
|
||||
_renderer = new SoftwareKeyboardRendererBase(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 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);
|
||||
StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
|
||||
StartRenderer(_renderAction, _renderer, _state, _stateLock);
|
||||
}
|
||||
|
||||
private static void StartTextBoxBlinker(TimedAction timedAction, TRef<int> blinkerCounter)
|
||||
private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
|
||||
{
|
||||
timedAction.Reset(() =>
|
||||
{
|
||||
// The blinker is on falf of the time and events such as input
|
||||
// changes can reset the blinker.
|
||||
var value = Volatile.Read(ref blinkerCounter.Value);
|
||||
value = (value + 1) % (2 * TextBoxBlinkThreshold);
|
||||
Volatile.Write(ref blinkerCounter.Value, value);
|
||||
lock (stateLock)
|
||||
{
|
||||
// The blinker is on half of the time and events such as input
|
||||
// changes can reset the blinker.
|
||||
state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
|
||||
|
||||
// Tell the render thread there is something new to render.
|
||||
Monitor.PulseAll(stateLock);
|
||||
}
|
||||
}, 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);
|
||||
var r = (byte)(color.R * 255);
|
||||
var g = (byte)(color.G * 255);
|
||||
var b = (byte)(color.B * 255);
|
||||
SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState();
|
||||
|
||||
if (flipRgb)
|
||||
bool canCreateSurface = false;
|
||||
bool needsUpdate = true;
|
||||
|
||||
timedAction.Reset(() =>
|
||||
{
|
||||
r = (byte)(255 - r);
|
||||
g = (byte)(255 - g);
|
||||
b = (byte)(255 - b);
|
||||
}
|
||||
lock (stateLock)
|
||||
{
|
||||
if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
return Color.FromArgb(overrideAlpha.GetValueOrDefault(a), r, g, b);
|
||||
needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText);
|
||||
needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin);
|
||||
needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd);
|
||||
needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed);
|
||||
needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed);
|
||||
needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode);
|
||||
needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled);
|
||||
needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled);
|
||||
needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
|
||||
|
||||
canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
|
||||
|
||||
if (canCreateSurface)
|
||||
{
|
||||
internalState.SurfaceInfo = state.SurfaceInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (canCreateSurface)
|
||||
{
|
||||
renderer.CreateSurface(internalState.SurfaceInfo);
|
||||
}
|
||||
|
||||
if (needsUpdate)
|
||||
{
|
||||
renderer.DrawMutableElements(internalState);
|
||||
renderer.CopyImageToBuffer();
|
||||
needsUpdate = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
||||
private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
|
||||
{
|
||||
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
||||
|
||||
Debug.Assert(resourceStream != null);
|
||||
|
||||
var originalImage = Image.FromStream(resourceStream);
|
||||
|
||||
if (newHeight == 0 || newWidth == 0)
|
||||
if (!source.Equals(destination))
|
||||
{
|
||||
return originalImage;
|
||||
destination = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
var newSize = new Rectangle(0, 0, newWidth, newHeight);
|
||||
var newImage = new Bitmap(newWidth, newHeight);
|
||||
|
||||
using (var graphics = System.Drawing.Graphics.FromImage(newImage))
|
||||
using (var wrapMode = new ImageAttributes())
|
||||
{
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
|
||||
graphics.DrawImage(originalImage, newSize, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
|
||||
}
|
||||
|
||||
return newImage;
|
||||
return false;
|
||||
}
|
||||
|
||||
#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
|
||||
{
|
||||
lock (_renderLock)
|
||||
lock (_stateLock)
|
||||
{
|
||||
// Update the parameters that were provided.
|
||||
_inputText = inputText != null ? inputText : _inputText;
|
||||
_cursorStart = cursorStart.GetValueOrDefault(_cursorStart);
|
||||
_cursorEnd = cursorEnd.GetValueOrDefault(_cursorEnd);
|
||||
_overwriteMode = overwriteMode.GetValueOrDefault(_overwriteMode);
|
||||
_typingEnabled = typingEnabled.GetValueOrDefault(_typingEnabled);
|
||||
_state.InputText = inputText != null ? inputText : _state.InputText;
|
||||
_state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
|
||||
_state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
|
||||
_state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
|
||||
_state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
|
||||
|
||||
// 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)
|
||||
{
|
||||
lock (_renderLock)
|
||||
lock (_stateLock)
|
||||
{
|
||||
// Update the parameters that were provided.
|
||||
_acceptPressed = acceptPressed.GetValueOrDefault(_acceptPressed);
|
||||
_cancelPressed = cancelPressed.GetValueOrDefault(_cancelPressed);
|
||||
_controllerEnabled = controllerEnabled.GetValueOrDefault(_controllerEnabled);
|
||||
_state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
|
||||
_state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
|
||||
_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())
|
||||
{
|
||||
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);
|
||||
// Tell the render thread there is something new to render.
|
||||
Monitor.PulseAll(_stateLock);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecreateSurface()
|
||||
internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
return _renderer.WriteBufferToMemory(destination, position);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_textBoxBlinkTimedAction.RequestCancel();
|
||||
_renderAction.RequestCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,585 @@
|
|||
using Ryujinx.HLE.Ui;
|
||||
using Ryujinx.Memory;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.Fonts;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class that generates the graphics for the software keyboard applet during inline mode.
|
||||
/// </summary>
|
||||
internal class SoftwareKeyboardRendererBase
|
||||
{
|
||||
public const int TextBoxBlinkThreshold = 8;
|
||||
|
||||
const string MessageText = "Please use the keyboard to input text";
|
||||
const string AcceptText = "Accept";
|
||||
const string CancelText = "Cancel";
|
||||
const string ControllerToggleText = "Toggle input";
|
||||
|
||||
private readonly object _bufferLock = new object();
|
||||
|
||||
private RenderingSurfaceInfo _surfaceInfo = null;
|
||||
private Image<Argb32> _surface = null;
|
||||
private byte[] _bufferData = null;
|
||||
|
||||
private Image _ryujinxLogo = null;
|
||||
private Image _padAcceptIcon = null;
|
||||
private Image _padCancelIcon = null;
|
||||
private Image _keyModeIcon = null;
|
||||
|
||||
private float _textBoxOutlineWidth;
|
||||
private float _padPressedPenWidth;
|
||||
|
||||
private Color _textNormalColor;
|
||||
private Color _textSelectedColor;
|
||||
private Color _textOverCursorColor;
|
||||
|
||||
private IBrush _panelBrush;
|
||||
private IBrush _disabledBrush;
|
||||
private IBrush _cursorBrush;
|
||||
private IBrush _selectionBoxBrush;
|
||||
|
||||
private Pen _textBoxOutlinePen;
|
||||
private Pen _cursorPen;
|
||||
private Pen _selectionBoxPen;
|
||||
private Pen _padPressedPen;
|
||||
|
||||
private int _inputTextFontSize;
|
||||
private Font _messageFont;
|
||||
private Font _inputTextFont;
|
||||
private Font _labelsTextFont;
|
||||
|
||||
private RectangleF _panelRectangle;
|
||||
private Point _logoPosition;
|
||||
private float _messagePositionY;
|
||||
|
||||
public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
|
||||
{
|
||||
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
|
||||
int ryujinxLogoSize = 32;
|
||||
|
||||
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
|
||||
|
||||
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
||||
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
||||
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
||||
|
||||
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
||||
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
||||
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
||||
|
||||
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
||||
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
||||
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
||||
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
||||
|
||||
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
|
||||
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
|
||||
_textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
||||
|
||||
float cursorWidth = 2;
|
||||
|
||||
_textBoxOutlineWidth = 2;
|
||||
_padPressedPenWidth = 2;
|
||||
|
||||
_panelBrush = new SolidBrush(panelColor);
|
||||
_disabledBrush = new SolidBrush(panelTransparentColor);
|
||||
_cursorBrush = new SolidBrush(_textNormalColor);
|
||||
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
||||
|
||||
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
||||
_cursorPen = new Pen(_textNormalColor, cursorWidth);
|
||||
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
||||
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
||||
|
||||
_inputTextFontSize = 20;
|
||||
|
||||
CreateFonts(uiTheme.FontFamily);
|
||||
}
|
||||
|
||||
private void CreateFonts(string uiThemeFontFamily)
|
||||
{
|
||||
// Try a list of fonts in case any of them is not available in the system.
|
||||
|
||||
string[] availableFonts = new string[]
|
||||
{
|
||||
uiThemeFontFamily,
|
||||
"Liberation Sans",
|
||||
"FreeSans",
|
||||
"DejaVu Sans"
|
||||
};
|
||||
|
||||
foreach (string fontFamily in availableFonts)
|
||||
{
|
||||
try
|
||||
{
|
||||
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
|
||||
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
|
||||
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
|
||||
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
|
||||
}
|
||||
|
||||
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
||||
{
|
||||
var a = (byte)(color.A * 255);
|
||||
var r = (byte)(color.R * 255);
|
||||
var g = (byte)(color.G * 255);
|
||||
var b = (byte)(color.B * 255);
|
||||
|
||||
if (flipRgb)
|
||||
{
|
||||
r = (byte)(255 - r);
|
||||
g = (byte)(255 - g);
|
||||
b = (byte)(255 - b);
|
||||
}
|
||||
|
||||
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
|
||||
}
|
||||
|
||||
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
||||
{
|
||||
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
||||
|
||||
Debug.Assert(resourceStream != null);
|
||||
|
||||
var image = Image.Load(resourceStream);
|
||||
|
||||
if (newHeight != 0 && newWidth != 0)
|
||||
{
|
||||
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private void SetGraphicsOptions(IImageProcessingContext context)
|
||||
{
|
||||
context.GetGraphicsOptions().Antialias = true;
|
||||
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
|
||||
}
|
||||
|
||||
private void DrawImmutableElements()
|
||||
{
|
||||
if (_surface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_surface.Mutate(context =>
|
||||
{
|
||||
SetGraphicsOptions(context);
|
||||
|
||||
context.Clear(Color.Transparent);
|
||||
context.Fill(_panelBrush, _panelRectangle);
|
||||
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
|
||||
|
||||
float halfWidth = _panelRectangle.Width / 2;
|
||||
float buttonsY = _panelRectangle.Y + 185;
|
||||
|
||||
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||
|
||||
DrawControllerToggle(context, disableButtonPosition);
|
||||
});
|
||||
}
|
||||
|
||||
public void DrawMutableElements(SoftwareKeyboardUiState state)
|
||||
{
|
||||
if (_surface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_surface.Mutate(context =>
|
||||
{
|
||||
var messageRectangle = MeasureString(MessageText, _messageFont);
|
||||
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
||||
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
||||
var messagePosition = new PointF(messagePositionX, messagePositionY);
|
||||
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
||||
|
||||
SetGraphicsOptions(context);
|
||||
|
||||
context.Fill(_panelBrush, messageBoundRectangle);
|
||||
|
||||
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
|
||||
|
||||
if (!state.TypingEnabled)
|
||||
{
|
||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||
|
||||
context.Fill(_disabledBrush, messageBoundRectangle);
|
||||
}
|
||||
|
||||
DrawTextBox(context, state);
|
||||
|
||||
float halfWidth = _panelRectangle.Width / 2;
|
||||
float buttonsY = _panelRectangle.Y + 185;
|
||||
|
||||
PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
|
||||
PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
|
||||
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||
|
||||
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
|
||||
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
|
||||
{
|
||||
if (_surfaceInfo != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_surfaceInfo = surfaceInfo;
|
||||
|
||||
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
||||
|
||||
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
||||
// image if the pitch is different.
|
||||
uint totalWidth = _surfaceInfo.Pitch / 4;
|
||||
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
||||
|
||||
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
||||
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
||||
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
||||
|
||||
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
|
||||
|
||||
ComputeConstants();
|
||||
DrawImmutableElements();
|
||||
}
|
||||
|
||||
private void ComputeConstants()
|
||||
{
|
||||
int totalWidth = (int)_surfaceInfo.Width;
|
||||
int totalHeight = (int)_surfaceInfo.Height;
|
||||
|
||||
int panelHeight = 240;
|
||||
int panelPositionY = totalHeight - panelHeight;
|
||||
|
||||
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
|
||||
|
||||
_messagePositionY = panelPositionY + 60;
|
||||
|
||||
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
||||
int logoPositionY = panelPositionY + 18;
|
||||
|
||||
_logoPosition = new Point(logoPositionX, logoPositionY);
|
||||
}
|
||||
|
||||
private RectangleF MeasureString(string text, Font font)
|
||||
{
|
||||
RendererOptions options = new RendererOptions(font);
|
||||
FontRectangle rectangle = TextMeasurer.Measure(text == "" ? " " : text, options);
|
||||
|
||||
if (text == "")
|
||||
{
|
||||
return new RectangleF(0, rectangle.Y, 0, rectangle.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
|
||||
{
|
||||
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
|
||||
|
||||
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
||||
float boxHeight = 32;
|
||||
float boxY = _panelRectangle.Y + 110;
|
||||
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
||||
|
||||
RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
|
||||
|
||||
RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
|
||||
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
|
||||
|
||||
context.Fill(_panelBrush, boundRectangle);
|
||||
|
||||
context.Draw(_textBoxOutlinePen, boxRectangle);
|
||||
|
||||
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
||||
float inputTextY = boxY + 5;
|
||||
|
||||
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
||||
|
||||
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
|
||||
|
||||
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
||||
|
||||
Color cursorTextColor;
|
||||
IBrush cursorBrush;
|
||||
Pen cursorPen;
|
||||
|
||||
float cursorPositionYTop = inputTextY + 1;
|
||||
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
|
||||
float cursorPositionXLeft;
|
||||
float cursorPositionXRight;
|
||||
|
||||
bool cursorVisible = false;
|
||||
|
||||
if (state.CursorBegin != state.CursorEnd)
|
||||
{
|
||||
Debug.Assert(state.InputText.Length > 0);
|
||||
|
||||
cursorTextColor = _textSelectedColor;
|
||||
cursorBrush = _selectionBoxBrush;
|
||||
cursorPen = _selectionBoxPen;
|
||||
|
||||
string textUntilBegin = state.InputText.Substring(0, state.CursorBegin);
|
||||
string textUntilEnd = state.InputText.Substring(0, state.CursorEnd);
|
||||
|
||||
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
|
||||
var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
|
||||
|
||||
cursorVisible = true;
|
||||
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
||||
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorTextColor = _textOverCursorColor;
|
||||
cursorBrush = _cursorBrush;
|
||||
cursorPen = _cursorPen;
|
||||
|
||||
if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
|
||||
{
|
||||
// Show the blinking cursor.
|
||||
|
||||
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
|
||||
string textUntilCursor = state.InputText.Substring(0, cursorBegin);
|
||||
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||
|
||||
cursorVisible = true;
|
||||
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||
|
||||
if (state.OverwriteMode)
|
||||
{
|
||||
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
||||
|
||||
if (state.CursorBegin < state.InputText.Length)
|
||||
{
|
||||
textUntilCursor = state.InputText.Substring(0, cursorBegin + 1);
|
||||
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The blinking cursor is in insert mode so it is only a line.
|
||||
cursorPositionXRight = cursorPositionXLeft;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorPositionXLeft = inputTextX;
|
||||
cursorPositionXRight = inputTextX;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.TypingEnabled && cursorVisible)
|
||||
{
|
||||
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
||||
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
||||
|
||||
if (cursorWidth == 0)
|
||||
{
|
||||
PointF[] points = new PointF[]
|
||||
{
|
||||
new PointF(cursorPositionXLeft, cursorPositionYTop),
|
||||
new PointF(cursorPositionXLeft, cursorPositionYBottom),
|
||||
};
|
||||
|
||||
context.DrawLines(cursorPen, points);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
||||
|
||||
context.Draw(cursorPen , cursorRectangle);
|
||||
context.Fill(cursorBrush, cursorRectangle);
|
||||
|
||||
Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
|
||||
textOverCursor.Mutate(context =>
|
||||
{
|
||||
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
|
||||
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
|
||||
});
|
||||
|
||||
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
|
||||
context.DrawImage(textOverCursor, cursorPosition, 1);
|
||||
}
|
||||
}
|
||||
else if (!state.TypingEnabled)
|
||||
{
|
||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||
|
||||
context.Fill(_disabledBrush, boundRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
|
||||
{
|
||||
// Use relative positions so we can center the the entire drawing later.
|
||||
|
||||
float iconX = 0;
|
||||
float iconY = 0;
|
||||
float iconWidth = icon.Width;
|
||||
float iconHeight = icon.Height;
|
||||
|
||||
var labelRectangle = MeasureString(label, _labelsTextFont);
|
||||
|
||||
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
||||
float labelPositionY = 3;
|
||||
|
||||
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
||||
float fullHeight = iconHeight;
|
||||
|
||||
// Convert all relative positions into absolute.
|
||||
|
||||
float originX = (int)(point.X - fullWidth / 2);
|
||||
float originY = (int)(point.Y - fullHeight / 2);
|
||||
|
||||
iconX += originX;
|
||||
iconY += originY;
|
||||
|
||||
var iconPosition = new Point((int)iconX, (int)iconY);
|
||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||
|
||||
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
||||
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
|
||||
|
||||
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
|
||||
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
|
||||
|
||||
context.Fill(_panelBrush, boundRectangle);
|
||||
context.DrawImage(icon, iconPosition, 1);
|
||||
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
context.Draw(_padPressedPen, selectedRectangle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||
|
||||
context.Fill(_disabledBrush, boundRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
|
||||
{
|
||||
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
|
||||
|
||||
// Use relative positions so we can center the the entire drawing later.
|
||||
|
||||
float keyWidth = _keyModeIcon.Width;
|
||||
float keyHeight = _keyModeIcon.Height;
|
||||
|
||||
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
||||
float labelPositionY = -labelRectangle.Y - 1;
|
||||
|
||||
float keyX = 0;
|
||||
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
||||
|
||||
float fullWidth = labelPositionX + labelRectangle.Width;
|
||||
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
||||
|
||||
// Convert all relative positions into absolute.
|
||||
|
||||
float originX = (int)(point.X - fullWidth / 2);
|
||||
float originY = (int)(point.Y - fullHeight / 2);
|
||||
|
||||
keyX += originX;
|
||||
keyY += originY;
|
||||
|
||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||
var overlayPosition = new Point((int)keyX, (int)keyY);
|
||||
|
||||
context.DrawImage(_keyModeIcon, overlayPosition, 1);
|
||||
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
|
||||
}
|
||||
|
||||
public void CopyImageToBuffer()
|
||||
{
|
||||
lock (_bufferLock)
|
||||
{
|
||||
if (_surface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the pixel format used in the image to the one used in the Switch surface.
|
||||
|
||||
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
|
||||
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
||||
|
||||
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
||||
|
||||
for (int i = 0; i < dataConvert.Length; i++)
|
||||
{
|
||||
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
|
||||
{
|
||||
lock (_bufferLock)
|
||||
{
|
||||
if (_bufferData == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
destination.Write(position, _bufferData);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Ryujinx.HLE.Ui;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
internal class SoftwareKeyboardUiState
|
||||
{
|
||||
public string InputText = "";
|
||||
public int CursorBegin = 0;
|
||||
public int CursorEnd = 0;
|
||||
public bool AcceptPressed = false;
|
||||
public bool CancelPressed = false;
|
||||
public bool OverwriteMode = false;
|
||||
public bool TypingEnabled = true;
|
||||
public bool ControllerEnabled = true;
|
||||
public int TextBoxBlinkCounter = 0;
|
||||
|
||||
public RenderingSurfaceInfo SurfaceInfo = null;
|
||||
}
|
||||
}
|
|
@ -144,6 +144,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
|||
}), cancelled);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for (int i = 0; i < substepData.SleepCount; i++)
|
||||
|
|
|
@ -25,6 +25,7 @@ using System.Reflection;
|
|||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
using static Ryujinx.HLE.HOS.ModLoader;
|
||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
|
@ -101,9 +102,11 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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);
|
||||
|
||||
|
@ -116,7 +119,7 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
@ -143,9 +146,11 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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);
|
||||
|
||||
|
@ -335,7 +340,14 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
||||
{
|
||||
_device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled);
|
||||
if (File.Exists(dlcContainer.Path))
|
||||
{
|
||||
_device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {dlcContainer.Path}. It may have been moved or renamed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -422,7 +434,9 @@ namespace Ryujinx.HLE.HOS
|
|||
// Sets TitleId, so be sure to call before using it
|
||||
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;
|
||||
|
||||
|
@ -434,10 +448,10 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
else
|
||||
{
|
||||
npdmFile.GetSize(out long fileSize).ThrowIfFailure();
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
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)
|
||||
{
|
||||
using var controlFile = new UniqueRef<IFile>();
|
||||
|
||||
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())
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -501,9 +517,11 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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
|
||||
|
@ -673,9 +691,11 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -684,7 +704,7 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using LibHac.Common;
|
||||
using LibHac.Common.Keys;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
|
@ -243,6 +244,7 @@ namespace Ryujinx.HLE.HOS
|
|||
AudioOutputManager = new AudioOutputManager();
|
||||
AudioInputManager = new AudioInputManager();
|
||||
AudioRendererManager = new AudioRendererManager();
|
||||
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
|
||||
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
|
||||
|
||||
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
|
||||
|
@ -255,6 +257,7 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
|
||||
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
|
||||
AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
|
||||
|
||||
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
|
||||
|
||||
|
@ -304,9 +307,9 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
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)
|
||||
|
@ -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()
|
||||
{
|
||||
AppletState.SetFocus(true);
|
||||
|
@ -404,7 +418,7 @@ namespace Ryujinx.HLE.HOS
|
|||
lock (KernelContext.Processes)
|
||||
{
|
||||
// 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.DecrementReferenceCount();
|
||||
|
@ -415,7 +429,7 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
// Terminate HLE services (must be done after the application is already terminated,
|
||||
// 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.DecrementReferenceCount();
|
||||
|
@ -461,7 +475,7 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
foreach (KProcess process in KernelContext.Processes.Values)
|
||||
{
|
||||
if (process.Flags.HasFlag(ProcessCreationFlags.IsApplication))
|
||||
if (process.IsApplication)
|
||||
{
|
||||
// Only game process should be paused.
|
||||
process.SetActivity(pause);
|
||||
|
|
|
@ -58,6 +58,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
return DramMemoryMap.DramBase + GetDramSize(size);
|
||||
}
|
||||
|
||||
public static ulong GenerateRandom()
|
||||
{
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static ulong GetDramSize(MemorySize size)
|
||||
{
|
||||
return size switch
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
{
|
||||
class KTimeManager : IDisposable
|
||||
{
|
||||
public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
|
||||
|
||||
private class WaitingObject
|
||||
{
|
||||
public IKFutureSchedulerObject Object { get; }
|
||||
|
@ -24,6 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
private readonly List<WaitingObject> _waitingObjects;
|
||||
private AutoResetEvent _waitEvent;
|
||||
private bool _keepRunning;
|
||||
private long _enforceWakeupFromSpinWait;
|
||||
|
||||
public KTimeManager(KernelContext context)
|
||||
{
|
||||
|
@ -41,11 +44,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
|
||||
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
|
||||
{
|
||||
long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout);
|
||||
long timePoint = PerformanceCounter.ElapsedTicks + ConvertNanosecondsToHostTicks(timeout);
|
||||
|
||||
lock (_context.CriticalSection.Lock)
|
||||
{
|
||||
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
||||
|
||||
if (timeout < 1000000)
|
||||
{
|
||||
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_waitEvent.Set();
|
||||
|
@ -61,27 +69,51 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
|
||||
private void WaitAndCheckScheduledObjects()
|
||||
{
|
||||
SpinWait spinWait = new SpinWait();
|
||||
WaitingObject next;
|
||||
|
||||
using (_waitEvent = new AutoResetEvent(false))
|
||||
{
|
||||
while (_keepRunning)
|
||||
{
|
||||
WaitingObject next;
|
||||
|
||||
lock (_context.CriticalSection.Lock)
|
||||
{
|
||||
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
|
||||
|
||||
next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (next != null)
|
||||
{
|
||||
long timePoint = PerformanceCounter.ElapsedMilliseconds;
|
||||
long timePoint = PerformanceCounter.ElapsedTicks;
|
||||
|
||||
if (next.TimePoint > timePoint)
|
||||
{
|
||||
_waitEvent.WaitOne((int)(next.TimePoint - timePoint));
|
||||
long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue);
|
||||
|
||||
if (ms > 0)
|
||||
{
|
||||
_waitEvent.WaitOne((int)ms);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint)
|
||||
{
|
||||
if (spinWait.NextSpinWillYield)
|
||||
{
|
||||
Thread.Yield();
|
||||
|
||||
spinWait.Reset();
|
||||
}
|
||||
|
||||
spinWait.SpinOnce();
|
||||
}
|
||||
|
||||
spinWait.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint;
|
||||
bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint;
|
||||
|
||||
if (timeUp)
|
||||
{
|
||||
|
@ -119,6 +151,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
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)
|
||||
{
|
||||
return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
public KProcessCapabilities Capabilities { get; private set; }
|
||||
|
||||
public ulong TitleId { get; private set; }
|
||||
public bool IsApplication { get; private set; }
|
||||
public long Pid { get; private set; }
|
||||
|
||||
private long _creationTimestamp;
|
||||
|
@ -193,6 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
_memRegion = memRegion;
|
||||
_contextFactory = contextFactory ?? new ProcessContextFactory();
|
||||
_customThreadStart = customThreadStart;
|
||||
IsApplication = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication);
|
||||
|
||||
ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
|
||||
|
||||
|
|
33
Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
Normal file
33
Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{
|
||||
enum InfoType : uint
|
||||
{
|
||||
CoreMask,
|
||||
PriorityMask,
|
||||
AliasRegionAddress,
|
||||
AliasRegionSize,
|
||||
HeapRegionAddress,
|
||||
HeapRegionSize,
|
||||
TotalMemorySize,
|
||||
UsedMemorySize,
|
||||
DebuggerAttached,
|
||||
ResourceLimit,
|
||||
IdleTickCount,
|
||||
RandomEntropy,
|
||||
AslrRegionAddress,
|
||||
AslrRegionSize,
|
||||
StackRegionAddress,
|
||||
StackRegionSize,
|
||||
SystemResourceSizeTotal,
|
||||
SystemResourceSizeUsed,
|
||||
ProgramId,
|
||||
// NOTE: Added in 4.0.0, removed in 5.0.0.
|
||||
InitialProcessIdRange,
|
||||
UserExceptionContextAddress,
|
||||
TotalNonSystemMemorySize,
|
||||
UsedNonSystemMemorySize,
|
||||
IsApplication,
|
||||
FreeThreadCount,
|
||||
ThreadTickCount
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue