Compare commits

..

10 Commits

Author SHA1 Message Date
Isaac Marovitz
139a930407 Implement missing service calls in pm (#4210)
* Implement `GetTitleId`

Fixes #2516

* Null check + Proper result code

* Better comment

* Implement `GetApplicationProcessId`

* Add TODOs

* Update Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Remove new function from KernelStatic

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-15 22:16:24 +01:00
Isaac Marovitz
719dc97bbd Ava UI: TitleUpdateWindow Refactor (#4276)
* Start Refactor

* Dialogue opens

* Changes

* Switch to ListBox

* Fix bugs and stuff

* Fix spacing

* Implement OpenLocation

* Change icon

* Color

* Color

* Remove background

* Make no update the same height

* Fix height and smooth scroll

* Height

* Fix update selection

* Make window smaller

* Add back remove all button

* Make selection more obvious

* Hide selection bar on SaveManager

* Fix autoscroll

* Fix no update not staying selected

* Better file opener

* Fix

* Revert that

* Update Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Log warning

* Update Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-15 11:11:52 +00:00
merry
41bba5310a Audren: Implement polyphase upsampler (#4256)
* Audren: Implement polyphase upsampler

* prefer shifting to modulo

* prefer MathF

* fix nits

* rm ResampleForUpsampler

* oop

* Array20

* nits
2023-01-15 05:20:49 +01:00
Ac_K
8071c8c8c0 Ava UI: Fixes "Hide Cursor on Idle" for Windows (#4266)
* Ava: Fixes "Hide Cursor on Idle" for Windows

* Add check in MouseDriver and reduce the time of idling

* Fix linux error

* Change idle time everywhere for consistencies
2023-01-15 01:05:44 +01:00
gnisman
b402b4e7f6 Change GetPageSize to use Environment.SystemPageSize (#4291)
* Change GetPageSize to use Environment.SystemPageSize

* Fix PR comment
2023-01-14 15:37:04 -03:00
gdkchan
93df366b2c Fix texture flush from CPU WaitSync regression on OpenGL (#4289) 2023-01-14 11:23:57 -03:00
gdkchan
cd3a15aea5 Fix NRE when MemoryUnmappedHandler is called for a destroyed channel (#4285) 2023-01-14 00:16:06 -03:00
gdkchan
070136b3f7 Fix texture modified on CPU from GPU thread after being modified on GPU not being updated (#4284) 2023-01-13 23:46:45 -03:00
Ac_K
08ab47c6c0 Update Program.cs 2023-01-13 07:56:41 +01:00
Ac_K
85faa9d8fa Revert "Relax Vulkan requirements (#4228)" (#4279)
This reverts commit dca5b14493.
2023-01-13 06:04:59 +00:00
36 changed files with 905 additions and 583 deletions

View File

@@ -40,6 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); 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; UpsamplerInfo = info;
} }
@@ -50,8 +56,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public void Process(CommandList context) public void Process(CommandList context)
{ {
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
for (int i = 0; i < bufferCount; i++) 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> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
float fraction = 0.0f; UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]);
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
} }
} }
} }

View File

@@ -579,52 +579,5 @@ namespace Ryujinx.Audio.Renderer.Dsp
fraction -= (int)fraction; 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;
}
}
} }
} }

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

View File

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

View File

@@ -37,6 +37,11 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler
/// </summary> /// </summary>
public ushort[] InputBufferIndices; public ushort[] InputBufferIndices;
/// <summary>
/// State of each input buffer index kept across invocations of the upsampler.
/// </summary>
public UpsamplerBufferState[] BufferStates;
/// <summary> /// <summary>
/// Create a new <see cref="UpsamplerState"/>. /// Create a new <see cref="UpsamplerState"/>.
/// </summary> /// </summary>

View File

@@ -44,8 +44,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using Image = SixLabors.ImageSharp.Image; using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
@@ -58,12 +60,14 @@ namespace Ryujinx.Ava
{ {
internal class AppHost 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 float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
private const int TargetFps = 60; private const int TargetFps = 60;
private const float VolumeDelta = 0.05f; private const float VolumeDelta = 0.05f;
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None); private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
private readonly IntPtr InvisibleCursorWin;
private readonly IntPtr DefaultCursorWin;
private readonly long _ticksPerFrame; private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono; private readonly Stopwatch _chrono;
@@ -81,7 +85,6 @@ namespace Ryujinx.Ava
private float _newVolume; private float _newVolume;
private KeyboardHotkeyState _prevHotkeyState; private KeyboardHotkeyState _prevHotkeyState;
private bool _hideCursorOnIdle;
private long _lastCursorMoveTime; private long _lastCursorMoveTime;
private bool _isCursorInRenderer; private bool _isCursorInRenderer;
@@ -131,7 +134,6 @@ namespace Ryujinx.Ava
_accountManager = accountManager; _accountManager = accountManager;
_userChannelPersistence = userChannelPersistence; _userChannelPersistence = userChannelPersistence;
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" }; _renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp(); _lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_topLevel = topLevel; _topLevel = topLevel;
@@ -159,9 +161,14 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed; ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
_topLevel.PointerLeave += TopLevel_PointerLeave;
_topLevel.PointerMoved += TopLevel_PointerMoved; _topLevel.PointerMoved += TopLevel_PointerMoved;
if (OperatingSystem.IsWindows())
{
InvisibleCursorWin = CreateEmptyCursor();
DefaultCursorWin = CreateArrowCursor();
}
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
@@ -172,20 +179,47 @@ namespace Ryujinx.Ava
private void TopLevel_PointerMoved(object sender, PointerEventArgs e) private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
{ {
if (sender is Control visual) if (sender is MainWindow window)
{ {
_lastCursorMoveTime = Stopwatch.GetTimestamp(); _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; Dispatcher.UIThread.Post(() =>
_viewModel.Cursor = Cursor.Default; {
_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) private void SetRendererWindowSize(Size size)
@@ -380,7 +414,6 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_topLevel.PointerMoved -= TopLevel_PointerMoved; _topLevel.PointerMoved -= TopLevel_PointerMoved;
_gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource.Cancel();
@@ -406,19 +439,10 @@ namespace Ryujinx.Ava
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state) private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
{ {
Dispatcher.UIThread.InvokeAsync(delegate if (state.NewValue)
{ {
_hideCursorOnIdle = state.NewValue; _lastCursorMoveTime = Stopwatch.GetTimestamp();
}
if (_hideCursorOnIdle)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
else
{
_viewModel.Cursor = Cursor.Default;
}
});
} }
public async Task<bool> LoadGuestApplication() 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() private bool UpdateFrame()
{ {
if (!_isActive) if (!_isActive)
@@ -890,23 +891,44 @@ namespace Ryujinx.Ava
return false; return false;
} }
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_viewModel.IsActive) 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(() => Dispatcher.UIThread.Post(() =>
{ {
HandleScreenState();
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{ {
Device.Application.DiskCacheLoadState?.Cancel(); Device.Application.DiskCacheLoadState?.Cancel();
} }
}); });
}
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_viewModel.IsActive)
{
KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
if (currentHotkeyState != _prevHotkeyState) if (currentHotkeyState != _prevHotkeyState)

View File

@@ -524,7 +524,7 @@
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
"OpenSetupGuideMessage": "Open the Setup Guide", "OpenSetupGuideMessage": "Open the Setup Guide",
"NoUpdate": "No Update", "NoUpdate": "No Update",
"TitleUpdateVersionLabel": "Version {0} - {1}", "TitleUpdateVersionLabel": "Version {0}",
"RyujinxInfo": "Ryujinx - Info", "RyujinxInfo": "Ryujinx - Info",
"RyujinxConfirm": "Ryujinx - Confirmation", "RyujinxConfirm": "Ryujinx - Confirmation",
"FileDialogAllTypes": "All types", "FileDialogAllTypes": "All types",
@@ -585,7 +585,7 @@
"UserProfilesSetProfileImage": "Set Profile Image", "UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required", "UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set", "UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", "GameUpdateWindowHeading": "Manage Updates for {0} ({1})",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
"UserProfilesName": "Name:", "UserProfilesName": "Name:",

View File

@@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using FluentAvalonia.Core;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Numerics; using System.Numerics;
@@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args) 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) 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) private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
@@ -86,12 +97,18 @@ namespace Ryujinx.Ava.Input
public void SetMousePressed(MouseButton button) public void SetMousePressed(MouseButton button)
{ {
PressedButtons[(int)button] = true; if (PressedButtons.Count() >= (int)button)
{
PressedButtons[(int)button] = true;
}
} }
public void SetMouseReleased(MouseButton button) 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) public void SetPosition(double x, double y)
@@ -101,7 +118,12 @@ namespace Ryujinx.Ava.Input
public bool IsButtonPressed(MouseButton button) public bool IsButtonPressed(MouseButton button)
{ {
return PressedButtons[(int)button]; if (PressedButtons.Count() >= (int)button)
{
return PressedButtons[(int)button];
}
return false;
} }
public Size GetClientSize() public Size GetClientSize()

View File

@@ -28,7 +28,7 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0; public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { 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)] [LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
@@ -276,4 +276,4 @@ namespace Ryujinx.Ava
Logger.Shutdown(); Logger.Shutdown();
} }
} }
} }

View File

@@ -34,6 +34,8 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
WindowHandle = IntPtr.Zero; WindowHandle = IntPtr.Zero;
X11Display = IntPtr.Zero; X11Display = IntPtr.Zero;
NsView = IntPtr.Zero;
MetalLayer = IntPtr.Zero;
} }
public EmbeddedWindow() public EmbeddedWindow()
@@ -42,7 +44,7 @@ namespace Ryujinx.Ava.UI.Helpers
stateObserverable.Subscribe(StateChanged); stateObserverable.Subscribe(StateChanged);
this.Initialized += NativeEmbeddedWindow_Initialized; Initialized += NativeEmbeddedWindow_Initialized;
} }
public virtual void OnWindowCreated() { } public virtual void OnWindowCreated() { }
@@ -127,7 +129,7 @@ namespace Ryujinx.Ava.UI.Helpers
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
style = ClassStyles.CS_OWNDC, style = ClassStyles.CS_OWNDC,
lpszClassName = Marshal.StringToHGlobalUni(_className), lpszClassName = Marshal.StringToHGlobalUni(_className),
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW) hCursor = CreateArrowCursor()
}; };
var atom = RegisterClassEx(ref wndClassEx); var atom = RegisterClassEx(ref wndClassEx);
@@ -198,6 +200,7 @@ namespace Ryujinx.Ava.UI.Helpers
KeyModifiers.None)); KeyModifiers.None));
break; break;
} }
return DefWindowProc(hWnd, msg, wParam, lParam); return DefWindowProc(hWnd, msg, wParam, lParam);
} }

View File

@@ -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")] [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
public static partial ushort RegisterClassEx(ref WNDCLASSEX param); public static partial ushort RegisterClassEx(ref WNDCLASSEX param);

View File

@@ -3,23 +3,17 @@ using Ryujinx.Ava.Common.Locale;
namespace Ryujinx.Ava.UI.Models namespace Ryujinx.Ava.UI.Models
{ {
internal class TitleUpdateModel public class TitleUpdateModel
{ {
public bool IsEnabled { get; set; }
public bool IsNoUpdate { get; }
public ApplicationControlProperty Control { get; } public ApplicationControlProperty Control { get; }
public string Path { get; } public string Path { get; }
public string Label => IsNoUpdate public string Label => string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString());
? LocaleManager.Instance[LocaleKeys.NoUpdate]
: string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString(),
Path);
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false) public TitleUpdateModel(ApplicationControlProperty control, string path)
{ {
Control = control; Control = control;
Path = path; Path = path;
IsNoUpdate = isNoUpdate;
} }
} }
} }

View File

@@ -1601,13 +1601,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void OpenTitleUpdateManager() public async void OpenTitleUpdateManager()
{ {
ApplicationData selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) await TitleUpdateWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
{
await new TitleUpdateWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
}
} }
} }

View File

@@ -0,0 +1,226 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SpanHelpers = LibHac.Common.SpanHelpers;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.ViewModels;
public class TitleUpdateViewModel : BaseModel
{
public TitleUpdateMetadata _titleUpdateWindowData;
public readonly string _titleUpdateJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private ulong _titleId { get; }
private string _titleName { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
public AvaloniaList<TitleUpdateModel> TitleUpdates
{
get => _titleUpdates;
set
{
_titleUpdates = value;
OnPropertyChanged();
}
}
public AvaloniaList<object> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public object SelectedUpdate
{
get => _selectedUpdate;
set
{
_selectedUpdate = value;
OnPropertyChanged();
}
}
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
_titleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "",
Paths = new List<string>()
};
}
LoadUpdates();
}
private void LoadUpdates()
{
foreach (string path in _titleUpdateWindowData.Paths)
{
AddUpdate(path);
}
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
SelectedUpdate = selected;
SortUpdates();
}
public void SortUpdates()
{
var list = TitleUpdates.ToList();
list.Sort((first, second) =>
{
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
{
return -1;
}
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
{
return 1;
}
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
});
Views.Clear();
Views.Add(new BaseModel());
Views.AddRange(list);
if (SelectedUpdate == null)
{
SelectedUpdate = Views[0];
}
else if (!TitleUpdates.Contains(SelectedUpdate))
{
if (Views.Count > 1)
{
SelectedUpdate = Views[1];
}
else
{
SelectedUpdate = Views[0];
}
}
}
private void AddUpdate(string path)
{
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
{
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
try
{
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
}
}
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
});
}
}
}
public void RemoveUpdate(TitleUpdateModel update)
{
TitleUpdates.Remove(update);
SortUpdates();
}
public async void Add()
{
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
{
foreach (string file in files)
{
AddUpdate(file);
}
}
}
SortUpdates();
}
}

View File

@@ -107,6 +107,7 @@
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<ListBox <ListBox
Name="SaveList" Name="SaveList"
VirtualizationMode="None"
Items="{Binding Views}" Items="{Binding Views}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
@@ -116,6 +117,9 @@
<Setter Property="Margin" Value="5" /> <Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
</Style> </Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="IsVisible" Value="False" />
</Style>
</ListBox.Styles> </ListBox.Styles>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="models:SaveModel"> <DataTemplate x:DataType="models:SaveModel">

View File

@@ -1,115 +1,135 @@
<window:StyleableWindow <UserControl
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow" x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Width="600" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
Height="400" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
MinWidth="600" Width="500"
MinHeight="400" Height="300"
MaxWidth="600"
MaxHeight="400"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d" mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:TitleUpdateViewModel"
Focusable="True"> Focusable="True">
<Grid Margin="15"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock
Name="Heading"
Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="18"
TextAlignment="Center"
TextWrapping="Wrap" />
<Border <Border
Grid.Row="2" Grid.Row="0"
Margin="5" Margin="0 0 0 24"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="Gray" BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"> BorderThickness="1"
<ScrollViewer CornerRadius="5"
VerticalAlignment="Stretch" Padding="2.5">
HorizontalScrollBarVisibility="Auto" <ListBox
VerticalScrollBarVisibility="Auto"> VirtualizationMode="None"
<ItemsControl Background="Transparent"
Margin="10" SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}"
HorizontalAlignment="Stretch" Items="{Binding Views}">
VerticalAlignment="Stretch" <ListBox.DataTemplates>
Items="{Binding _titleUpdates}"> <DataTemplate
<ItemsControl.ItemTemplate> DataType="models:TitleUpdateModel">
<DataTemplate> <Panel Margin="10">
<RadioButton <TextBlock
Padding="8,0" HorizontalAlignment="Left"
VerticalContentAlignment="Center" VerticalAlignment="Center"
GroupName="Update" TextWrapping="Wrap"
IsChecked="{Binding IsEnabled, Mode=TwoWay}"> Text="{Binding Label}" />
<Label <StackPanel
Margin="0" Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button
VerticalAlignment="Center" VerticalAlignment="Center"
Content="{Binding Label}" HorizontalAlignment="Right"
FontSize="12" /> Padding="10"
</RadioButton> MinWidth="0"
</DataTemplate> MinHeight="0"
</ItemsControl.ItemTemplate> Click="OpenLocation">
</ItemsControl> <ui:SymbolIcon
</ScrollViewer> Symbol="OpenFolder"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
<Button
VerticalAlignment="Center"
HorizontalAlignment="Right"
Padding="10"
MinWidth="0"
MinHeight="0"
Click="RemoveUpdate">
<ui:SymbolIcon
Symbol="Cancel"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Panel>
</DataTemplate>
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="33"
Margin="10">
<TextBlock
HorizontalAlignment="Left"
VerticalAlignment="Center"
TextWrapping="Wrap"
Text="{locale:Locale NoUpdate}" />
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.Styles>
</ListBox>
</Border> </Border>
<DockPanel <Panel
Grid.Row="3" Grid.Row="1"
Margin="0"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left"> <StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Left">
<Button <Button
Name="AddButton" Name="AddButton"
MinWidth="90" MinWidth="90"
Margin="5" Command="{ReflectionBinding Add}">
Command="{Binding Add}">
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button> </Button>
<Button
Name="RemoveButton"
MinWidth="90"
Margin="5"
Command="{Binding RemoveSelected}">
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
<Button <Button
Name="RemoveAllButton" Name="RemoveAllButton"
MinWidth="90" MinWidth="90"
Margin="5" Click="RemoveAll">
Command="{Binding RemoveAll}">
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
</Button> </Button>
</DockPanel> </StackPanel>
<DockPanel Margin="0" HorizontalAlignment="Right"> <StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Right">
<Button <Button
Name="SaveButton" Name="SaveButton"
MinWidth="90" MinWidth="90"
Margin="5" Click="Save">
Command="{Binding Save}">
<TextBlock Text="{locale:Locale SettingsButtonSave}" /> <TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button> </Button>
<Button <Button
Name="CancelButton" Name="CancelButton"
MinWidth="90" MinWidth="90"
Margin="5" Click="Close">
Command="{Binding Close}">
<TextBlock Text="{locale:Locale InputDialogCancel}" /> <TextBlock Text="{locale:Locale InputDialogCancel}" />
</Button> </Button>
</DockPanel> </StackPanel>
</DockPanel> </Panel>
</Grid> </Grid>
</window:StyleableWindow> </UserControl>

View File

@@ -1,271 +1,116 @@
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Interactivity;
using LibHac.Common; using Avalonia.Styling;
using LibHac.Fs; using FluentAvalonia.UI.Controls;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Path = System.IO.Path; using System.Threading.Tasks;
using SpanHelpers = LibHac.Common.SpanHelpers; using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
public partial class TitleUpdateWindow : StyleableWindow public partial class TitleUpdateWindow : UserControl
{ {
private readonly string _titleUpdateJsonPath; public TitleUpdateViewModel ViewModel;
private TitleUpdateMetadata _titleUpdateWindowData;
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
private ulong _titleId { get; }
private string _titleName { get; }
public TitleUpdateWindow() public TitleUpdateWindow()
{ {
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
} }
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
_virtualFileSystem = virtualFileSystem; DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId, titleName);
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
_titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
}
catch
{
_titleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "",
Paths = new List<string>()
};
}
DataContext = this;
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
LoadUpdates();
PrintHeading();
} }
private void PrintHeading() public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
Heading.Text = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], _titleUpdates.Count - 1, _titleName, _titleId.ToString("X16")); ContentDialog contentDialog = new()
}
private void LoadUpdates()
{
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
foreach (string path in _titleUpdateWindowData.Paths)
{ {
AddUpdate(path); PrimaryButtonText = "",
} SecondaryButtonText = "",
CloseButtonText = "",
if (_titleUpdateWindowData.Selected == "") Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
{ Title = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], titleName, titleId.ToString("X16"))
_titleUpdates[0].IsEnabled = true;
}
else
{
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
foreach (TitleUpdateModel update in enabled)
{
update.IsEnabled = false;
}
if (selected != null)
{
selected.IsEnabled = true;
}
}
SortUpdates();
}
private void AddUpdate(string path)
{
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
{
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
try
{
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in _titleUpdates)
{
update.IsEnabled = false;
}
_titleUpdates.Last().IsEnabled = true;
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
}
}
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
});
}
}
}
private void RemoveUpdates(bool removeSelectedOnly = false)
{
if (removeSelectedOnly)
{
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
}
else
{
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
}
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
SortUpdates();
PrintHeading();
}
public void RemoveSelected()
{
RemoveUpdates(true);
}
public void RemoveAll()
{
RemoveUpdates();
}
public async void Add()
{
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true
}; };
dialog.Filters.Add(new FileDialogFilter Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
{ bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
Name = "NSP",
Extensions = { "nsp" }
});
string[] files = await dialog.ShowAsync(this); contentDialog.Styles.Add(bottomBorder);
if (files != null) await ContentDialogHelper.ShowAsync(contentDialog);
{
foreach (string file in files)
{
AddUpdate(file);
}
}
SortUpdates();
PrintHeading();
} }
private void SortUpdates() private void Close(object sender, RoutedEventArgs e)
{ {
var list = _titleUpdates.ToList(); ((ContentDialog)Parent).Hide();
list.Sort((first, second) =>
{
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
{
return -1;
}
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
{
return 1;
}
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
});
_titleUpdates.Clear();
_titleUpdates.AddRange(list);
} }
public void Save() public void Save(object sender, RoutedEventArgs e)
{ {
_titleUpdateWindowData.Paths.Clear(); ViewModel._titleUpdateWindowData.Paths.Clear();
_titleUpdateWindowData.Selected = ""; ViewModel._titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in _titleUpdates) foreach (TitleUpdateModel update in ViewModel.TitleUpdates)
{ {
_titleUpdateWindowData.Paths.Add(update.Path); ViewModel._titleUpdateWindowData.Paths.Add(update.Path);
if (update.IsEnabled) if (update == ViewModel.SelectedUpdate)
{ {
_titleUpdateWindowData.Selected = update.Path; ViewModel._titleUpdateWindowData.Selected = update.Path;
} }
} }
using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough)) using (FileStream titleUpdateJsonStream = File.Create(ViewModel._titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
{ {
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(ViewModel._titleUpdateWindowData, true)));
} }
if (Owner is MainWindow window) if (VisualRoot is MainWindow window)
{ {
window.ViewModel.LoadApplications(); window.ViewModel.LoadApplications();
} }
Close(); ((ContentDialog)Parent).Hide();
}
private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is TitleUpdateModel model)
{
OpenHelper.LocateFile(model.Path);
}
}
}
private void RemoveUpdate(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
}
}
private void RemoveAll(object sender, RoutedEventArgs e)
{
ViewModel.TitleUpdates.Clear();
ViewModel.SortUpdates();
} }
} }
} }

View File

@@ -73,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. // Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
TextureManager.ReloadPools(); TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune(); memoryManager.Physical.BufferCache.QueuePrune();
} }
/// <summary> /// <summary>
@@ -84,7 +84,9 @@ namespace Ryujinx.Graphics.Gpu
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e) private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
{ {
TextureManager.ReloadPools(); TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune();
var memoryManager = Volatile.Read(ref _memoryManager);
memoryManager?.Physical.BufferCache.QueuePrune();
} }
/// <summary> /// <summary>

View File

@@ -1431,9 +1431,20 @@ namespace Ryujinx.Graphics.Gpu.Image
return; 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(() => _context.Renderer.BackgroundContextAction(() =>
{ {
handle.Sync(_context); if (!isGpuThread)
{
handle.Sync(_context);
}
Storage.SignalModifiedDirty(); Storage.SignalModifiedDirty();

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// A tracking handle for a texture group, which represents a range of views in a storage texture. /// A tracking handle for a texture group, which represents a range of views in a storage texture.
/// Retains a list of overlapping texture views, a modified flag, and tracking for each /// Retains a list of overlapping texture views, a modified flag, and tracking for each
/// CPU VA range that the views cover. /// CPU VA range that the views cover.
/// Also tracks copy dependencies for the handle - references to other handles that must be kept /// Also tracks copy dependencies for the handle - references to other handles that must be kept
/// in sync with this one before use. /// in sync with this one before use.
/// </summary> /// </summary>
class TextureGroupHandle : IDisposable class TextureGroupHandle : IDisposable
@@ -232,32 +232,23 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="context">The GPU context used to wait for sync</param> /// <param name="context">The GPU context used to wait for sync</param>
public void Sync(GpuContext context) 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; context.Renderer.WaitSync(registeredSync);
long diff = (long)(context.SyncNumber - registeredSync);
if (diff > 0) if ((long)(_modifiedSync - registeredSync) > 0)
{ {
context.Renderer.WaitSync(registeredSync); // Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
return;
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;
} }
// 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; 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> /// <summary>

View File

@@ -14,12 +14,6 @@ namespace Ryujinx.Graphics.Vulkan
MemoryPropertyFlags.HostCoherentBit | MemoryPropertyFlags.HostCoherentBit |
MemoryPropertyFlags.HostCachedBit; MemoryPropertyFlags.HostCachedBit;
// Some drivers don't expose a "HostCached" memory type,
// so we need those alternative flags for the allocation to succeed there.
private const MemoryPropertyFlags DefaultBufferMemoryAltFlags =
MemoryPropertyFlags.HostVisibleBit |
MemoryPropertyFlags.HostCoherentBit;
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags = private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
MemoryPropertyFlags.DeviceLocalBit; MemoryPropertyFlags.DeviceLocalBit;
@@ -100,21 +94,9 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements); gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
MemoryPropertyFlags allocateFlags; var allocateFlags = deviceLocal ? DeviceLocalBufferMemoryFlags : DefaultBufferMemoryFlags;
MemoryPropertyFlags allocateFlagsAlt;
if (deviceLocal) var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, allocateFlags);
{
allocateFlags = DeviceLocalBufferMemoryFlags;
allocateFlagsAlt = DeviceLocalBufferMemoryFlags;
}
else
{
allocateFlags = DefaultBufferMemoryFlags;
allocateFlagsAlt = DefaultBufferMemoryAltFlags;
}
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, allocateFlags, allocateFlagsAlt);
if (allocation.Memory.Handle == 0UL) if (allocation.Memory.Handle == 0UL)
{ {

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
public int[] AttachmentIndices { get; } public int[] AttachmentIndices { get; }
public int AttachmentsCount { get; } public int AttachmentsCount { get; }
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1; public int MaxColorAttachmentIndex { get; }
public bool HasDepthStencil { get; } public bool HasDepthStencil { get; }
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
@@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentSamples = new uint[count]; AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count]; AttachmentFormats = new VkFormat[count];
AttachmentIndices = new int[count]; AttachmentIndices = new int[count];
MaxColorAttachmentIndex = colors.Length - 1;
uint width = uint.MaxValue; uint width = uint.MaxValue;
uint height = uint.MaxValue; uint height = uint.MaxValue;

View File

@@ -27,16 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryRequirements requirements, MemoryRequirements requirements,
MemoryPropertyFlags flags = 0) MemoryPropertyFlags flags = 0)
{ {
return AllocateDeviceMemory(physicalDevice, requirements, flags, flags); int memoryTypeIndex = FindSuitableMemoryTypeIndex(_api, physicalDevice, requirements.MemoryTypeBits, flags);
}
public MemoryAllocation AllocateDeviceMemory(
PhysicalDevice physicalDevice,
MemoryRequirements requirements,
MemoryPropertyFlags flags,
MemoryPropertyFlags alternativeFlags)
{
int memoryTypeIndex = FindSuitableMemoryTypeIndex(_api, physicalDevice, requirements.MemoryTypeBits, flags, alternativeFlags);
if (memoryTypeIndex < 0) if (memoryTypeIndex < 0)
{ {
return default; return default;
@@ -65,35 +56,21 @@ namespace Ryujinx.Graphics.Vulkan
return newBl.Allocate(size, alignment, map); return newBl.Allocate(size, alignment, map);
} }
private static int FindSuitableMemoryTypeIndex( private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits, MemoryPropertyFlags flags)
Vk api,
PhysicalDevice physicalDevice,
uint memoryTypeBits,
MemoryPropertyFlags flags,
MemoryPropertyFlags alternativeFlags)
{ {
int bestCandidateIndex = -1;
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties); api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
for (int i = 0; i < properties.MemoryTypeCount; i++) for (int i = 0; i < properties.MemoryTypeCount; i++)
{ {
var type = properties.MemoryTypes[i]; var type = properties.MemoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0) if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags))
{ {
if (type.PropertyFlags.HasFlag(flags)) return i;
{
return i;
}
else if (type.PropertyFlags.HasFlag(alternativeFlags))
{
bestCandidateIndex = i;
}
} }
} }
return bestCandidateIndex; return -1;
} }
public void Dispose() public void Dispose()

View File

@@ -1344,7 +1344,8 @@ namespace Ryujinx.Graphics.Vulkan
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan(); var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats); FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++) int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex + (FramebufferParams.HasDepthStencil ? 1 : 0);
for (int i = FramebufferParams.AttachmentFormats.Length; i <= maxAttachmentIndex; i++)
{ {
dstAttachmentFormats[i] = 0; dstAttachmentFormats[i] = 0;
} }

View File

@@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
Intel, Intel,
Nvidia, Nvidia,
ARM, ARM,
Broadcom,
Qualcomm, Qualcomm,
Apple, Apple,
Unknown Unknown
@@ -29,7 +28,6 @@ namespace Ryujinx.Graphics.Vulkan
0x106B => Vendor.Apple, 0x106B => Vendor.Apple,
0x10DE => Vendor.Nvidia, 0x10DE => Vendor.Nvidia,
0x13B5 => Vendor.ARM, 0x13B5 => Vendor.ARM,
0x14E4 => Vendor.Broadcom,
0x8086 => Vendor.Intel, 0x8086 => Vendor.Intel,
0x5143 => Vendor.Qualcomm, 0x5143 => Vendor.Qualcomm,
_ => Vendor.Unknown _ => Vendor.Unknown
@@ -45,7 +43,6 @@ namespace Ryujinx.Graphics.Vulkan
0x106B => "Apple", 0x106B => "Apple",
0x10DE => "NVIDIA", 0x10DE => "NVIDIA",
0x13B5 => "ARM", 0x13B5 => "ARM",
0x14E4 => "Broadcom",
0x1AE0 => "Google", 0x1AE0 => "Google",
0x5143 => "Qualcomm", 0x5143 => "Qualcomm",
0x8086 => "Intel", 0x8086 => "Intel",

View File

@@ -162,6 +162,7 @@ namespace Ryujinx.Graphics.Vulkan
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt)) if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
{ {
Logger.Error?.Print(LogClass.Gpu, msg); Logger.Error?.Print(LogClass.Gpu, msg);
//throw new Exception(msg);
} }
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt)) else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
{ {
@@ -378,34 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceFeatures2 SType = StructureType.PhysicalDeviceFeatures2
}; };
PhysicalDeviceVulkan11Features supportedFeaturesVk11 = new PhysicalDeviceVulkan11Features() PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColorSupported = new PhysicalDeviceCustomBorderColorFeaturesEXT()
{ {
SType = StructureType.PhysicalDeviceVulkan11Features, SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
PNext = features2.PNext
};
features2.PNext = &supportedFeaturesVk11;
PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT()
{
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
PNext = features2.PNext
}; };
if (supportedExtensions.Contains("VK_EXT_custom_border_color")) if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
{ {
features2.PNext = &supportedFeaturesCustomBorderColor; features2.PNext = &featuresCustomBorderColorSupported;
}
PhysicalDeviceTransformFeedbackFeaturesEXT supportedFeaturesTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
{
SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
PNext = features2.PNext
};
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
{
features2.PNext = &supportedFeaturesTransformFeedback;
} }
PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
@@ -427,48 +408,41 @@ namespace Ryujinx.Graphics.Vulkan
var features = new PhysicalDeviceFeatures() var features = new PhysicalDeviceFeatures()
{ {
DepthBiasClamp = true, DepthBiasClamp = true,
DepthClamp = supportedFeatures.DepthClamp, DepthClamp = true,
DualSrcBlend = supportedFeatures.DualSrcBlend, DualSrcBlend = true,
FragmentStoresAndAtomics = true, FragmentStoresAndAtomics = true,
GeometryShader = supportedFeatures.GeometryShader, GeometryShader = supportedFeatures.GeometryShader,
ImageCubeArray = true, ImageCubeArray = true,
IndependentBlend = true, IndependentBlend = true,
LogicOp = supportedFeatures.LogicOp, LogicOp = supportedFeatures.LogicOp,
MultiViewport = supportedFeatures.MultiViewport, MultiViewport = true,
PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery, PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
SamplerAnisotropy = true, SamplerAnisotropy = true,
ShaderClipDistance = true, ShaderClipDistance = true,
ShaderFloat64 = supportedFeatures.ShaderFloat64, ShaderFloat64 = supportedFeatures.ShaderFloat64,
ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended, ShaderImageGatherExtended = true,
ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample, ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample,
// ShaderStorageImageReadWithoutFormat = true, // ShaderStorageImageReadWithoutFormat = true,
// ShaderStorageImageWriteWithoutFormat = true, // ShaderStorageImageWriteWithoutFormat = true,
TessellationShader = supportedFeatures.TessellationShader, TessellationShader = true,
VertexPipelineStoresAndAtomics = true, VertexPipelineStoresAndAtomics = true,
RobustBufferAccess = useRobustBufferAccess RobustBufferAccess = useRobustBufferAccess
}; };
void* pExtendedFeatures = null; void* pExtendedFeatures = null;
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback; var featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
{ {
featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT() SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
{ PNext = pExtendedFeatures,
SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt, TransformFeedback = true
PNext = pExtendedFeatures, };
TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback
};
pExtendedFeatures = &featuresTransformFeedback; pExtendedFeatures = &featuresTransformFeedback;
}
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
if (supportedExtensions.Contains("VK_EXT_robustness2")) if (supportedExtensions.Contains("VK_EXT_robustness2"))
{ {
featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
{ {
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
@@ -491,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.PhysicalDeviceVulkan11Features, SType = StructureType.PhysicalDeviceVulkan11Features,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters ShaderDrawParameters = true
}; };
pExtendedFeatures = &featuresVk11; pExtendedFeatures = &featuresVk11;
@@ -552,8 +526,8 @@ namespace Ryujinx.Graphics.Vulkan
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor; PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
if (supportedExtensions.Contains("VK_EXT_custom_border_color") && if (supportedExtensions.Contains("VK_EXT_custom_border_color") &&
supportedFeaturesCustomBorderColor.CustomBorderColors && featuresCustomBorderColorSupported.CustomBorderColors &&
supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat) featuresCustomBorderColorSupported.CustomBorderColorWithoutFormat)
{ {
featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT() featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT()
{ {

View File

@@ -590,11 +590,7 @@ namespace Ryujinx.Graphics.Vulkan
IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows); IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows); IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsTBDR = IsMoltenVk || IsTBDR = IsMoltenVk || Vendor == Vendor.Qualcomm || Vendor == Vendor.ARM || Vendor == Vendor.ImgTec;
Vendor == Vendor.Qualcomm ||
Vendor == Vendor.ARM ||
Vendor == Vendor.Broadcom ||
Vendor == Vendor.ImgTec;
GpuVendor = vendorName; GpuVendor = vendorName;
GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);

View File

@@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageSharingMode = SharingMode.Exclusive, ImageSharingMode = SharingMode.Exclusive,
ImageArrayLayers = 1, ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform, PreTransform = capabilities.CurrentTransform,
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha), CompositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr,
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled), PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
Clipped = true, Clipped = true,
OldSwapchain = oldSwapchain OldSwapchain = oldSwapchain
@@ -182,22 +182,6 @@ namespace Ryujinx.Graphics.Vulkan
return availableFormats[0]; return availableFormats[0];
} }
private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags)
{
if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr))
{
return CompositeAlphaFlagsKHR.OpaqueBitKhr;
}
else if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr))
{
return CompositeAlphaFlagsKHR.PreMultipliedBitKhr;
}
else
{
return CompositeAlphaFlagsKHR.InheritBitKhr;
}
}
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled) private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
{ {
if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
@@ -208,6 +192,10 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return PresentModeKHR.MailboxKhr; return PresentModeKHR.MailboxKhr;
} }
else if (availablePresentModes.Contains(PresentModeKHR.FifoKhr))
{
return PresentModeKHR.FifoKhr;
}
else else
{ {
return PresentModeKHR.FifoKhr; return PresentModeKHR.FifoKhr;

View File

@@ -1,5 +1,4 @@
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
@@ -71,4 +70,4 @@ namespace Ryujinx.HLE.HOS.Kernel
return null; return null;
} }
} }
} }

View File

@@ -10,6 +10,24 @@ namespace Ryujinx.HLE.HOS.Services.Pm
{ {
public IDebugMonitorInterface(ServiceCtx context) { } public IDebugMonitorInterface(ServiceCtx context) { }
[CommandHipc(4)]
// GetProgramId() -> sf::Out<ncm::ProgramId> out_process_id
public ResultCode GetApplicationProcessId(ServiceCtx context)
{
// TODO: Not correct as it shouldn't be directly using kernel objects here
foreach (KProcess process in context.Device.System.KernelContext.Processes.Values)
{
if (process.IsApplication)
{
context.ResponseData.Write(process.Pid);
return ResultCode.Success;
}
}
return ResultCode.ProcessNotFound;
}
[CommandHipc(65000)] [CommandHipc(65000)]
// AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status // AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status
public ResultCode GetProcessInfo(ServiceCtx context) public ResultCode GetProcessInfo(ServiceCtx context)

View File

@@ -1,8 +1,28 @@
namespace Ryujinx.HLE.HOS.Services.Pm using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Process;
namespace Ryujinx.HLE.HOS.Services.Pm
{ {
[Service("pm:info")] [Service("pm:info")]
class IInformationInterface : IpcService class IInformationInterface : IpcService
{ {
public IInformationInterface(ServiceCtx context) { } public IInformationInterface(ServiceCtx context) { }
[CommandHipc(0)]
// GetProgramId(os::ProcessId process_id) -> sf::Out<ncm::ProgramId> out
public ResultCode GetProgramId(ServiceCtx context)
{
ulong pid = context.RequestData.ReadUInt64();
// TODO: Not correct as it shouldn't be directly using kernel objects here
if (context.Device.System.KernelContext.Processes.TryGetValue(pid, out KProcess process))
{
context.ResponseData.Write(process.TitleId);
return ResultCode.Success;
}
return ResultCode.ProcessNotFound;
}
} }
} }

View File

@@ -0,0 +1,17 @@
namespace Ryujinx.HLE.HOS.Services.Pm
{
enum ResultCode
{
ModuleId = 15,
ErrorCodeShift = 9,
Success = 0,
ProcessNotFound = (1 << ErrorCodeShift) | ModuleId,
AlreadyStarted = (2 << ErrorCodeShift) | ModuleId,
NotTerminated = (3 << ErrorCodeShift) | ModuleId,
DebugHookInUse = (4 << ErrorCodeShift) | ModuleId,
ApplicationRunning = (5 << ErrorCodeShift) | ModuleId,
InvalidSize = (6 << ErrorCodeShift) | ModuleId,
}
}

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Headless.SDL2
{ {
class SDL2MouseDriver : IGamepadDriver class SDL2MouseDriver : IGamepadDriver
{ {
private const int CursorHideIdleTime = 8; // seconds private const int CursorHideIdleTime = 5; // seconds
private bool _isDisposed; private bool _isDisposed;
private HideCursor _hideCursor; private HideCursor _hideCursor;

View File

@@ -435,12 +435,7 @@ namespace Ryujinx.Memory
public static ulong GetPageSize() public static ulong GetPageSize()
{ {
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) return (ulong)Environment.SystemPageSize;
{
return 1UL << 14;
}
return 1UL << 12;
} }
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException(); private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();

View File

@@ -1,19 +1,75 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Ui.Common.Helper namespace Ryujinx.Ui.Common.Helper
{ {
public static class OpenHelper public static partial class OpenHelper
{ {
[LibraryImport("shell32.dll", SetLastError = true)]
public static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags);
[LibraryImport("shell32.dll", SetLastError = true)]
public static partial void ILFree(IntPtr pidlList);
[LibraryImport("shell32.dll", SetLastError = true)]
public static partial IntPtr ILCreateFromPathW([MarshalAs(UnmanagedType.LPWStr)] string pszPath);
public static void OpenFolder(string path) public static void OpenFolder(string path)
{ {
Process.Start(new ProcessStartInfo if (Directory.Exists(path))
{ {
FileName = path, Process.Start(new ProcessStartInfo
UseShellExecute = true, {
Verb = "open" FileName = path,
}); UseShellExecute = true,
Verb = "open"
});
}
else
{
Logger.Notice.Print(LogClass.Application, $"Directory \"{path}\" doesn't exist!");
}
}
public static void LocateFile(string path)
{
if (File.Exists(path))
{
if (OperatingSystem.IsWindows())
{
IntPtr pidlList = ILCreateFromPathW(path);
if (pidlList != IntPtr.Zero)
{
try
{
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
}
finally
{
ILFree(pidlList);
}
}
}
else if (OperatingSystem.IsMacOS())
{
Process.Start("open", $"-R \"{path}\"");
}
else if (OperatingSystem.IsLinux())
{
Process.Start("dbus-send", $"--session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"file://{path}\" string:\"\"");
}
else
{
OpenFolder(Path.GetDirectoryName(path));
}
}
else
{
Logger.Notice.Print(LogClass.Application, $"File \"{path}\" doesn't exist!");
}
} }
public static void OpenUrl(string url) public static void OpenUrl(string url)

View File

@@ -68,7 +68,7 @@ namespace Ryujinx.Ui
private readonly CancellationTokenSource _gpuCancellationTokenSource; private readonly CancellationTokenSource _gpuCancellationTokenSource;
// Hide Cursor // Hide Cursor
const int CursorHideIdleTime = 8; // seconds const int CursorHideIdleTime = 5; // seconds
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor); private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
private long _lastCursorMoveTime; private long _lastCursorMoveTime;
private bool _hideCursorOnIdle; private bool _hideCursorOnIdle;