Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
41bba5310a | |||
8071c8c8c0 | |||
b402b4e7f6 | |||
93df366b2c | |||
cd3a15aea5 | |||
070136b3f7 | |||
08ab47c6c0 | |||
85faa9d8fa | |||
dca5b14493 | |||
4d2c8e2a44 | |||
8fa248ceb4 | |||
30862b5ffd | |||
9f57747c57 |
@ -40,6 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||
}
|
||||
|
||||
if (info.BufferStates?.Length != (int)inputCount)
|
||||
{
|
||||
// Keep state if possible.
|
||||
info.BufferStates = new UpsamplerBufferState[(int)inputCount];
|
||||
}
|
||||
|
||||
UpsamplerInfo = info;
|
||||
}
|
||||
|
||||
@ -50,8 +56,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
|
||||
|
||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
@ -59,9 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||
|
||||
float fraction = 0.0f;
|
||||
|
||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
||||
UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -579,52 +579,5 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
// Currently a simple cubic interpolation, assuming duplicated values at edges.
|
||||
// TODO: Discover and use algorithm that the switch uses.
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
int maxIndex = inputBuffer.Length - 1;
|
||||
int cubicEnd = inputBuffer.Length - 3;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float s0, s1, s2, s3;
|
||||
|
||||
s1 = inputBuffer[inputBufferIndex];
|
||||
|
||||
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
|
||||
{
|
||||
// Clamp interplation values at the ends of the input buffer.
|
||||
s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)];
|
||||
s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)];
|
||||
s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)];
|
||||
}
|
||||
else
|
||||
{
|
||||
s0 = inputBuffer[inputBufferIndex - 1];
|
||||
s2 = inputBuffer[inputBufferIndex + 1];
|
||||
s3 = inputBuffer[inputBufferIndex + 2];
|
||||
}
|
||||
|
||||
float a = s3 - s2 - s0 + s1;
|
||||
float b = s0 - s1 - a;
|
||||
float c = s2 - s0;
|
||||
float d = s1;
|
||||
|
||||
float f2 = fraction * fraction;
|
||||
float f3 = f2 * fraction;
|
||||
|
||||
outputBuffer[i] = a * f3 + b * f2 + c * fraction + d;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public class UpsamplerHelper
|
||||
{
|
||||
private const int HistoryLength = UpsamplerBufferState.HistoryLength;
|
||||
private const int FilterBankLength = 20;
|
||||
// Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
private const int Bank0CenterIndex = 9;
|
||||
private static readonly Array20<float> Bank1 = PrecomputeFilterBank(1.0f / 6.0f);
|
||||
private static readonly Array20<float> Bank2 = PrecomputeFilterBank(2.0f / 6.0f);
|
||||
private static readonly Array20<float> Bank3 = PrecomputeFilterBank(3.0f / 6.0f);
|
||||
private static readonly Array20<float> Bank4 = PrecomputeFilterBank(4.0f / 6.0f);
|
||||
private static readonly Array20<float> Bank5 = PrecomputeFilterBank(5.0f / 6.0f);
|
||||
|
||||
private static Array20<float> PrecomputeFilterBank(float offset)
|
||||
{
|
||||
float Sinc(float x)
|
||||
{
|
||||
if (x == 0)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
return (MathF.Sin(MathF.PI * x) / (MathF.PI * x));
|
||||
}
|
||||
|
||||
float BlackmanWindow(float x)
|
||||
{
|
||||
const float a = 0.18f;
|
||||
const float a0 = 0.5f - 0.5f * a;
|
||||
const float a1 = -0.5f;
|
||||
const float a2 = 0.5f * a;
|
||||
return a0 + a1 * MathF.Cos(2 * MathF.PI * x) + a2 * MathF.Cos(4 * MathF.PI * x);
|
||||
}
|
||||
|
||||
Array20<float> result = new Array20<float>();
|
||||
|
||||
for (int i = 0; i < FilterBankLength; i++)
|
||||
{
|
||||
float x = (Bank0CenterIndex - i) + offset;
|
||||
result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Polyphase upsampling algorithm
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Upsample(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state)
|
||||
{
|
||||
if (!state.Initialized)
|
||||
{
|
||||
state.Scale = inputSampleCount switch
|
||||
{
|
||||
40 => 6.0f,
|
||||
80 => 3.0f,
|
||||
160 => 1.5f,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
state.Initialized = true;
|
||||
}
|
||||
|
||||
if (outputSampleCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||
{
|
||||
float result = 0.0f;
|
||||
|
||||
Debug.Assert(state.History.Length == HistoryLength);
|
||||
Debug.Assert(bank.Length == FilterBankLength);
|
||||
for (int j = 0; j < FilterBankLength; j++)
|
||||
{
|
||||
result += bank[j] * state.History[j];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void NextInput(ref UpsamplerBufferState state, float input)
|
||||
{
|
||||
state.History.AsSpan().Slice(1).CopyTo(state.History.AsSpan());
|
||||
state.History[HistoryLength - 1] = input;
|
||||
}
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
switch (state.Scale)
|
||||
{
|
||||
case 6.0f:
|
||||
for (int i = 0; i < outputSampleCount; i++)
|
||||
{
|
||||
switch (state.Phase)
|
||||
{
|
||||
case 0:
|
||||
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||
break;
|
||||
case 1:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank1);
|
||||
break;
|
||||
case 2:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||
break;
|
||||
case 3:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank3);
|
||||
break;
|
||||
case 4:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||
break;
|
||||
case 5:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank5);
|
||||
break;
|
||||
}
|
||||
|
||||
state.Phase = (state.Phase + 1) % 6;
|
||||
}
|
||||
break;
|
||||
case 3.0f:
|
||||
for (int i = 0; i < outputSampleCount; i++)
|
||||
{
|
||||
switch (state.Phase)
|
||||
{
|
||||
case 0:
|
||||
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||
break;
|
||||
case 1:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||
break;
|
||||
case 2:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||
break;
|
||||
}
|
||||
|
||||
state.Phase = (state.Phase + 1) % 3;
|
||||
}
|
||||
break;
|
||||
case 1.5f:
|
||||
// Upsample by 3 then decimate by 2.
|
||||
for (int i = 0; i < outputSampleCount; i++)
|
||||
{
|
||||
switch (state.Phase)
|
||||
{
|
||||
case 0:
|
||||
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||
break;
|
||||
case 1:
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||
break;
|
||||
case 2:
|
||||
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||
break;
|
||||
}
|
||||
|
||||
state.Phase = (state.Phase + 1) % 3;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
{
|
||||
public struct UpsamplerBufferState
|
||||
{
|
||||
public const int HistoryLength = 20;
|
||||
|
||||
public float Scale;
|
||||
public Array20<float> History;
|
||||
public bool Initialized;
|
||||
public int Phase;
|
||||
}
|
||||
}
|
@ -37,6 +37,11 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
/// </summary>
|
||||
public ushort[] InputBufferIndices;
|
||||
|
||||
/// <summary>
|
||||
/// State of each input buffer index kept across invocations of the upsampler.
|
||||
/// </summary>
|
||||
public UpsamplerBufferState[] BufferStates;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
|
@ -44,8 +44,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
@ -58,12 +60,14 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
internal class AppHost
|
||||
{
|
||||
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
|
||||
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||
private const int TargetFps = 60;
|
||||
private const float VolumeDelta = 0.05f;
|
||||
|
||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||
private readonly IntPtr InvisibleCursorWin;
|
||||
private readonly IntPtr DefaultCursorWin;
|
||||
|
||||
private readonly long _ticksPerFrame;
|
||||
private readonly Stopwatch _chrono;
|
||||
@ -81,7 +85,6 @@ namespace Ryujinx.Ava
|
||||
private float _newVolume;
|
||||
private KeyboardHotkeyState _prevHotkeyState;
|
||||
|
||||
private bool _hideCursorOnIdle;
|
||||
private long _lastCursorMoveTime;
|
||||
private bool _isCursorInRenderer;
|
||||
|
||||
@ -131,7 +134,6 @@ namespace Ryujinx.Ava
|
||||
_accountManager = accountManager;
|
||||
_userChannelPersistence = userChannelPersistence;
|
||||
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||
_topLevel = topLevel;
|
||||
@ -159,9 +161,14 @@ namespace Ryujinx.Ava
|
||||
|
||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
||||
|
||||
_topLevel.PointerLeave += TopLevel_PointerLeave;
|
||||
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
InvisibleCursorWin = CreateEmptyCursor();
|
||||
DefaultCursorWin = CreateArrowCursor();
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
@ -172,20 +179,47 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Control visual)
|
||||
if (sender is MainWindow window)
|
||||
{
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
|
||||
var point = e.GetCurrentPoint(visual).Position;
|
||||
if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null)
|
||||
{
|
||||
var point = e.GetCurrentPoint(window).Position;
|
||||
var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip;
|
||||
|
||||
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
|
||||
_isCursorInRenderer = point.X >= bounds.X &&
|
||||
point.X <= bounds.Width + bounds.X &&
|
||||
point.Y >= bounds.Y &&
|
||||
point.Y <= bounds.Height + bounds.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
|
||||
private void ShowCursor()
|
||||
{
|
||||
_isCursorInRenderer = false;
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetCursor(DefaultCursorWin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HideCursor()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = InvisibleCursor;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetCursor(InvisibleCursorWin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SetRendererWindowSize(Size size)
|
||||
@ -380,7 +414,6 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||
|
||||
_topLevel.PointerLeave -= TopLevel_PointerLeave;
|
||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||
|
||||
_gpuCancellationTokenSource.Cancel();
|
||||
@ -406,19 +439,10 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(delegate
|
||||
if (state.NewValue)
|
||||
{
|
||||
_hideCursorOnIdle = state.NewValue;
|
||||
|
||||
if (_hideCursorOnIdle)
|
||||
{
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
}
|
||||
});
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> LoadGuestApplication()
|
||||
@ -860,29 +884,6 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleScreenState()
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_hideCursorOnIdle)
|
||||
{
|
||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool UpdateFrame()
|
||||
{
|
||||
if (!_isActive)
|
||||
@ -890,23 +891,44 @@ namespace Ryujinx.Ava
|
||||
return false;
|
||||
}
|
||||
|
||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||
|
||||
if (_viewModel.IsActive)
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
if (_isCursorInRenderer)
|
||||
{
|
||||
HideCursor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCursor();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ConfigurationState.Instance.HideCursorOnIdle)
|
||||
{
|
||||
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||
{
|
||||
HideCursor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
HandleScreenState();
|
||||
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||
|
||||
if (_viewModel.IsActive)
|
||||
{
|
||||
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
||||
|
||||
if (currentHotkeyState != _prevHotkeyState)
|
||||
|
@ -234,6 +234,9 @@
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
||||
</Style>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.NumberBoxTextBoxStyle">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
||||
</Style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using FluentAvalonia.Core;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||
{
|
||||
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
||||
int button = (int)args.InitialPressMouseButton - 1;
|
||||
|
||||
if (PressedButtons.Count() >= button)
|
||||
{
|
||||
PressedButtons[button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||
{
|
||||
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
|
||||
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
||||
|
||||
if (PressedButtons.Count() >= button)
|
||||
{
|
||||
PressedButtons[button] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
||||
@ -86,12 +97,18 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public void SetMousePressed(MouseButton button)
|
||||
{
|
||||
PressedButtons[(int)button] = true;
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
PressedButtons[(int)button] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMouseReleased(MouseButton button)
|
||||
{
|
||||
PressedButtons[(int)button] = false;
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
PressedButtons[(int)button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(double x, double y)
|
||||
@ -101,7 +118,12 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public bool IsButtonPressed(MouseButton button)
|
||||
{
|
||||
return PressedButtons[(int)button];
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
return PressedButtons[(int)button];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Size GetClientSize()
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Ava
|
||||
public static double DesktopScaleFactor { get; set; } = 1.0;
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
|
||||
|
@ -34,6 +34,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
WindowHandle = IntPtr.Zero;
|
||||
X11Display = IntPtr.Zero;
|
||||
NsView = IntPtr.Zero;
|
||||
MetalLayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public EmbeddedWindow()
|
||||
@ -42,7 +44,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
stateObserverable.Subscribe(StateChanged);
|
||||
|
||||
this.Initialized += NativeEmbeddedWindow_Initialized;
|
||||
Initialized += NativeEmbeddedWindow_Initialized;
|
||||
}
|
||||
|
||||
public virtual void OnWindowCreated() { }
|
||||
@ -127,7 +129,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CS_OWNDC,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
||||
hCursor = CreateArrowCursor()
|
||||
};
|
||||
|
||||
var atom = RegisterClassEx(ref wndClassEx);
|
||||
@ -198,6 +200,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
KeyModifiers.None));
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,22 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr CreateEmptyCursor()
|
||||
{
|
||||
return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 });
|
||||
}
|
||||
|
||||
public static IntPtr CreateArrowCursor()
|
||||
{
|
||||
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW);
|
||||
}
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr SetCursor(IntPtr handle);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
||||
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -16,7 +12,6 @@ namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class SaveModel : BaseModel
|
||||
{
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private long _size;
|
||||
|
||||
public ulong SaveId { get; }
|
||||
@ -41,11 +36,29 @@ namespace Ryujinx.Ava.UI.Models
|
||||
|
||||
public bool SizeAvailable { get; set; }
|
||||
|
||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
||||
public string SizeString => GetSizeString();
|
||||
|
||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
private string GetSizeString()
|
||||
{
|
||||
const int scale = 1024;
|
||||
string[] orders = { "GiB", "MiB", "KiB" };
|
||||
long max = (long)Math.Pow(scale, orders.Length);
|
||||
|
||||
foreach (string order in orders)
|
||||
{
|
||||
if (Size > max)
|
||||
{
|
||||
return $"{decimal.Divide(Size, max):##.##} {order}";
|
||||
}
|
||||
|
||||
max /= scale;
|
||||
}
|
||||
|
||||
return "0 KiB";
|
||||
}
|
||||
|
||||
public SaveModel(SaveDataInfo info, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
SaveId = info.SaveDataId;
|
||||
TitleId = info.ProgramId;
|
||||
UserId = info.UserId;
|
||||
|
@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 0;
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
|
||||
public string TimeZone { get; set; }
|
||||
@ -311,25 +311,66 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||
|
||||
// User Interface
|
||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||
ShowConfirmExit = config.ShowConfirmExit;
|
||||
HideCursorOnIdle = config.HideCursorOnIdle;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||
|
||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||
CustomThemePath = config.Ui.CustomThemePath;
|
||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||
|
||||
// Input
|
||||
EnableDockedMode = config.System.EnableDockedMode;
|
||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||
EnableMouse = config.Hid.EnableMouse;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
||||
|
||||
// System
|
||||
Region = (int)config.System.Region.Value;
|
||||
Language = (int)config.System.Language.Value;
|
||||
TimeZone = config.System.TimeZone;
|
||||
|
||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||
|
||||
DateOffset = dateTimeOffset.Date;
|
||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||
EnableVsync = config.Graphics.EnableVsync;
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
ExpandDramSize = config.System.ExpandRam;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
|
||||
// CPU
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
|
||||
// Graphics
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
|
||||
// Audio
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
Volume = config.System.AudioVolume * 100;
|
||||
|
||||
// Network
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
|
||||
// Logging
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
EnableInfo = config.Logger.EnableInfo;
|
||||
@ -339,94 +380,69 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||
Volume = config.System.AudioVolume * 100;
|
||||
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
TimeZone = config.System.TimeZone;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
CustomThemePath = config.Ui.CustomThemePath;
|
||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
|
||||
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
||||
|
||||
Language = (int)config.System.Language.Value;
|
||||
Region = (int)config.System.Region.Value;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
|
||||
float anisotropy = config.Graphics.MaxAnisotropy;
|
||||
|
||||
MaxAnisotropy = anisotropy == -1 ? 0 : (int)(MathF.Log2(anisotropy));
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
|
||||
int resolution = config.Graphics.ResScale;
|
||||
|
||||
ResolutionScale = resolution == -1 ? 0 : resolution;
|
||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||
|
||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||
|
||||
DateOffset = dateTimeOffset.Date;
|
||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||
|
||||
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
// User Interface
|
||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
||||
|
||||
if (_directoryChanged)
|
||||
{
|
||||
List<string> gameDirs = new List<string>(GameDirectories);
|
||||
List<string> gameDirs = new(GameDirectories);
|
||||
config.Ui.GameDirs.Value = gameDirs;
|
||||
}
|
||||
|
||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||
|
||||
// Input
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
||||
|
||||
// System
|
||||
config.System.Region.Value = (Region)Region;
|
||||
config.System.Language.Value = (Language)Language;
|
||||
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
config.System.TimeZone.Value = TimeZone;
|
||||
}
|
||||
|
||||
config.Logger.EnableError.Value = EnableError;
|
||||
config.Logger.EnableTrace.Value = EnableTrace;
|
||||
config.Logger.EnableWarn.Value = EnableWarn;
|
||||
config.Logger.EnableInfo.Value = EnableInfo;
|
||||
config.Logger.EnableStub.Value = EnableStub;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||
|
||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
|
||||
// Graphics
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||
config.System.Language.Value = (Language)Language;
|
||||
config.System.Region.Value = (Region)Region;
|
||||
|
||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
@ -434,22 +450,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
|
||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||
|
||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||
|
||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
|
||||
float anisotropy = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
|
||||
config.Graphics.MaxAnisotropy.Value = anisotropy;
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.ResScale.Value = ResolutionScale == 0 ? -1 : ResolutionScale;
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.System.AudioVolume.Value = Volume / 100;
|
||||
|
||||
// Audio
|
||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||
if (audioBackend != config.System.AudioBackend.Value)
|
||||
{
|
||||
@ -458,7 +461,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||
}
|
||||
|
||||
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
||||
config.System.AudioVolume.Value = Volume / 100;
|
||||
|
||||
// Network
|
||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||
|
||||
// Logging
|
||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||
config.Logger.EnableStub.Value = EnableStub;
|
||||
config.Logger.EnableInfo.Value = EnableInfo;
|
||||
config.Logger.EnableWarn.Value = EnableWarn;
|
||||
config.Logger.EnableError.Value = EnableError;
|
||||
config.Logger.EnableTrace.Value = EnableTrace;
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
|
@ -13,8 +13,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private int _sortIndex;
|
||||
private int _orderIndex;
|
||||
private string _search;
|
||||
private ObservableCollection<SaveModel> _saves;
|
||||
private ObservableCollection<SaveModel> _views;
|
||||
private ObservableCollection<SaveModel> _saves = new();
|
||||
private ObservableCollection<SaveModel> _views = new();
|
||||
private AccountManager _accountManager;
|
||||
|
||||
public string SaveManagerHeading =>
|
||||
@ -77,8 +77,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public UserSaveManagerViewModel(AccountManager accountManager)
|
||||
{
|
||||
_accountManager = accountManager;
|
||||
_saves = new ObservableCollection<SaveModel>();
|
||||
_views = new ObservableCollection<SaveModel>();
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
|
@ -74,7 +74,6 @@
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
KeyUp="SearchBox_OnKeyUp"
|
||||
Text="{Binding SearchText}"
|
||||
|
@ -82,9 +82,6 @@
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
||||
</ComboBoxItem>
|
||||
@ -97,6 +94,9 @@
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ui:NumberBox
|
||||
Margin="10,0,0,0"
|
||||
|
@ -55,6 +55,11 @@
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBox.Styles>
|
||||
<Style Selector="ContentControl#ContentPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
</ComboBox.Styles>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
@ -69,6 +74,11 @@
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderDescending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBox.Styles>
|
||||
<Style Selector="ContentControl#ContentPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
</ComboBox.Styles>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
@ -122,6 +132,8 @@
|
||||
Height="42"
|
||||
Width="42"
|
||||
Padding="10"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Help"
|
||||
|
@ -94,7 +94,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
var saveModel = new SaveModel(save, _virtualFileSystem);
|
||||
saves.Add(saveModel);
|
||||
}
|
||||
}
|
||||
@ -137,10 +137,9 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId);
|
||||
ViewModel.Saves.Remove(saveModel);
|
||||
ViewModel.Sort();
|
||||
}
|
||||
|
||||
ViewModel.Saves.Remove(saveModel);
|
||||
ViewModel.Views.Remove(saveModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public readonly bool HasFrontFacingBug;
|
||||
public readonly bool HasVectorIndexingBug;
|
||||
public readonly bool NeedsFragmentOutputSpecialization;
|
||||
public readonly bool ReduceShaderPrecision;
|
||||
|
||||
public readonly bool SupportsAstcCompression;
|
||||
public readonly bool SupportsBc123Compression;
|
||||
@ -49,6 +51,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
string vendorName,
|
||||
bool hasFrontFacingBug,
|
||||
bool hasVectorIndexingBug,
|
||||
bool needsFragmentOutputSpecialization,
|
||||
bool reduceShaderPrecision,
|
||||
bool supportsAstcCompression,
|
||||
bool supportsBc123Compression,
|
||||
bool supportsBc45Compression,
|
||||
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
VendorName = vendorName;
|
||||
HasFrontFacingBug = hasFrontFacingBug;
|
||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
|
||||
ReduceShaderPrecision = reduceShaderPrecision;
|
||||
SupportsAstcCompression = supportsAstcCompression;
|
||||
SupportsBc123Compression = supportsBc123Compression;
|
||||
SupportsBc45Compression = supportsBc45Compression;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
internal class SpecializationStateUpdater
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private GpuChannelGraphicsState _graphics;
|
||||
private GpuChannelPoolState _pool;
|
||||
|
||||
@ -18,6 +20,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
private bool _changed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the specialization state updater class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public SpecializationStateUpdater(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the specialization state has changed.
|
||||
/// </summary>
|
||||
@ -232,6 +243,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the type of the outputs produced by the fragment shader based on the current render target state.
|
||||
/// </summary>
|
||||
/// <param name="rtControl">The render target control register</param>
|
||||
/// <param name="state">The color attachment state</param>
|
||||
public void SetFragmentOutputTypes(RtControl rtControl, ref Array8<RtColorState> state)
|
||||
{
|
||||
bool changed = false;
|
||||
int count = rtControl.UnpackCount();
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||
|
||||
var colorState = state[rtIndex];
|
||||
|
||||
if (index < count && StateUpdater.IsRtEnabled(colorState))
|
||||
{
|
||||
Format format = colorState.Format.Convert().Format;
|
||||
|
||||
AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
|
||||
|
||||
if (type != _graphics.FragmentOutputTypes[index])
|
||||
{
|
||||
_graphics.FragmentOutputTypes[index] = type;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||
/// </summary>
|
||||
|
@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given register group is dirty without clearing it.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group to check</param>
|
||||
/// <returns>True if dirty, false otherwise</returns>
|
||||
public bool IsDirty(int groupIndex)
|
||||
{
|
||||
return (_dirtyMask & (1UL << groupIndex)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
||||
/// </summary>
|
||||
|
@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public const int ScissorStateIndex = 16;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
public const int RenderTargetStateIndex = 27;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
@ -264,6 +265,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_prevTfEnable = false;
|
||||
}
|
||||
|
||||
if (_updateTracker.IsDirty(RenderTargetStateIndex))
|
||||
{
|
||||
UpdateRenderTargetSpecialization();
|
||||
}
|
||||
|
||||
_updateTracker.Update(ulong.MaxValue);
|
||||
|
||||
// If any state that the shader depends on changed,
|
||||
@ -526,12 +532,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates specialization state based on render target state.
|
||||
/// </summary>
|
||||
public void UpdateRenderTargetSpecialization()
|
||||
{
|
||||
_currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a render target color buffer is used.
|
||||
/// </summary>
|
||||
/// <param name="colorState">Color buffer information</param>
|
||||
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
||||
private static bool IsRtEnabled(RtColorState colorState)
|
||||
internal static bool IsRtEnabled(RtColorState colorState)
|
||||
{
|
||||
// Colors are disabled by writing 0 to the format.
|
||||
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
||||
@ -893,7 +907,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||
|
||||
format = Format.R32G32B32A32Float;
|
||||
format = vertexAttrib.UnpackType() switch
|
||||
{
|
||||
VertexAttribType.Sint => Format.R32G32B32A32Sint,
|
||||
VertexAttribType.Uint => Format.R32G32B32A32Uint,
|
||||
_ => Format.R32G32B32A32Float
|
||||
};
|
||||
}
|
||||
|
||||
vertexAttribs[index] = new VertexAttribDescriptor(
|
||||
|
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||
|
||||
var spec = new SpecializationStateUpdater();
|
||||
var spec = new SpecializationStateUpdater(context);
|
||||
var drawState = new DrawState();
|
||||
|
||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
@ -31,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
internal MemoryManager MemoryManager => _memoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Host hardware capabilities from the GPU context.
|
||||
/// </summary>
|
||||
internal ref Capabilities Capabilities => ref _context.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a GPU channel.
|
||||
/// </summary>
|
||||
@ -67,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||
TextureManager.ReloadPools();
|
||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
||||
memoryManager.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -78,7 +84,9 @@ namespace Ryujinx.Graphics.Gpu
|
||||
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
||||
{
|
||||
TextureManager.ReloadPools();
|
||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
||||
|
||||
var memoryManager = Volatile.Read(ref _memoryManager);
|
||||
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1431,9 +1431,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
bool isGpuThread = _context.IsGpuThread();
|
||||
|
||||
if (isGpuThread)
|
||||
{
|
||||
// No need to wait if we're on the GPU thread, we can just clear the modified flag immediately.
|
||||
handle.Modified = false;
|
||||
}
|
||||
|
||||
_context.Renderer.BackgroundContextAction(() =>
|
||||
{
|
||||
handle.Sync(_context);
|
||||
if (!isGpuThread)
|
||||
{
|
||||
handle.Sync(_context);
|
||||
}
|
||||
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
|
@ -232,32 +232,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
{
|
||||
bool needsSync = !context.IsGpuThread();
|
||||
ulong registeredSync = _registeredSync;
|
||||
long diff = (long)(context.SyncNumber - registeredSync);
|
||||
|
||||
if (needsSync)
|
||||
if (diff > 0)
|
||||
{
|
||||
ulong registeredSync = _registeredSync;
|
||||
long diff = (long)(context.SyncNumber - registeredSync);
|
||||
context.Renderer.WaitSync(registeredSync);
|
||||
|
||||
if (diff > 0)
|
||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||
{
|
||||
context.Renderer.WaitSync(registeredSync);
|
||||
|
||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||
{
|
||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||
return;
|
||||
}
|
||||
|
||||
Modified = false;
|
||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||
}
|
||||
else
|
||||
{
|
||||
Modified = false;
|
||||
}
|
||||
|
||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -107,6 +107,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AttributeType QueryFragmentOutputType(int location)
|
||||
{
|
||||
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
||||
|
||||
|
@ -113,6 +113,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return _state.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AttributeType QueryFragmentOutputType(int location)
|
||||
{
|
||||
return _state.GraphicsState.FragmentOutputTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
||||
|
||||
|
@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
};
|
||||
}
|
||||
|
||||
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
||||
|
||||
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
||||
|
||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||
|
@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
public bool HasUnalignedStorageBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Type of the fragment shader outputs.
|
||||
/// </summary>
|
||||
public Array8<AttributeType> FragmentOutputTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
@ -105,6 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
||||
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
||||
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
||||
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
||||
public GpuChannelGraphicsState(
|
||||
bool earlyZForce,
|
||||
PrimitiveTopology topology,
|
||||
@ -120,7 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
float alphaTestReference,
|
||||
ref Array32<AttributeType> attributeTypes,
|
||||
bool hasConstantBufferDrawParameters,
|
||||
bool hasUnalignedStorageBuffer)
|
||||
bool hasUnalignedStorageBuffer,
|
||||
ref Array8<AttributeType> fragmentOutputTypes)
|
||||
{
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
@ -137,6 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
AttributeTypes = attributeTypes;
|
||||
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||
FragmentOutputTypes = fragmentOutputTypes;
|
||||
}
|
||||
}
|
||||
}
|
@ -530,6 +530,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
vendorName: GpuVendor,
|
||||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||
needsFragmentOutputSpecialization: false,
|
||||
reduceShaderPrecision: false,
|
||||
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
||||
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
||||
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
||||
|
@ -346,12 +346,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
string name = context.OperandManager.DeclareLocal(decl);
|
||||
|
||||
context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
|
||||
context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetVarTypeName(AggregateType type, bool precise = true)
|
||||
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
|
||||
{
|
||||
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
{
|
||||
precise = false;
|
||||
}
|
||||
|
||||
return type switch
|
||||
{
|
||||
AggregateType.Void => "void",
|
||||
@ -666,7 +671,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AppendLine($"layout (location = {attr}) out vec4 {name};");
|
||||
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
|
||||
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
|
||||
{
|
||||
AttributeType.Sint => "ivec4",
|
||||
AttributeType.Uint => "uvec4",
|
||||
_ => "vec4"
|
||||
};
|
||||
|
||||
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
|
||||
{
|
||||
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AppendLine($"layout (location = {attr}) out {type} {name};");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
for (int i = 1; i < info.Functions.Count; i++)
|
||||
{
|
||||
context.AppendLine($"{GetFunctionSignature(info.Functions[i])};");
|
||||
context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};");
|
||||
}
|
||||
|
||||
context.AppendLine();
|
||||
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
context.CurrentFunction = function;
|
||||
|
||||
context.AppendLine(GetFunctionSignature(function, funcName));
|
||||
context.AppendLine(GetFunctionSignature(context, function, funcName));
|
||||
context.EnterScope();
|
||||
|
||||
Declarations.DeclareLocals(context, function);
|
||||
@ -54,23 +54,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.LeaveScope();
|
||||
}
|
||||
|
||||
private static string GetFunctionSignature(StructuredFunction function, string funcName = null)
|
||||
private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null)
|
||||
{
|
||||
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
|
||||
|
||||
for (int i = 0; i < function.InArguments.Length; i++)
|
||||
{
|
||||
args[i] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
||||
args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
||||
}
|
||||
|
||||
for (int i = 0; i < function.OutArguments.Length; i++)
|
||||
{
|
||||
int j = i + function.InArguments.Length;
|
||||
|
||||
args[j] = $"out {Declarations.GetVarTypeName(function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
||||
args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
||||
}
|
||||
|
||||
return $"{Declarations.GetVarTypeName(function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
||||
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
||||
}
|
||||
|
||||
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
||||
|
@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||
{
|
||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({imageConst})";
|
||||
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
|
||||
}
|
||||
|
||||
return imageConst;
|
||||
@ -513,7 +513,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||
{
|
||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({scalarValue})";
|
||||
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,6 +577,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
context.Decorate(spvVar, Decoration.Patch);
|
||||
}
|
||||
|
||||
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.Invariant);
|
||||
}
|
||||
|
||||
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
|
||||
|
||||
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
|
||||
|
@ -2194,13 +2194,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||
{
|
||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.FP64, result);
|
||||
}
|
||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||
{
|
||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
else
|
||||
@ -2255,13 +2265,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||
{
|
||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.FP64, result);
|
||||
}
|
||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||
{
|
||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
else
|
||||
|
@ -114,6 +114,16 @@ namespace Ryujinx.Graphics.Shader
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries output type for fragment shaders.
|
||||
/// </summary>
|
||||
/// <param name="location">Location of the framgent output</param>
|
||||
/// <returns>Output location</returns>
|
||||
AttributeType QueryFragmentOutputType(int location)
|
||||
{
|
||||
return AttributeType.Float;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size X for compute shaders.
|
||||
/// </summary>
|
||||
@ -186,6 +196,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about whether to reduce precision to improve performance.
|
||||
/// </summary>
|
||||
/// <returns>True if precision is limited to vertex position, false otherwise</returns>
|
||||
bool QueryHostReducedPrecision()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
|
@ -128,7 +128,15 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false);
|
||||
int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||
var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
|
||||
{
|
||||
AttributeType.Sint => AggregateType.S32,
|
||||
AttributeType.Uint => AggregateType.U32,
|
||||
_ => AggregateType.FP32
|
||||
};
|
||||
|
||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
|
||||
}
|
||||
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
||||
{
|
||||
|
@ -140,6 +140,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _attachments[index];
|
||||
}
|
||||
|
||||
public ComponentType GetAttachmentComponentType(int index)
|
||||
{
|
||||
if (_colors != null && (uint)index < _colors.Length)
|
||||
{
|
||||
var format = _colors[index].Info.Format;
|
||||
|
||||
if (format.IsSint())
|
||||
{
|
||||
return ComponentType.SignedInteger;
|
||||
}
|
||||
else if (format.IsUint())
|
||||
{
|
||||
return ComponentType.UnsignedInteger;
|
||||
}
|
||||
}
|
||||
|
||||
return ComponentType.Float;
|
||||
}
|
||||
|
||||
public bool IsValidColorAttachment(int bindIndex)
|
||||
{
|
||||
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
|
||||
|
@ -1,7 +1,20 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
[Flags]
|
||||
enum PortabilitySubsetFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
VertexBufferAlignment4B = 1,
|
||||
NoTriangleFans = 1 << 1,
|
||||
NoPointMode = 1 << 2,
|
||||
No3DImageView = 1 << 3,
|
||||
NoLodBias = 1 << 4
|
||||
}
|
||||
|
||||
readonly struct HardwareCapabilities
|
||||
{
|
||||
public readonly bool SupportsIndexTypeUint8;
|
||||
@ -23,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly uint MaxSubgroupSize;
|
||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||
public readonly SampleCountFlags SupportedSampleCounts;
|
||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||
|
||||
public HardwareCapabilities(
|
||||
bool supportsIndexTypeUint8,
|
||||
@ -43,7 +57,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
uint minSubgroupSize,
|
||||
uint maxSubgroupSize,
|
||||
ShaderStageFlags requiredSubgroupSizeStages,
|
||||
SampleCountFlags supportedSampleCounts)
|
||||
SampleCountFlags supportedSampleCounts,
|
||||
PortabilitySubsetFlags portabilitySubset)
|
||||
{
|
||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||
@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
MaxSubgroupSize = maxSubgroupSize;
|
||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||
SupportedSampleCounts = supportedSampleCounts;
|
||||
PortabilitySubset = portabilitySubset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,13 @@ using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
enum ComponentType
|
||||
{
|
||||
Float,
|
||||
SignedInteger,
|
||||
UnsignedInteger
|
||||
}
|
||||
|
||||
class HelperShader : IDisposable
|
||||
{
|
||||
private const int UniformBufferAlignment = 256;
|
||||
@ -18,7 +25,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly ISampler _samplerNearest;
|
||||
private readonly IProgram _programColorBlit;
|
||||
private readonly IProgram _programColorBlitClearAlpha;
|
||||
private readonly IProgram _programColorClear;
|
||||
private readonly IProgram _programColorClearF;
|
||||
private readonly IProgram _programColorClearSI;
|
||||
private readonly IProgram _programColorClearUI;
|
||||
private readonly IProgram _programStrideChange;
|
||||
private readonly IProgram _programConvertIndexBuffer;
|
||||
private readonly IProgram _programConvertIndirectData;
|
||||
@ -63,10 +72,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
_programColorClear = gd.CreateProgramWithMinimalLayout(new[]
|
||||
_programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
_programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
_programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var strideChangeBindings = new ShaderBindings(
|
||||
@ -242,6 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int dstWidth,
|
||||
int dstHeight,
|
||||
VkFormat dstFormat,
|
||||
ComponentType type,
|
||||
Rectangle<int> scissor)
|
||||
{
|
||||
const int ClearColorBufferSize = 16;
|
||||
@ -273,7 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
scissors[0] = scissor;
|
||||
|
||||
_pipeline.SetProgram(_programColorClear);
|
||||
IProgram program;
|
||||
|
||||
if (type == ComponentType.SignedInteger)
|
||||
{
|
||||
program = _programColorClearSI;
|
||||
}
|
||||
else if (type == ComponentType.UnsignedInteger)
|
||||
{
|
||||
program = _programColorClearUI;
|
||||
}
|
||||
else
|
||||
{
|
||||
program = _programColorClearF;
|
||||
}
|
||||
|
||||
_pipeline.SetProgram(program);
|
||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
||||
_pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
@ -948,7 +985,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_programColorBlitClearAlpha.Dispose();
|
||||
_programColorBlit.Dispose();
|
||||
_programColorClear.Dispose();
|
||||
_programColorClearF.Dispose();
|
||||
_programColorClearSI.Dispose();
|
||||
_programColorClearUI.Dispose();
|
||||
_programStrideChange.Dispose();
|
||||
_programConvertIndexBuffer.Dispose();
|
||||
_programConvertIndirectData.Dispose();
|
||||
|
104
Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
104
Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
{
|
||||
enum MVKConfigLogLevel : int
|
||||
{
|
||||
None = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Debug = 4
|
||||
}
|
||||
|
||||
enum MVKConfigTraceVulkanCalls : int
|
||||
{
|
||||
None = 0,
|
||||
Enter = 1,
|
||||
EnterExit = 2,
|
||||
Duration = 3
|
||||
}
|
||||
|
||||
enum MVKConfigAutoGPUCaptureScope : int
|
||||
{
|
||||
None = 0,
|
||||
Device = 1,
|
||||
Frame = 2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum MVKConfigAdvertiseExtensions : int
|
||||
{
|
||||
All = 0x00000001,
|
||||
MoltenVK = 0x00000002,
|
||||
WSI = 0x00000004,
|
||||
Portability = 0x00000008
|
||||
}
|
||||
|
||||
enum MVKVkSemaphoreSupportStyle : int
|
||||
{
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF
|
||||
}
|
||||
|
||||
readonly struct Bool32
|
||||
{
|
||||
uint Value { get; }
|
||||
|
||||
public Bool32(uint value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Bool32(bool value)
|
||||
{
|
||||
Value = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
public static implicit operator bool(Bool32 val) => val.Value == 1;
|
||||
public static implicit operator Bool32(bool val) => new Bool32(val);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct MVKConfiguration
|
||||
{
|
||||
public Bool32 DebugMode;
|
||||
public Bool32 ShaderConversionFlipVertexY;
|
||||
public Bool32 SynchronousQueueSubmits;
|
||||
public Bool32 PrefillMetalCommandBuffers;
|
||||
public uint MaxActiveMetalCommandBuffersPerQueue;
|
||||
public Bool32 SupportLargeQueryPools;
|
||||
public Bool32 PresentWithCommandBuffer;
|
||||
public Bool32 SwapchainMagFilterUseNearest;
|
||||
public ulong MetalCompileTimeout;
|
||||
public Bool32 PerformanceTracking;
|
||||
public uint PerformanceLoggingFrameCount;
|
||||
public Bool32 DisplayWatermark;
|
||||
public Bool32 SpecializedQueueFamilies;
|
||||
public Bool32 SwitchSystemGPU;
|
||||
public Bool32 FullImageViewSwizzle;
|
||||
public uint DefaultGPUCaptureScopeQueueFamilyIndex;
|
||||
public uint DefaultGPUCaptureScopeQueueIndex;
|
||||
public Bool32 FastMathEnabled;
|
||||
public MVKConfigLogLevel LogLevel;
|
||||
public MVKConfigTraceVulkanCalls TraceVulkanCalls;
|
||||
public Bool32 ForceLowPowerGPU;
|
||||
public Bool32 SemaphoreUseMTLFence;
|
||||
public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle;
|
||||
public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope;
|
||||
public IntPtr AutoGPUCaptureOutputFilepath;
|
||||
public Bool32 Texture1DAs2D;
|
||||
public Bool32 PreallocateDescriptors;
|
||||
public Bool32 UseCommandPooling;
|
||||
public Bool32 UseMTLHeap;
|
||||
public Bool32 LogActivityPerformanceInline;
|
||||
public uint ApiVersionToAdvertise;
|
||||
public MVKConfigAdvertiseExtensions AdvertiseExtensions;
|
||||
public Bool32 ResumeLostDevice;
|
||||
public Bool32 UseMetalArgumentBuffers;
|
||||
}
|
||||
}
|
31
Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
31
Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static partial class MVKInitialization
|
||||
{
|
||||
[LibraryImport("libMoltenVK.dylib")]
|
||||
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
||||
|
||||
[LibraryImport("libMoltenVK.dylib")]
|
||||
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
var configSize = (IntPtr)Marshal.SizeOf<MVKConfiguration>();
|
||||
|
||||
vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize);
|
||||
|
||||
config.UseMetalArgumentBuffers = true;
|
||||
|
||||
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
||||
config.SynchronousQueueSubmits = false;
|
||||
|
||||
vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -50,6 +51,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private Auto<DisposableRenderPass> _renderPass;
|
||||
private int _writtenAttachmentCount;
|
||||
|
||||
private bool _framebufferUsingColorWriteMask;
|
||||
|
||||
private ITexture[] _preMaskColors;
|
||||
private ITexture _preMaskDepthStencil;
|
||||
|
||||
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
||||
|
||||
private IndexBufferState _indexBuffer;
|
||||
@ -905,22 +911,35 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
SignalStateChange();
|
||||
|
||||
if (writtenAttachments != _writtenAttachmentCount)
|
||||
if (_framebufferUsingColorWriteMask)
|
||||
{
|
||||
SignalAttachmentChange();
|
||||
_writtenAttachmentCount = writtenAttachments;
|
||||
SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalStateChange();
|
||||
|
||||
if (writtenAttachments != _writtenAttachmentCount)
|
||||
{
|
||||
SignalAttachmentChange();
|
||||
_writtenAttachmentCount = writtenAttachments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
FramebufferParams?.UpdateModifications();
|
||||
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
SignalAttachmentChange();
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
{
|
||||
FramebufferParams?.UpdateModifications();
|
||||
CreateFramebuffer(colors, depthStencil);
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
SignalAttachmentChange();
|
||||
_framebufferUsingColorWriteMask = false;
|
||||
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
||||
}
|
||||
|
||||
public void SetRenderTargetScale(float scale)
|
||||
@ -1102,7 +1121,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
int vbSize = vertexBuffer.Buffer.Size;
|
||||
|
||||
if (Gd.Vendor == Vendor.Amd && vertexBuffer.Stride > 0)
|
||||
if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
|
||||
{
|
||||
// AMD has a bug where if offset + stride * count is greater than
|
||||
// the size, then the last attribute will have the wrong value.
|
||||
@ -1119,7 +1138,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
buffer.Dispose();
|
||||
|
||||
if ((vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
||||
if (!Gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B) &&
|
||||
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
||||
{
|
||||
buffer = new VertexBufferState(
|
||||
vb,
|
||||
@ -1259,8 +1279,62 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil)
|
||||
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
if (filterWriteMasked)
|
||||
{
|
||||
// TBDR GPUs don't work properly if the same attachment is bound to multiple targets,
|
||||
// due to each attachment being a copy of the real attachment, rather than a direct write.
|
||||
|
||||
// Just try to remove duplicate attachments.
|
||||
// Save a copy of the array to rebind when mask changes.
|
||||
|
||||
void maskOut()
|
||||
{
|
||||
if (!_framebufferUsingColorWriteMask)
|
||||
{
|
||||
_preMaskColors = colors.ToArray();
|
||||
_preMaskDepthStencil = depthStencil;
|
||||
}
|
||||
|
||||
// If true, then the framebuffer must be recreated when the mask changes.
|
||||
_framebufferUsingColorWriteMask = true;
|
||||
}
|
||||
|
||||
// Look for textures that are masked out.
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
if (colors[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
|
||||
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
// Check each binding for a duplicate binding before it.
|
||||
|
||||
if (colors[i] == colors[j])
|
||||
{
|
||||
// Prefer the binding with no write mask.
|
||||
ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j];
|
||||
if (vkBlend.ColorWriteMask == 0)
|
||||
{
|
||||
colors[i] = null;
|
||||
maskOut();
|
||||
}
|
||||
else if (vkBlend2.ColorWriteMask == 0)
|
||||
{
|
||||
colors[j] = null;
|
||||
maskOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
||||
UpdatePipelineAttachmentFormats();
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(int)FramebufferParams.Width,
|
||||
(int)FramebufferParams.Height,
|
||||
FramebufferParams.AttachmentFormats[index],
|
||||
FramebufferParams.GetAttachmentComponentType(index),
|
||||
ClearScissor);
|
||||
}
|
||||
else
|
||||
|
@ -0,0 +1,9 @@
|
||||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec4 clear_colour;
|
||||
layout (location = 0) out ivec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
colour = floatBitsToInt(clear_colour);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec4 clear_colour;
|
||||
layout (location = 0) out uvec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
colour = floatBitsToUint(clear_colour);
|
||||
}
|
@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
|
||||
0x3C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
};
|
||||
|
||||
public static readonly byte[] ColorClearFragmentShaderSource = new byte[]
|
||||
public static readonly byte[] ColorClearFFragmentShaderSource = new byte[]
|
||||
{
|
||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
|
||||
@ -459,6 +459,68 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
|
||||
0x0C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
};
|
||||
|
||||
public static readonly byte[] ColorClearSIFragmentShaderSource = new byte[]
|
||||
{
|
||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
|
||||
0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
|
||||
0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
|
||||
0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||
0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
};
|
||||
|
||||
public static readonly byte[] ColorClearUIFragmentShaderSource = new byte[]
|
||||
{
|
||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
|
||||
0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
|
||||
0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
|
||||
0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||
0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
};
|
||||
|
||||
public static readonly byte[] ColorClearVertexShaderSource = new byte[]
|
||||
{
|
||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x36, 0x00, 0x00, 0x00,
|
||||
|
@ -106,7 +106,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
flags |= ImageCreateFlags.CreateCubeCompatibleBit;
|
||||
}
|
||||
|
||||
if (type == ImageType.Type3D)
|
||||
if (type == ImageType.Type3D && !gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
|
||||
{
|
||||
flags |= ImageCreateFlags.Create2DArrayCompatibleBit;
|
||||
}
|
||||
|
@ -94,8 +94,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
||||
var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
||||
|
||||
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType)
|
||||
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags = 0)
|
||||
{
|
||||
var usage = new ImageViewUsageCreateInfo()
|
||||
{
|
||||
SType = StructureType.ImageViewUsageCreateInfo,
|
||||
Usage = usageFlags
|
||||
};
|
||||
|
||||
var imageCreateInfo = new ImageViewCreateInfo()
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
@ -103,7 +109,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ViewType = viewType,
|
||||
Format = format,
|
||||
Components = cm,
|
||||
SubresourceRange = sr
|
||||
SubresourceRange = sr,
|
||||
PNext = usageFlags == 0 ? null : &usage
|
||||
};
|
||||
|
||||
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
@ -124,9 +131,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Framebuffer attachments also require 3D textures to be bound as 2D array.
|
||||
if (info.Target == Target.Texture3D)
|
||||
{
|
||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
|
||||
if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
|
||||
{
|
||||
if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil()))
|
||||
{
|
||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1);
|
||||
|
||||
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
|
||||
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2D, ImageUsageFlags.ColorAttachmentBit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
|
||||
|
||||
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
|
||||
}
|
||||
}
|
||||
|
||||
Valid = true;
|
||||
@ -353,7 +372,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
if (VulkanConfiguration.UseSlowSafeBlitOnAmd &&
|
||||
_gd.Vendor == Vendor.Amd &&
|
||||
(_gd.Vendor == Vendor.Amd || _gd.IsMoltenVk) &&
|
||||
src.Info.Target == Target.Texture2D &&
|
||||
dst.Info.Target == Target.Texture2D &&
|
||||
!dst.Info.Format.IsDepthOrStencil())
|
||||
|
@ -5,9 +5,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
enum Vendor
|
||||
{
|
||||
Amd,
|
||||
ImgTec,
|
||||
Intel,
|
||||
Nvidia,
|
||||
ARM,
|
||||
Qualcomm,
|
||||
Apple,
|
||||
Unknown
|
||||
}
|
||||
|
||||
@ -21,7 +24,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return id switch
|
||||
{
|
||||
0x1002 => Vendor.Amd,
|
||||
0x1010 => Vendor.ImgTec,
|
||||
0x106B => Vendor.Apple,
|
||||
0x10DE => Vendor.Nvidia,
|
||||
0x13B5 => Vendor.ARM,
|
||||
0x8086 => Vendor.Intel,
|
||||
0x5143 => Vendor.Qualcomm,
|
||||
_ => Vendor.Unknown
|
||||
@ -34,6 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
0x1002 => "AMD",
|
||||
0x1010 => "ImgTec",
|
||||
0x106B => "Apple",
|
||||
0x10DE => "NVIDIA",
|
||||
0x13B5 => "ARM",
|
||||
0x1AE0 => "Google",
|
||||
|
@ -82,9 +82,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
}
|
||||
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -389,6 +389,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &featuresCustomBorderColorSupported;
|
||||
}
|
||||
|
||||
PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
{
|
||||
supportedFeaturesRobustness2.PNext = features2.PNext;
|
||||
|
||||
features2.PNext = &supportedFeaturesRobustness2;
|
||||
}
|
||||
|
||||
api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
|
||||
|
||||
var supportedFeatures = features2.Features;
|
||||
@ -428,14 +440,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
pExtendedFeatures = &featuresTransformFeedback;
|
||||
|
||||
var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
NullDescriptor = true
|
||||
};
|
||||
var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
NullDescriptor = supportedFeaturesRobustness2.NullDescriptor
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresRobustness2;
|
||||
pExtendedFeatures = &featuresRobustness2;
|
||||
}
|
||||
|
||||
var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Ryujinx.Graphics.Vulkan.MoltenVK;
|
||||
using Ryujinx.Graphics.Vulkan.Queries;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
@ -77,6 +78,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsAmdWindows { get; private set; }
|
||||
internal bool IsIntelWindows { get; private set; }
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
public string GpuVendor { get; private set; }
|
||||
public string GpuRenderer { get; private set; }
|
||||
public string GpuVersion { get; private set; }
|
||||
@ -93,6 +96,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Shaders = new HashSet<ShaderCollection>();
|
||||
Textures = new HashSet<ITexture>();
|
||||
Samplers = new HashSet<SamplerHolder>();
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
MVKInitialization.Initialize();
|
||||
|
||||
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
|
||||
IsMoltenVk = true;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
||||
@ -161,7 +172,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
properties2.PNext = &propertiesTransformFeedback;
|
||||
}
|
||||
|
||||
Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
|
||||
PhysicalDevicePortabilitySubsetPropertiesKHR propertiesPortabilitySubset = new PhysicalDevicePortabilitySubsetPropertiesKHR()
|
||||
{
|
||||
SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr
|
||||
};
|
||||
|
||||
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
||||
{
|
||||
@ -183,6 +197,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
|
||||
};
|
||||
|
||||
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
|
||||
{
|
||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
{
|
||||
features2.PNext = &featuresRobustness2;
|
||||
@ -200,8 +219,31 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &featuresCustomBorderColor;
|
||||
}
|
||||
|
||||
bool usePortability = supportedExtensions.Contains("VK_KHR_portability_subset");
|
||||
|
||||
if (usePortability)
|
||||
{
|
||||
propertiesPortabilitySubset.PNext = properties2.PNext;
|
||||
properties2.PNext = &propertiesPortabilitySubset;
|
||||
|
||||
featuresPortabilitySubset.PNext = features2.PNext;
|
||||
features2.PNext = &featuresPortabilitySubset;
|
||||
}
|
||||
|
||||
Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
||||
|
||||
var portabilityFlags = PortabilitySubsetFlags.None;
|
||||
|
||||
if (usePortability)
|
||||
{
|
||||
portabilityFlags |= propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment > 1 ? PortabilitySubsetFlags.VertexBufferAlignment4B : 0;
|
||||
portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans;
|
||||
portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode;
|
||||
portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView;
|
||||
portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
|
||||
}
|
||||
|
||||
bool customBorderColorSupported = supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
||||
featuresCustomBorderColor.CustomBorderColors &&
|
||||
featuresCustomBorderColor.CustomBorderColorWithoutFormat;
|
||||
@ -224,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
|
||||
supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
|
||||
features2.Features.MultiViewport,
|
||||
featuresRobustness2.NullDescriptor,
|
||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||
supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
|
||||
supportsTransformFeedback,
|
||||
propertiesTransformFeedback.TransformFeedbackQueries,
|
||||
@ -232,7 +274,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
propertiesSubgroupSizeControl.MinSubgroupSize,
|
||||
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
||||
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
||||
supportedSampleCounts);
|
||||
supportedSampleCounts,
|
||||
portabilityFlags);
|
||||
|
||||
MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
|
||||
|
||||
@ -413,6 +456,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
|
||||
GAL.Format.R4G4B4A4Unorm);
|
||||
|
||||
bool supportsAstcFormats = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
|
||||
GAL.Format.Astc4x4Unorm,
|
||||
GAL.Format.Astc5x4Unorm,
|
||||
GAL.Format.Astc5x5Unorm,
|
||||
GAL.Format.Astc6x5Unorm,
|
||||
GAL.Format.Astc6x6Unorm,
|
||||
GAL.Format.Astc8x5Unorm,
|
||||
GAL.Format.Astc8x6Unorm,
|
||||
GAL.Format.Astc8x8Unorm,
|
||||
GAL.Format.Astc10x5Unorm,
|
||||
GAL.Format.Astc10x6Unorm,
|
||||
GAL.Format.Astc10x8Unorm,
|
||||
GAL.Format.Astc10x10Unorm,
|
||||
GAL.Format.Astc12x10Unorm,
|
||||
GAL.Format.Astc12x12Unorm,
|
||||
GAL.Format.Astc4x4Srgb,
|
||||
GAL.Format.Astc5x4Srgb,
|
||||
GAL.Format.Astc5x5Srgb,
|
||||
GAL.Format.Astc6x5Srgb,
|
||||
GAL.Format.Astc6x6Srgb,
|
||||
GAL.Format.Astc8x5Srgb,
|
||||
GAL.Format.Astc8x6Srgb,
|
||||
GAL.Format.Astc8x8Srgb,
|
||||
GAL.Format.Astc10x5Srgb,
|
||||
GAL.Format.Astc10x6Srgb,
|
||||
GAL.Format.Astc10x8Srgb,
|
||||
GAL.Format.Astc10x10Srgb,
|
||||
GAL.Format.Astc12x10Srgb,
|
||||
GAL.Format.Astc12x12Srgb);
|
||||
|
||||
PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features
|
||||
@ -434,7 +507,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GpuVendor,
|
||||
hasFrontFacingBug: IsIntelWindows,
|
||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr,
|
||||
needsFragmentOutputSpecialization: IsMoltenVk,
|
||||
reduceShaderPrecision: IsMoltenVk,
|
||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
||||
supportsBc123Compression: supportsBc123CompressionFormat,
|
||||
supportsBc45Compression: supportsBc45CompressionFormat,
|
||||
supportsBc67Compression: supportsBc67CompressionFormat,
|
||||
@ -515,12 +590,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
IsTBDR = IsMoltenVk || Vendor == Vendor.Qualcomm || Vendor == Vendor.ARM || Vendor == Vendor.ImgTec;
|
||||
|
||||
GpuVendor = vendorName;
|
||||
GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);
|
||||
GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
|
||||
|
||||
IsAmdGcn = Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
|
||||
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
|
||||
|
||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
||||
}
|
||||
@ -531,6 +607,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles,
|
||||
GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip,
|
||||
GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) ? GAL.PrimitiveTopology.Triangles : topology,
|
||||
_ => topology
|
||||
};
|
||||
}
|
||||
@ -540,6 +617,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return topology switch
|
||||
{
|
||||
GAL.PrimitiveTopology.Quads => true,
|
||||
GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
@ -553,7 +631,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
|
||||
{
|
||||
if (Vendor != Vendor.Nvidia)
|
||||
if (Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B))
|
||||
{
|
||||
alignment = 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Vendor != Vendor.Nvidia)
|
||||
{
|
||||
// Vulkan requires that vertex attributes are globally aligned by their component size,
|
||||
// so buffer strides that don't divide by the largest scalar element are invalid.
|
||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
class SDL2MouseDriver : IGamepadDriver
|
||||
{
|
||||
private const int CursorHideIdleTime = 8; // seconds
|
||||
private const int CursorHideIdleTime = 5; // seconds
|
||||
|
||||
private bool _isDisposed;
|
||||
private HideCursor _hideCursor;
|
||||
|
@ -267,6 +267,8 @@ namespace Ryujinx.Horizon.Generators.Hipc
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
int inArgIndex = 0;
|
||||
int outArgIndex = 0;
|
||||
int inCopyHandleIndex = 0;
|
||||
int inMoveHandleIndex = 0;
|
||||
int inObjectIndex = 0;
|
||||
@ -284,7 +286,7 @@ namespace Ryujinx.Horizon.Generators.Hipc
|
||||
{
|
||||
if (IsNonSpanOutBuffer(compilation, parameter))
|
||||
{
|
||||
generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));");
|
||||
generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({outArgIndex++}));");
|
||||
|
||||
argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}";
|
||||
}
|
||||
@ -302,7 +304,7 @@ namespace Ryujinx.Horizon.Generators.Hipc
|
||||
switch (argType)
|
||||
{
|
||||
case CommandArgType.InArgument:
|
||||
value = $"CommandSerialization.DeserializeArg<{canonicalTypeName}>(inRawData, processor.GetInArgOffset({index}))";
|
||||
value = $"CommandSerialization.DeserializeArg<{canonicalTypeName}>(inRawData, processor.GetInArgOffset({inArgIndex++}))";
|
||||
break;
|
||||
case CommandArgType.InCopyHandle:
|
||||
value = $"CommandSerialization.DeserializeCopyHandle(ref context, {inCopyHandleIndex++})";
|
||||
|
@ -42,7 +42,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, Uid.Null);
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, userId, true);
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -107,25 +107,25 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
}
|
||||
|
||||
[CmifCommand(20100)]
|
||||
public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, Uid.Null);
|
||||
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId);
|
||||
}
|
||||
|
||||
[CmifCommand(20101)]
|
||||
public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, userId, true);
|
||||
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId);
|
||||
}
|
||||
|
||||
[CmifCommand(40100)] // 2.0.0+
|
||||
@ -164,7 +164,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
private static Result ProcessPlayReport(PlayReportKind playReportKind, ulong pid, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, Uid userId, bool withUserId = false)
|
||||
private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, Uid userId, bool withUserId = false, Sdk.Ncm.ApplicationId applicationId = default)
|
||||
{
|
||||
if (withUserId)
|
||||
{
|
||||
@ -191,16 +191,23 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
return PrepoResult.InvalidBufferSize;
|
||||
}
|
||||
|
||||
// NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
|
||||
// Reports are stored internally and an event is signaled to transmit them.
|
||||
|
||||
StringBuilder builder = new();
|
||||
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("PlayReport log:");
|
||||
builder.AppendLine($" Kind: {playReportKind}");
|
||||
builder.AppendLine($" Pid: {pid}");
|
||||
|
||||
// NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
|
||||
// Reports are stored internally and an event is signaled to transmit them.
|
||||
if (pid != 0)
|
||||
{
|
||||
builder.AppendLine($" Pid: {pid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($" ApplicationId: {applicationId}");
|
||||
}
|
||||
|
||||
if (!userId.IsNull)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
|
||||
namespace Ryujinx.Horizon.Sdk.Account
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly record struct Uid
|
||||
readonly record struct Uid
|
||||
{
|
||||
public readonly long High;
|
||||
public readonly long Low;
|
||||
|
52
Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs
Normal file
52
Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Ncm
|
||||
{
|
||||
readonly struct ApplicationId
|
||||
{
|
||||
public readonly ulong Id;
|
||||
|
||||
public static int Length => sizeof(ulong);
|
||||
|
||||
public static ApplicationId First => new(0x0100000000010000);
|
||||
|
||||
public static ApplicationId Last => new(0x01FFFFFFFFFFFFFF);
|
||||
|
||||
public static ApplicationId Invalid => new(0);
|
||||
|
||||
public bool IsValid => Id >= First.Id && Id <= Last.Id;
|
||||
|
||||
public ApplicationId(ulong id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ApplicationId applicationId && applicationId.Equals(this);
|
||||
}
|
||||
|
||||
public bool Equals(ApplicationId other)
|
||||
{
|
||||
return other.Id == Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(ApplicationId lhs, ApplicationId rhs)
|
||||
{
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(ApplicationId lhs, ApplicationId rhs)
|
||||
{
|
||||
return !lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"0x{Id:x}";
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ namespace Ryujinx.Horizon.Sdk.Prepo
|
||||
Result RequestImmediateTransmission();
|
||||
Result GetTransmissionStatus(out int status);
|
||||
Result GetSystemSessionId(out ulong systemSessionId);
|
||||
Result SaveSystemReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveSystemReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveSystemReport(ReadOnlySpan<byte> gameRoomBuffer, Ncm.ApplicationId applicationId, ReadOnlySpan<byte> reportBuffer);
|
||||
Result SaveSystemReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, Ncm.ApplicationId applicationId, ReadOnlySpan<byte> reportBuffer);
|
||||
Result IsUserAgreementCheckEnabled(out bool enabled);
|
||||
Result SetUserAgreementCheckEnabled(bool enabled);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
|
||||
{
|
||||
public static ServiceName Invalid { get; } = new ServiceName(0);
|
||||
|
||||
public bool IsInvalid => Packed == 0;
|
||||
public bool IsValid => Packed != 0;
|
||||
|
||||
public int Length => sizeof(ulong);
|
||||
|
||||
|
@ -435,12 +435,7 @@ namespace Ryujinx.Memory
|
||||
|
||||
public static ulong GetPageSize()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
return 1UL << 14;
|
||||
}
|
||||
|
||||
return 1UL << 12;
|
||||
return (ulong)Environment.SystemPageSize;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
||||
|
@ -68,7 +68,7 @@ namespace Ryujinx.Ui
|
||||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||
|
||||
// Hide Cursor
|
||||
const int CursorHideIdleTime = 8; // seconds
|
||||
const int CursorHideIdleTime = 5; // seconds
|
||||
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
|
||||
private long _lastCursorMoveTime;
|
||||
private bool _hideCursorOnIdle;
|
||||
|
Reference in New Issue
Block a user