merge from master

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

View file

@ -50,7 +50,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/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

View file

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

View file

@ -151,7 +151,7 @@ namespace ARMeilleure.CodeGen.X86
public static CallConvName GetCurrentCallConv()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
return OperatingSystem.IsWindows()
? CallConvName.Windows
: CallConvName.SystemV;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -35,7 +35,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
If you wish to build the emulator yourself you will need to:
**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)**

View file

@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
{
if (channelCount == 0)
{
@ -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);

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

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

View file

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

View file

@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.dylib">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="SoundIo\Native\libsoundio\libs\libsoundio.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
</Project>

View file

@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
};
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
{
if (channelCount == 0)
{
@ -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)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,7 +78,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
public void Start(IHardwareDeviceDriver deviceDriver)
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
{
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
@ -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);

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -6,5 +6,6 @@
public Key Screenshot { get; set; }
public Key ShowUi { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -68,8 +68,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ExponentB2,
FSIBegin,
FSIEnd,
FindFirstSetS32,
FindFirstSetU32,
FindLSB,
FindMSBS32,
FindMSBU32,
Floor,
FusedMultiplyAdd,
GroupMemoryBarrier,

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,585 @@
using Ryujinx.HLE.Ui;
using Ryujinx.Memory;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.Fonts;
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Base class that generates the graphics for the software keyboard applet during inline mode.
/// </summary>
internal class SoftwareKeyboardRendererBase
{
public const int TextBoxBlinkThreshold = 8;
const string MessageText = "Please use the keyboard to input text";
const string AcceptText = "Accept";
const string CancelText = "Cancel";
const string ControllerToggleText = "Toggle input";
private readonly object _bufferLock = new object();
private RenderingSurfaceInfo _surfaceInfo = null;
private Image<Argb32> _surface = null;
private byte[] _bufferData = null;
private Image _ryujinxLogo = null;
private Image _padAcceptIcon = null;
private Image _padCancelIcon = null;
private Image _keyModeIcon = null;
private float _textBoxOutlineWidth;
private float _padPressedPenWidth;
private Color _textNormalColor;
private Color _textSelectedColor;
private Color _textOverCursorColor;
private IBrush _panelBrush;
private IBrush _disabledBrush;
private IBrush _cursorBrush;
private IBrush _selectionBoxBrush;
private Pen _textBoxOutlinePen;
private Pen _cursorPen;
private Pen _selectionBoxPen;
private Pen _padPressedPen;
private int _inputTextFontSize;
private Font _messageFont;
private Font _inputTextFont;
private Font _labelsTextFont;
private RectangleF _panelRectangle;
private Point _logoPosition;
private float _messagePositionY;
public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
{
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
int ryujinxLogoSize = 32;
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
_textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
float cursorWidth = 2;
_textBoxOutlineWidth = 2;
_padPressedPenWidth = 2;
_panelBrush = new SolidBrush(panelColor);
_disabledBrush = new SolidBrush(panelTransparentColor);
_cursorBrush = new SolidBrush(_textNormalColor);
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
_cursorPen = new Pen(_textNormalColor, cursorWidth);
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
_inputTextFontSize = 20;
CreateFonts(uiTheme.FontFamily);
}
private void CreateFonts(string uiThemeFontFamily)
{
// Try a list of fonts in case any of them is not available in the system.
string[] availableFonts = new string[]
{
uiThemeFontFamily,
"Liberation Sans",
"FreeSans",
"DejaVu Sans"
};
foreach (string fontFamily in availableFonts)
{
try
{
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
return;
}
catch
{
}
}
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
}
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
{
var a = (byte)(color.A * 255);
var r = (byte)(color.R * 255);
var g = (byte)(color.G * 255);
var b = (byte)(color.B * 255);
if (flipRgb)
{
r = (byte)(255 - r);
g = (byte)(255 - g);
b = (byte)(255 - b);
}
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
}
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
{
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
Debug.Assert(resourceStream != null);
var image = Image.Load(resourceStream);
if (newHeight != 0 && newWidth != 0)
{
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
}
return image;
}
private void SetGraphicsOptions(IImageProcessingContext context)
{
context.GetGraphicsOptions().Antialias = true;
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
}
private void DrawImmutableElements()
{
if (_surface == null)
{
return;
}
_surface.Mutate(context =>
{
SetGraphicsOptions(context);
context.Clear(Color.Transparent);
context.Fill(_panelBrush, _panelRectangle);
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185;
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
DrawControllerToggle(context, disableButtonPosition);
});
}
public void DrawMutableElements(SoftwareKeyboardUiState state)
{
if (_surface == null)
{
return;
}
_surface.Mutate(context =>
{
var messageRectangle = MeasureString(MessageText, _messageFont);
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
float messagePositionY = _messagePositionY - messageRectangle.Y;
var messagePosition = new PointF(messagePositionX, messagePositionY);
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
SetGraphicsOptions(context);
context.Fill(_panelBrush, messageBoundRectangle);
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
if (!state.TypingEnabled)
{
// Just draw a semi-transparent rectangle on top to fade the component with the background.
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, messageBoundRectangle);
}
DrawTextBox(context, state);
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185;
PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
});
}
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
{
if (_surfaceInfo != null)
{
return;
}
_surfaceInfo = surfaceInfo;
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
// image if the pitch is different.
uint totalWidth = _surfaceInfo.Pitch / 4;
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
Debug.Assert(_surfaceInfo.Width <= totalWidth);
Debug.Assert(_surfaceInfo.Height <= totalHeight);
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
ComputeConstants();
DrawImmutableElements();
}
private void ComputeConstants()
{
int totalWidth = (int)_surfaceInfo.Width;
int totalHeight = (int)_surfaceInfo.Height;
int panelHeight = 240;
int panelPositionY = totalHeight - panelHeight;
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
_messagePositionY = panelPositionY + 60;
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
int logoPositionY = panelPositionY + 18;
_logoPosition = new Point(logoPositionX, logoPositionY);
}
private RectangleF MeasureString(string text, Font font)
{
RendererOptions options = new RendererOptions(font);
FontRectangle rectangle = TextMeasurer.Measure(text == "" ? " " : text, options);
if (text == "")
{
return new RectangleF(0, rectangle.Y, 0, rectangle.Height);
}
else
{
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
}
}
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
{
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
float boxHeight = 32;
float boxY = _panelRectangle.Y + 110;
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
context.Fill(_panelBrush, boundRectangle);
context.Draw(_textBoxOutlinePen, boxRectangle);
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
float inputTextY = boxY + 5;
var inputTextPosition = new PointF(inputTextX, inputTextY);
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
Color cursorTextColor;
IBrush cursorBrush;
Pen cursorPen;
float cursorPositionYTop = inputTextY + 1;
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
float cursorPositionXLeft;
float cursorPositionXRight;
bool cursorVisible = false;
if (state.CursorBegin != state.CursorEnd)
{
Debug.Assert(state.InputText.Length > 0);
cursorTextColor = _textSelectedColor;
cursorBrush = _selectionBoxBrush;
cursorPen = _selectionBoxPen;
string textUntilBegin = state.InputText.Substring(0, state.CursorBegin);
string textUntilEnd = state.InputText.Substring(0, state.CursorEnd);
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
cursorVisible = true;
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
}
else
{
cursorTextColor = _textOverCursorColor;
cursorBrush = _cursorBrush;
cursorPen = _cursorPen;
if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
{
// Show the blinking cursor.
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
string textUntilCursor = state.InputText.Substring(0, cursorBegin);
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
cursorVisible = true;
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
if (state.OverwriteMode)
{
// The blinking cursor is in overwrite mode so it takes the size of a character.
if (state.CursorBegin < state.InputText.Length)
{
textUntilCursor = state.InputText.Substring(0, cursorBegin + 1);
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
}
else
{
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
}
}
else
{
// The blinking cursor is in insert mode so it is only a line.
cursorPositionXRight = cursorPositionXLeft;
}
}
else
{
cursorPositionXLeft = inputTextX;
cursorPositionXRight = inputTextX;
}
}
if (state.TypingEnabled && cursorVisible)
{
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
if (cursorWidth == 0)
{
PointF[] points = new PointF[]
{
new PointF(cursorPositionXLeft, cursorPositionYTop),
new PointF(cursorPositionXLeft, cursorPositionYBottom),
};
context.DrawLines(cursorPen, points);
}
else
{
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
context.Draw(cursorPen , cursorRectangle);
context.Fill(cursorBrush, cursorRectangle);
Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
textOverCursor.Mutate(context =>
{
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
});
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
context.DrawImage(textOverCursor, cursorPosition, 1);
}
}
else if (!state.TypingEnabled)
{
// Just draw a semi-transparent rectangle on top to fade the component with the background.
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, boundRectangle);
}
}
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
{
// Use relative positions so we can center the the entire drawing later.
float iconX = 0;
float iconY = 0;
float iconWidth = icon.Width;
float iconHeight = icon.Height;
var labelRectangle = MeasureString(label, _labelsTextFont);
float labelPositionX = iconWidth + 8 - labelRectangle.X;
float labelPositionY = 3;
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
float fullHeight = iconHeight;
// Convert all relative positions into absolute.
float originX = (int)(point.X - fullWidth / 2);
float originY = (int)(point.Y - fullHeight / 2);
iconX += originX;
iconY += originY;
var iconPosition = new Point((int)iconX, (int)iconY);
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
context.Fill(_panelBrush, boundRectangle);
context.DrawImage(icon, iconPosition, 1);
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
if (enabled)
{
if (pressed)
{
context.Draw(_padPressedPen, selectedRectangle);
}
}
else
{
// Just draw a semi-transparent rectangle on top to fade the component with the background.
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, boundRectangle);
}
}
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
{
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
// Use relative positions so we can center the the entire drawing later.
float keyWidth = _keyModeIcon.Width;
float keyHeight = _keyModeIcon.Height;
float labelPositionX = keyWidth + 8 - labelRectangle.X;
float labelPositionY = -labelRectangle.Y - 1;
float keyX = 0;
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
float fullWidth = labelPositionX + labelRectangle.Width;
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
// Convert all relative positions into absolute.
float originX = (int)(point.X - fullWidth / 2);
float originY = (int)(point.Y - fullHeight / 2);
keyX += originX;
keyY += originY;
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
var overlayPosition = new Point((int)keyX, (int)keyY);
context.DrawImage(_keyModeIcon, overlayPosition, 1);
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
}
public void CopyImageToBuffer()
{
lock (_bufferLock)
{
if (_surface == null)
{
return;
}
// Convert the pixel format used in the image to the one used in the Switch surface.
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
{
return;
}
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
for (int i = 0; i < dataConvert.Length; i++)
{
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
}
}
}
public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
{
lock (_bufferLock)
{
if (_bufferData == null)
{
return false;
}
try
{
destination.Write(position, _bufferData);
}
catch
{
return false;
}
return true;
}
}
}
}

View file

@ -0,0 +1,22 @@
using Ryujinx.HLE.Ui;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// TODO
/// </summary>
internal class SoftwareKeyboardUiState
{
public string InputText = "";
public int CursorBegin = 0;
public int CursorEnd = 0;
public bool AcceptPressed = false;
public bool CancelPressed = false;
public bool OverwriteMode = false;
public bool TypingEnabled = true;
public bool ControllerEnabled = true;
public int TextBoxBlinkCounter = 0;
public RenderingSurfaceInfo SurfaceInfo = null;
}
}

View file

@ -144,6 +144,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
}), cancelled);
}
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++)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,33 @@
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
enum InfoType : uint
{
CoreMask,
PriorityMask,
AliasRegionAddress,
AliasRegionSize,
HeapRegionAddress,
HeapRegionSize,
TotalMemorySize,
UsedMemorySize,
DebuggerAttached,
ResourceLimit,
IdleTickCount,
RandomEntropy,
AslrRegionAddress,
AslrRegionSize,
StackRegionAddress,
StackRegionSize,
SystemResourceSizeTotal,
SystemResourceSizeUsed,
ProgramId,
// NOTE: Added in 4.0.0, removed in 5.0.0.
InitialProcessIdRange,
UserExceptionContextAddress,
TotalNonSystemMemorySize,
UsedNonSystemMemorySize,
IsApplication,
FreeThreadCount,
ThreadTickCount
}
}

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