Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7805d27e67 | ||
|
6c515e1822 | ||
|
8a363b5df2 | ||
|
2b5abac809 | ||
|
c19c8bbade | ||
|
1c7a90ef35 |
@@ -5,7 +5,6 @@ using Avalonia.Styling;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Styling;
|
using FluentAvalonia.Styling;
|
||||||
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.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@@ -67,7 +66,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
var path = Process.GetCurrentProcess().MainModule.FileName;
|
var path = Environment.ProcessPath;
|
||||||
var proc = Process.Start(path, CommandLineState.Arguments);
|
var proc = Process.Start(path, CommandLineState.Arguments);
|
||||||
desktop.Shutdown();
|
desktop.Shutdown();
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@@ -26,6 +27,7 @@ using Ryujinx.Graphics.GAL.Multithreading;
|
|||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.Graphics.OpenGL;
|
using Ryujinx.Graphics.OpenGL;
|
||||||
using Ryujinx.Graphics.Vulkan;
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
@@ -41,7 +43,6 @@ using SixLabors.ImageSharp;
|
|||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using SPB.Graphics.Exceptions;
|
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -50,10 +51,12 @@ using System.IO;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
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;
|
||||||
using MouseButton = Ryujinx.Input.MouseButton;
|
using MouseButton = Ryujinx.Input.MouseButton;
|
||||||
|
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||||
using Size = Avalonia.Size;
|
using Size = Avalonia.Size;
|
||||||
using Switch = Ryujinx.HLE.Switch;
|
using Switch = Ryujinx.HLE.Switch;
|
||||||
|
|
||||||
@@ -66,9 +69,9 @@ namespace Ryujinx.Ava
|
|||||||
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 _invisibleCursorWin;
|
||||||
private readonly IntPtr DefaultCursorWin;
|
private readonly IntPtr _defaultCursorWin;
|
||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
@@ -81,7 +84,7 @@ namespace Ryujinx.Ava
|
|||||||
private readonly MainWindowViewModel _viewModel;
|
private readonly MainWindowViewModel _viewModel;
|
||||||
private readonly IKeyboard _keyboardInterface;
|
private readonly IKeyboard _keyboardInterface;
|
||||||
private readonly TopLevel _topLevel;
|
private readonly TopLevel _topLevel;
|
||||||
public RendererHost _rendererHost;
|
public RendererHost RendererHost;
|
||||||
|
|
||||||
private readonly GraphicsDebugLevel _glLogLevel;
|
private readonly GraphicsDebugLevel _glLogLevel;
|
||||||
private float _newVolume;
|
private float _newVolume;
|
||||||
@@ -94,7 +97,7 @@ namespace Ryujinx.Ava
|
|||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
private bool _renderingStarted;
|
private bool _renderingStarted;
|
||||||
|
|
||||||
private ManualResetEvent _gpuDoneEvent;
|
private readonly ManualResetEvent _gpuDoneEvent;
|
||||||
|
|
||||||
private IRenderer _renderer;
|
private IRenderer _renderer;
|
||||||
private readonly Thread _renderingThread;
|
private readonly Thread _renderingThread;
|
||||||
@@ -150,14 +153,14 @@ namespace Ryujinx.Ava
|
|||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
_rendererHost = renderer;
|
RendererHost = renderer;
|
||||||
|
|
||||||
_chrono = new Stopwatch();
|
_chrono = new Stopwatch();
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
|
||||||
if (ApplicationPath.StartsWith("@SystemContent"))
|
if (ApplicationPath.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
ApplicationPath = _viewModel.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
|
ApplicationPath = VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
|
||||||
|
|
||||||
_isFirmwareTitle = true;
|
_isFirmwareTitle = true;
|
||||||
}
|
}
|
||||||
@@ -170,8 +173,8 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
InvisibleCursorWin = CreateEmptyCursor();
|
_invisibleCursorWin = CreateEmptyCursor();
|
||||||
DefaultCursorWin = CreateArrowCursor();
|
_defaultCursorWin = CreateArrowCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||||
@@ -196,10 +199,10 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
if (_rendererHost.EmbeddedWindow.TransformedBounds != null)
|
if (RendererHost.EmbeddedWindow.TransformedBounds != null)
|
||||||
{
|
{
|
||||||
var point = e.GetCurrentPoint(window).Position;
|
var point = e.GetCurrentPoint(window).Position;
|
||||||
var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
|
var bounds = RendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
|
||||||
|
|
||||||
_isCursorInRenderer = point.X >= bounds.X &&
|
_isCursorInRenderer = point.X >= bounds.X &&
|
||||||
point.X <= bounds.Width + bounds.X &&
|
point.X <= bounds.Width + bounds.X &&
|
||||||
@@ -220,7 +223,7 @@ namespace Ryujinx.Ava
|
|||||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
|
private void UpdateScalingFilter(object sender, ReactiveEventArgs<ScalingFilter> e)
|
||||||
{
|
{
|
||||||
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||||
@@ -234,7 +237,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
SetCursor(DefaultCursorWin);
|
SetCursor(_defaultCursorWin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -243,11 +246,11 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_viewModel.Cursor = InvisibleCursor;
|
_viewModel.Cursor = _invisibleCursor;
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
SetCursor(InvisibleCursorWin);
|
SetCursor(_invisibleCursorWin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -276,7 +279,7 @@ namespace Ryujinx.Ava
|
|||||||
string directory = AppDataManager.Mode switch
|
string directory = AppDataManager.Mode switch
|
||||||
{
|
{
|
||||||
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||||
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
|
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"),
|
||||||
};
|
};
|
||||||
|
|
||||||
string path = Path.Combine(directory, filename);
|
string path = Path.Combine(directory, filename);
|
||||||
@@ -305,9 +308,9 @@ namespace Ryujinx.Ava
|
|||||||
image.Mutate(x => x.Flip(FlipMode.Vertical));
|
image.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||||
}
|
}
|
||||||
|
|
||||||
image.SaveAsPng(path, new PngEncoder()
|
image.SaveAsPng(path, new PngEncoder
|
||||||
{
|
{
|
||||||
ColorType = PngColorType.Rgb
|
ColorType = PngColorType.Rgb,
|
||||||
});
|
});
|
||||||
|
|
||||||
image.Dispose();
|
image.Dispose();
|
||||||
@@ -348,9 +351,9 @@ namespace Ryujinx.Ava
|
|||||||
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||||
});
|
});
|
||||||
|
|
||||||
_viewModel.SetUIProgressHandlers(Device);
|
_viewModel.SetUiProgressHandlers(Device);
|
||||||
|
|
||||||
_rendererHost.SizeChanged += Window_SizeChanged;
|
RendererHost.SizeChanged += Window_SizeChanged;
|
||||||
|
|
||||||
_isActive = true;
|
_isActive = true;
|
||||||
|
|
||||||
@@ -379,7 +382,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
|
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e)
|
||||||
{
|
{
|
||||||
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
|
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
|
||||||
}
|
}
|
||||||
@@ -448,7 +451,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
if (Device.Processes != null)
|
if (Device.Processes != null)
|
||||||
{
|
{
|
||||||
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
@@ -477,7 +480,7 @@ namespace Ryujinx.Ava
|
|||||||
_windowsMultimediaTimerResolution = null;
|
_windowsMultimediaTimerResolution = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_rendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow)
|
if (RendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow)
|
||||||
{
|
{
|
||||||
// Try to bind the OpenGL context before calling the shutdown event.
|
// Try to bind the OpenGL context before calling the shutdown event.
|
||||||
openGlWindow.MakeCurrent(false, false);
|
openGlWindow.MakeCurrent(false, false);
|
||||||
@@ -508,7 +511,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
|
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
|
||||||
{
|
{
|
||||||
@@ -526,7 +529,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (result != UserResult.Yes)
|
if (result != UserResult.Yes)
|
||||||
{
|
{
|
||||||
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -535,7 +538,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
|
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
|
||||||
{
|
{
|
||||||
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -558,7 +561,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -727,7 +730,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
renderer = new VulkanRenderer(
|
renderer = new VulkanRenderer(
|
||||||
Vk.GetApi(),
|
Vk.GetApi(),
|
||||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
|
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
|
||||||
VulkanHelper.GetRequiredInstanceExtensions,
|
VulkanHelper.GetRequiredInstanceExtensions,
|
||||||
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
|
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
|
||||||
}
|
}
|
||||||
@@ -738,18 +741,18 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||||
|
|
||||||
var isGALthreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
|
var isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
|
||||||
if (isGALthreaded)
|
if (isGALThreaded)
|
||||||
{
|
{
|
||||||
renderer = new ThreadedRenderer(renderer);
|
renderer = new ThreadedRenderer(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALthreaded}");
|
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}");
|
||||||
|
|
||||||
// Initialize Configuration.
|
// Initialize Configuration.
|
||||||
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GiB : HLE.MemoryConfiguration.MemoryConfiguration4GiB;
|
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB;
|
||||||
|
|
||||||
HLE.HLEConfiguration configuration = new(VirtualFileSystem,
|
HLEConfiguration configuration = new(VirtualFileSystem,
|
||||||
_viewModel.LibHacHorizonManager,
|
_viewModel.LibHacHorizonManager,
|
||||||
ContentManager,
|
ContentManager,
|
||||||
_accountManager,
|
_accountManager,
|
||||||
@@ -780,12 +783,12 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static IHardwareDeviceDriver InitializeAudio()
|
private static IHardwareDeviceDriver InitializeAudio()
|
||||||
{
|
{
|
||||||
var availableBackends = new List<AudioBackend>()
|
var availableBackends = new List<AudioBackend>
|
||||||
{
|
{
|
||||||
AudioBackend.SDL2,
|
AudioBackend.SDL2,
|
||||||
AudioBackend.SoundIo,
|
AudioBackend.SoundIo,
|
||||||
AudioBackend.OpenAl,
|
AudioBackend.OpenAl,
|
||||||
AudioBackend.Dummy
|
AudioBackend.Dummy,
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value;
|
AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value;
|
||||||
@@ -806,13 +809,11 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
return new T();
|
return new T();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}.");
|
Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}.");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
IHardwareDeviceDriver deviceDriver = null;
|
IHardwareDeviceDriver deviceDriver = null;
|
||||||
|
|
||||||
@@ -826,7 +827,7 @@ namespace Ryujinx.Ava
|
|||||||
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
|
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
|
||||||
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
|
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
|
||||||
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
|
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
|
||||||
_ => new DummyHardwareDeviceDriver()
|
_ => new DummyHardwareDeviceDriver(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deviceDriver != null)
|
if (deviceDriver != null)
|
||||||
@@ -879,7 +880,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer);
|
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer);
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
@@ -887,8 +888,8 @@ namespace Ryujinx.Ava
|
|||||||
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||||
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||||
|
|
||||||
Width = (int)_rendererHost.Bounds.Width;
|
Width = (int)RendererHost.Bounds.Width;
|
||||||
Height = (int)_rendererHost.Bounds.Height;
|
Height = (int)RendererHost.Bounds.Height;
|
||||||
|
|
||||||
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
||||||
|
|
||||||
@@ -923,7 +924,7 @@ namespace Ryujinx.Ava
|
|||||||
_viewModel.SwitchToRenderer(false);
|
_viewModel.SwitchToRenderer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
if (_ticks >= _ticksPerFrame)
|
||||||
@@ -941,7 +942,7 @@ namespace Ryujinx.Ava
|
|||||||
_gpuDoneEvent.Set();
|
_gpuDoneEvent.Set();
|
||||||
});
|
});
|
||||||
|
|
||||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
|
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatus()
|
public void UpdateStatus()
|
||||||
|
@@ -18,7 +18,6 @@ using Ryujinx.Ava.UI.Helpers;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
@@ -27,6 +26,7 @@ using System.Buffers;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common
|
namespace Ryujinx.Ava.Common
|
||||||
@@ -57,7 +57,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]");
|
Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]");
|
||||||
|
|
||||||
if (Utilities.IsZeros(controlHolder.ByteSpan))
|
if (controlHolder.ByteSpan.IsZeros())
|
||||||
{
|
{
|
||||||
// If the current application doesn't have a loaded control property, create a dummy one
|
// If the current application doesn't have a loaded control property, create a dummy one
|
||||||
// and set the savedata sizes so a user savedata will be created.
|
// and set the savedata sizes so a user savedata will be created.
|
||||||
@@ -72,7 +72,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||||
|
|
||||||
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
|
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new ApplicationId(titleId), in control, in user);
|
||||||
if (result.IsFailure())
|
if (result.IsFailure())
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
@@ -147,7 +147,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
{
|
{
|
||||||
OpenFolderDialog folderDialog = new()
|
OpenFolderDialog folderDialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
|
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
|
||||||
};
|
};
|
||||||
|
|
||||||
string destination = await folderDialog.ShowAsync(_owner);
|
string destination = await folderDialog.ShowAsync(_owner);
|
||||||
@@ -293,10 +293,11 @@ namespace Ryujinx.Ava.Common
|
|||||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
{
|
||||||
extractorThread.Name = "GUI.NcaSectionExtractorThread";
|
Name = "GUI.NcaSectionExtractorThread",
|
||||||
extractorThread.IsBackground = true;
|
IsBackground = true,
|
||||||
|
};
|
||||||
extractorThread.Start();
|
extractorThread.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,6 @@ namespace Ryujinx.Ava.Common
|
|||||||
FileType,
|
FileType,
|
||||||
FileSize,
|
FileSize,
|
||||||
Path,
|
Path,
|
||||||
Favorite
|
Favorite,
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,6 +11,6 @@
|
|||||||
ResScaleUp,
|
ResScaleUp,
|
||||||
ResScaleDown,
|
ResScaleDown,
|
||||||
VolumeUp,
|
VolumeUp,
|
||||||
VolumeDown
|
VolumeDown,
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -21,7 +21,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
ReflectionBindingExtension binding = new($"[{keyToUse}]")
|
ReflectionBindingExtension binding = new($"[{keyToUse}]")
|
||||||
{
|
{
|
||||||
Mode = BindingMode.OneWay,
|
Mode = BindingMode.OneWay,
|
||||||
Source = LocaleManager.Instance
|
Source = LocaleManager.Instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
return binding.ProvideValue(serviceProvider);
|
return binding.ProvideValue(serviceProvider);
|
||||||
|
@@ -13,11 +13,11 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
private const string DefaultLanguageCode = "en_US";
|
private const string DefaultLanguageCode = "en_US";
|
||||||
|
|
||||||
private Dictionary<LocaleKeys, string> _localeStrings;
|
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
||||||
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
||||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||||
|
|
||||||
public static LocaleManager Instance { get; } = new LocaleManager();
|
public static LocaleManager Instance { get; } = new();
|
||||||
|
|
||||||
public LocaleManager()
|
public LocaleManager()
|
||||||
{
|
{
|
||||||
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode = DefaultLanguageCode)
|
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode = DefaultLanguageCode)
|
||||||
{
|
{
|
||||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||||
|
@@ -120,6 +120,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
_buttonsUserMapping.Clear();
|
_buttonsUserMapping.Clear();
|
||||||
|
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
// Left JoyCon
|
// Left JoyCon
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||||
@@ -143,6 +144,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||||
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -143,7 +143,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
AvaKey.OemBackslash,
|
AvaKey.OemBackslash,
|
||||||
|
|
||||||
// NOTE: invalid
|
// NOTE: invalid
|
||||||
AvaKey.None
|
AvaKey.None,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
||||||
|
@@ -31,13 +31,13 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
internal static class Updater
|
internal static class Updater
|
||||||
{
|
{
|
||||||
private const string GitHubApiURL = "https://api.github.com";
|
private const string GitHubApiUrl = "https://api.github.com";
|
||||||
private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
|
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||||
private static readonly int ConnectionCount = 4;
|
private static readonly int _connectionCount = 4;
|
||||||
|
|
||||||
private static string _buildVer;
|
private static string _buildVer;
|
||||||
private static string _platformExt;
|
private static string _platformExt;
|
||||||
@@ -46,7 +46,7 @@ namespace Ryujinx.Modules
|
|||||||
private static bool _updateSuccessful;
|
private static bool _updateSuccessful;
|
||||||
private static bool _running;
|
private static bool _running;
|
||||||
|
|
||||||
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>();
|
||||||
|
|
||||||
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
|
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
|
||||||
{
|
{
|
||||||
@@ -99,9 +99,9 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
using HttpClient jsonClient = ConstructHttpClient();
|
using HttpClient jsonClient = ConstructHttpClient();
|
||||||
|
|
||||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
|
||||||
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
|
||||||
_buildVer = fetched.Name;
|
_buildVer = fetched.Name;
|
||||||
|
|
||||||
foreach (var asset in fetched.Assets)
|
foreach (var asset in fetched.Assets)
|
||||||
@@ -195,8 +195,7 @@ namespace Ryujinx.Modules
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch build size information to learn chunk sizes.
|
// Fetch build size information to learn chunk sizes.
|
||||||
using (HttpClient buildSizeClient = ConstructHttpClient())
|
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||||
@@ -212,7 +211,6 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
_buildSize = -1;
|
_buildSize = -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
@@ -248,23 +246,22 @@ namespace Ryujinx.Modules
|
|||||||
_updateSuccessful = false;
|
_updateSuccessful = false;
|
||||||
|
|
||||||
// Empty update dir, although it shouldn't ever have anything inside it
|
// Empty update dir, although it shouldn't ever have anything inside it
|
||||||
if (Directory.Exists(UpdateDir))
|
if (Directory.Exists(_updateDir))
|
||||||
{
|
{
|
||||||
Directory.Delete(UpdateDir, true);
|
Directory.Delete(_updateDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(UpdateDir);
|
Directory.CreateDirectory(_updateDir);
|
||||||
|
|
||||||
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||||
|
|
||||||
TaskDialog taskDialog = new()
|
TaskDialog taskDialog = new()
|
||||||
{
|
{
|
||||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||||
Buttons = { },
|
|
||||||
ShowProgressBar = true,
|
ShowProgressBar = true,
|
||||||
XamlRoot = parent
|
XamlRoot = parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
taskDialog.Opened += (s, e) =>
|
taskDialog.Opened += (s, e) =>
|
||||||
@@ -301,7 +298,7 @@ namespace Ryujinx.Modules
|
|||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
|
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
|
||||||
string newBundlePath = Path.Combine(UpdateDir, "Ryujinx.app");
|
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
|
||||||
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
|
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
|
||||||
string currentPid = Environment.ProcessId.ToString();
|
string currentPid = Environment.ProcessId.ToString();
|
||||||
|
|
||||||
@@ -328,7 +325,7 @@ namespace Ryujinx.Modules
|
|||||||
ProcessStartInfo processStart = new(ryuName)
|
ProcessStartInfo processStart = new(ryuName)
|
||||||
{
|
{
|
||||||
UseShellExecute = true,
|
UseShellExecute = true,
|
||||||
WorkingDirectory = executableDirectory
|
WorkingDirectory = executableDirectory,
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (string argument in CommandLineState.Arguments)
|
foreach (string argument in CommandLineState.Arguments)
|
||||||
@@ -347,22 +344,22 @@ namespace Ryujinx.Modules
|
|||||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
// Multi-Threaded Updater
|
// Multi-Threaded Updater
|
||||||
long chunkSize = _buildSize / ConnectionCount;
|
long chunkSize = _buildSize / _connectionCount;
|
||||||
long remainderChunk = _buildSize % ConnectionCount;
|
long remainderChunk = _buildSize % _connectionCount;
|
||||||
|
|
||||||
int completedRequests = 0;
|
int completedRequests = 0;
|
||||||
int totalProgressPercentage = 0;
|
int totalProgressPercentage = 0;
|
||||||
int[] progressPercentage = new int[ConnectionCount];
|
int[] progressPercentage = new int[_connectionCount];
|
||||||
|
|
||||||
List<byte[]> list = new(ConnectionCount);
|
List<byte[]> list = new(_connectionCount);
|
||||||
List<WebClient> webClients = new(ConnectionCount);
|
List<WebClient> webClients = new(_connectionCount);
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < _connectionCount; i++)
|
||||||
{
|
{
|
||||||
list.Add(Array.Empty<byte>());
|
list.Add(Array.Empty<byte>());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < _connectionCount; i++)
|
||||||
{
|
{
|
||||||
#pragma warning disable SYSLIB0014
|
#pragma warning disable SYSLIB0014
|
||||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||||
@@ -371,7 +368,7 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
webClients.Add(client);
|
webClients.Add(client);
|
||||||
|
|
||||||
if (i == ConnectionCount - 1)
|
if (i == _connectionCount - 1)
|
||||||
{
|
{
|
||||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||||
}
|
}
|
||||||
@@ -388,7 +385,7 @@ namespace Ryujinx.Modules
|
|||||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
|
||||||
};
|
};
|
||||||
|
|
||||||
client.DownloadDataCompleted += (_, args) =>
|
client.DownloadDataCompleted += (_, args) =>
|
||||||
@@ -407,10 +404,10 @@ namespace Ryujinx.Modules
|
|||||||
list[index] = args.Result;
|
list[index] = args.Result;
|
||||||
Interlocked.Increment(ref completedRequests);
|
Interlocked.Increment(ref completedRequests);
|
||||||
|
|
||||||
if (Equals(completedRequests, ConnectionCount))
|
if (Equals(completedRequests, _connectionCount))
|
||||||
{
|
{
|
||||||
byte[] mergedFileBytes = new byte[_buildSize];
|
byte[] mergedFileBytes = new byte[_buildSize];
|
||||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
|
||||||
{
|
{
|
||||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||||
destinationOffset += list[connectionIndex].Length;
|
destinationOffset += list[connectionIndex].Length;
|
||||||
@@ -421,11 +418,10 @@ namespace Ryujinx.Modules
|
|||||||
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
|
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
using (Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }))
|
using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile });
|
||||||
{
|
|
||||||
xattrProcess.WaitForExit();
|
xattrProcess.WaitForExit();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -437,8 +433,6 @@ namespace Ryujinx.Modules
|
|||||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||||
|
|
||||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -470,9 +464,8 @@ namespace Ryujinx.Modules
|
|||||||
// We do not want to timeout while downloading
|
// We do not want to timeout while downloading
|
||||||
client.Timeout = TimeSpan.FromDays(1);
|
client.Timeout = TimeSpan.FromDays(1);
|
||||||
|
|
||||||
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result;
|
||||||
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result;
|
||||||
{
|
|
||||||
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
|
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
|
||||||
|
|
||||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||||
@@ -495,7 +488,6 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
updateFileStream.Write(buffer, 0, readSize);
|
updateFileStream.Write(buffer, 0, readSize);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
InstallUpdate(taskDialog, updateFile);
|
InstallUpdate(taskDialog, updateFile);
|
||||||
}
|
}
|
||||||
@@ -510,7 +502,7 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
||||||
{
|
{
|
||||||
Name = "Updater.SingleThreadWorker"
|
Name = "Updater.SingleThreadWorker",
|
||||||
};
|
};
|
||||||
|
|
||||||
worker.Start();
|
worker.Start();
|
||||||
@@ -537,10 +529,8 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||||
|
|
||||||
using (FileStream outStream = File.OpenWrite(outPath))
|
using FileStream outStream = File.OpenWrite(outPath);
|
||||||
{
|
|
||||||
tarStream.CopyEntryContents(outStream);
|
tarStream.CopyEntryContents(outStream);
|
||||||
}
|
|
||||||
|
|
||||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||||
@@ -566,17 +556,19 @@ namespace Ryujinx.Modules
|
|||||||
foreach (ZipEntry zipEntry in zipFile)
|
foreach (ZipEntry zipEntry in zipFile)
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
if (zipEntry.IsDirectory) continue;
|
if (zipEntry.IsDirectory)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||||
|
|
||||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||||
using (FileStream outStream = File.OpenWrite(outPath))
|
using FileStream outStream = File.OpenWrite(outPath);
|
||||||
{
|
|
||||||
zipStream.CopyTo(outStream);
|
zipStream.CopyTo(outStream);
|
||||||
}
|
|
||||||
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||||
|
|
||||||
@@ -597,11 +589,11 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
|
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsWindows())
|
else if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
ExtractZipFile(taskDialog, updateFile, UpdateDir);
|
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -648,10 +640,10 @@ namespace Ryujinx.Modules
|
|||||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||||
});
|
});
|
||||||
|
|
||||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
|
||||||
});
|
});
|
||||||
|
|
||||||
Directory.Delete(UpdateDir, true);
|
Directory.Delete(_updateDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateSuccessful = true;
|
_updateSuccessful = true;
|
||||||
@@ -738,15 +730,15 @@ namespace Ryujinx.Modules
|
|||||||
// NOTE: This method should always reflect the latest build layout.
|
// NOTE: This method should always reflect the latest build layout.
|
||||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||||
{
|
{
|
||||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir.
|
||||||
|
|
||||||
// Determine and exclude user files only when the updater is running, not when cleaning old files
|
// Determine and exclude user files only when the updater is running, not when cleaning old files
|
||||||
if (_running && !OperatingSystem.IsMacOS())
|
if (_running && !OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
|
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
|
||||||
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
|
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename));
|
||||||
|
|
||||||
// Remove user files from the paths in files.
|
// Remove user files from the paths in files.
|
||||||
files = files.Except(userFiles);
|
files = files.Except(userFiles);
|
||||||
@@ -754,9 +746,9 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
foreach (string dir in WindowsDependencyDirs)
|
foreach (string dir in _windowsDependencyDirs)
|
||||||
{
|
{
|
||||||
string dirPath = Path.Combine(HomeDir, dir);
|
string dirPath = Path.Combine(_homeDir, dir);
|
||||||
if (Directory.Exists(dirPath))
|
if (Directory.Exists(dirPath))
|
||||||
{
|
{
|
||||||
files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
|
files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
|
||||||
@@ -798,7 +790,7 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
public static void CleanupUpdate()
|
public static void CleanupUpdate()
|
||||||
{
|
{
|
||||||
foreach (string file in Directory.GetFiles(HomeDir, "*.ryuold", SearchOption.AllDirectories))
|
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ namespace Ryujinx.Ava
|
|||||||
[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);
|
||||||
|
|
||||||
private const uint MB_ICONWARNING = 0x30;
|
private const uint MbIconwarning = 0x30;
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
||||||
{
|
{
|
||||||
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
|
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreviewerDetached = true;
|
PreviewerDetached = true;
|
||||||
@@ -60,7 +60,7 @@ namespace Ryujinx.Ava
|
|||||||
EnableMultiTouch = true,
|
EnableMultiTouch = true,
|
||||||
EnableIme = true,
|
EnableIme = true,
|
||||||
UseEGL = false,
|
UseEGL = false,
|
||||||
UseGpu = true
|
UseGpu = true,
|
||||||
})
|
})
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
@@ -192,7 +192,7 @@ namespace Ryujinx.Ava
|
|||||||
"never" => HideCursorMode.Never,
|
"never" => HideCursorMode.Never,
|
||||||
"onidle" => HideCursorMode.OnIdle,
|
"onidle" => HideCursorMode.OnIdle,
|
||||||
"always" => HideCursorMode.Always,
|
"always" => HideCursorMode.Always,
|
||||||
_ => ConfigurationState.Instance.HideCursor.Value
|
_ => ConfigurationState.Instance.HideCursor.Value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||||
(int)Symbol.Important,
|
(int)Symbol.Important,
|
||||||
deferEvent,
|
deferEvent,
|
||||||
async (window) =>
|
async window =>
|
||||||
{
|
{
|
||||||
if (opened)
|
if (opened)
|
||||||
{
|
{
|
||||||
@@ -112,7 +112,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await SwkbdAppletDialog.ShowInputDialog(_parent, LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
|
var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
|
||||||
|
|
||||||
if (response.Result == UserResult.Ok)
|
if (response.Result == UserResult.Ok)
|
||||||
{
|
{
|
||||||
@@ -142,10 +142,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
|
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
|
||||||
{
|
{
|
||||||
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
|
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
|
||||||
if (_parent.ViewModel.AppHost != null)
|
_parent.ViewModel.AppHost?.Stop();
|
||||||
{
|
|
||||||
_parent.ViewModel.AppHost.Stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
|
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
|
||||||
@@ -162,7 +159,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||||
Width = 400
|
Width = 400,
|
||||||
};
|
};
|
||||||
|
|
||||||
object response = await msgDialog.Run();
|
object response = await msgDialog.Run();
|
||||||
|
@@ -3,13 +3,11 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.Ui;
|
using Ryujinx.HLE.Ui;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using HidKey = Ryujinx.Common.Configuration.Hid.Key;
|
using HidKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
@@ -17,7 +15,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler
|
class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler
|
||||||
{
|
{
|
||||||
private MainWindow _parent;
|
private MainWindow _parent;
|
||||||
private OffscreenTextBox _hiddenTextBox;
|
private readonly OffscreenTextBox _hiddenTextBox;
|
||||||
private bool _canProcessInput;
|
private bool _canProcessInput;
|
||||||
private IDisposable _textChangedSubscription;
|
private IDisposable _textChangedSubscription;
|
||||||
private IDisposable _selectionStartChangedSubscription;
|
private IDisposable _selectionStartChangedSubscription;
|
||||||
@@ -76,7 +74,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent();
|
e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent();
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
@@ -96,7 +94,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent();
|
e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent();
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
|
@@ -9,7 +9,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
public AvaloniaHostUiTheme(MainWindow parent)
|
public AvaloniaHostUiTheme(MainWindow parent)
|
||||||
{
|
{
|
||||||
FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000, 0) ? "Segoe UI Variable" : parent.FontFamily.Name;
|
FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
|
||||||
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
|
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
|
||||||
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
|
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
|
||||||
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
|
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
|
||||||
@@ -25,7 +25,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
public ThemeColor SelectionBackgroundColor { get; }
|
public ThemeColor SelectionBackgroundColor { get; }
|
||||||
public ThemeColor SelectionForegroundColor { get; }
|
public ThemeColor SelectionForegroundColor { get; }
|
||||||
|
|
||||||
private ThemeColor BrushToThemeColor(IBrush brush)
|
private static ThemeColor BrushToThemeColor(IBrush brush)
|
||||||
{
|
{
|
||||||
if (brush is SolidColorBrush solidColor)
|
if (brush is SolidColorBrush solidColor)
|
||||||
{
|
{
|
||||||
@@ -34,10 +34,8 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
(float)solidColor.Color.G / 255,
|
(float)solidColor.Color.G / 255,
|
||||||
(float)solidColor.Color.B / 255);
|
(float)solidColor.Color.B / 255);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ThemeColor();
|
return new ThemeColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
#if DEBUG
|
||||||
|
using Avalonia;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
{
|
{
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using FluentAvalonia.Core;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
|
||||||
using Ryujinx.HLE.HOS.Applets;
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||||
using System;
|
using System;
|
||||||
@@ -22,7 +19,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
private Predicate<string> _checkInput = _ => true;
|
private Predicate<string> _checkInput = _ => true;
|
||||||
private int _inputMax;
|
private int _inputMax;
|
||||||
private int _inputMin;
|
private int _inputMin;
|
||||||
private string _placeholder;
|
private readonly string _placeholder;
|
||||||
|
|
||||||
private ContentDialog _host;
|
private ContentDialog _host;
|
||||||
|
|
||||||
@@ -57,13 +54,13 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
public string MainText { get; set; } = "";
|
public string MainText { get; set; } = "";
|
||||||
public string SecondaryText { get; set; } = "";
|
public string SecondaryText { get; set; } = "";
|
||||||
|
|
||||||
public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args)
|
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUiArgs args)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new ContentDialog();
|
ContentDialog contentDialog = new();
|
||||||
|
|
||||||
UserResult result = UserResult.Cancel;
|
UserResult result = UserResult.Cancel;
|
||||||
|
|
||||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
|
SwkbdAppletDialog content = new(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
|
||||||
|
|
||||||
string input = string.Empty;
|
string input = string.Empty;
|
||||||
|
|
||||||
@@ -78,15 +75,16 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel];
|
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel];
|
||||||
contentDialog.Content = content;
|
contentDialog.Content = content;
|
||||||
|
|
||||||
TypedEventHandler<ContentDialog, ContentDialogClosedEventArgs> handler = (sender, eventArgs) =>
|
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Result == ContentDialogResult.Primary)
|
if (eventArgs.Result == ContentDialogResult.Primary)
|
||||||
{
|
{
|
||||||
result = UserResult.Ok;
|
result = UserResult.Ok;
|
||||||
input = content.Input.Text;
|
input = content.Input.Text;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
contentDialog.Closed += handler;
|
|
||||||
|
contentDialog.Closed += Handler;
|
||||||
|
|
||||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
|
||||||
|
@@ -18,7 +18,6 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using UserId = LibHac.Fs.UserId;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
@@ -53,7 +52,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
|
public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if ((sender as MenuItem)?.DataContext is MainWindowViewModel viewModel)
|
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel })
|
||||||
{
|
{
|
||||||
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
|
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Core;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
@@ -10,7 +9,6 @@ using LibHac.Fs;
|
|||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Views.User;
|
using Ryujinx.Ava.UI.Views.User;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
@@ -19,6 +17,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
@@ -56,7 +55,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GoBack(object parameter = null)
|
public void GoBack()
|
||||||
{
|
{
|
||||||
if (ContentFrame.BackStack.Count > 0)
|
if (ContentFrame.BackStack.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -75,14 +74,14 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||||
{
|
{
|
||||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||||
ContentDialog contentDialog = new ContentDialog
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = content,
|
Content = content,
|
||||||
Padding = new Thickness(0)
|
Padding = new Thickness(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
contentDialog.Closed += (sender, args) =>
|
contentDialog.Closed += (sender, args) =>
|
||||||
@@ -125,7 +124,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new();
|
HashSet<UserId> lostAccounts = new();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -139,15 +138,15 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
for (int i = 0; i < readCount; i++)
|
for (int i = 0; i < readCount; i++)
|
||||||
{
|
{
|
||||||
var save = saveDataInfo[i];
|
var save = saveDataInfo[i];
|
||||||
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||||
if (ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault( x=> x.UserId == id) == null)
|
if (ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId == id) == null)
|
||||||
{
|
{
|
||||||
lostAccounts.Add(id);
|
lostAccounts.Add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(var account in lostAccounts)
|
foreach (var account in lostAccounts)
|
||||||
{
|
{
|
||||||
ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this));
|
ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this));
|
||||||
}
|
}
|
||||||
@@ -166,7 +165,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
{
|
{
|
||||||
async void Action()
|
static async void Action()
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Core;
|
using FluentAvalonia.Core;
|
||||||
@@ -36,13 +37,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
PrimaryButtonText = primaryButton,
|
PrimaryButtonText = primaryButton,
|
||||||
SecondaryButtonText = secondaryButton,
|
SecondaryButtonText = secondaryButton,
|
||||||
CloseButtonText = closeButton,
|
CloseButtonText = closeButton,
|
||||||
Content = content
|
Content = content,
|
||||||
};
|
PrimaryButtonCommand = MiniCommand.Create(() =>
|
||||||
|
|
||||||
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
|
|
||||||
{
|
{
|
||||||
result = primaryButtonResult;
|
result = primaryButtonResult;
|
||||||
});
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
Func<Window, Task> doWhileDeferred = null)
|
Func<Window, Task> doWhileDeferred = null)
|
||||||
{
|
{
|
||||||
bool startedDeferring = false;
|
bool startedDeferring = false;
|
||||||
UserResult result = UserResult.None;
|
|
||||||
|
|
||||||
return await ShowTextDialog(
|
return await ShowTextDialog(
|
||||||
title,
|
title,
|
||||||
@@ -123,8 +122,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
var deferral = args.GetDeferral();
|
var deferral = args.GetDeferral();
|
||||||
|
|
||||||
result = primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok;
|
|
||||||
|
|
||||||
sender.PrimaryButtonClick -= DeferClose;
|
sender.PrimaryButtonClick -= DeferClose;
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
@@ -150,10 +147,10 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
Grid content = new()
|
Grid content = new()
|
||||||
{
|
{
|
||||||
RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() },
|
RowDefinitions = new RowDefinitions { new(), new() },
|
||||||
ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() },
|
ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() },
|
||||||
|
|
||||||
MinHeight = 80
|
MinHeight = 80,
|
||||||
};
|
};
|
||||||
|
|
||||||
SymbolIcon icon = new()
|
SymbolIcon icon = new()
|
||||||
@@ -161,7 +158,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
Symbol = (Symbol)symbol,
|
Symbol = (Symbol)symbol,
|
||||||
Margin = new Thickness(10),
|
Margin = new Thickness(10),
|
||||||
FontSize = 40,
|
FontSize = 40,
|
||||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
};
|
};
|
||||||
|
|
||||||
Grid.SetColumn(icon, 0);
|
Grid.SetColumn(icon, 0);
|
||||||
@@ -173,7 +170,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
Text = primaryText,
|
Text = primaryText,
|
||||||
Margin = new Thickness(5),
|
Margin = new Thickness(5),
|
||||||
TextWrapping = TextWrapping.Wrap,
|
TextWrapping = TextWrapping.Wrap,
|
||||||
MaxWidth = 450
|
MaxWidth = 450,
|
||||||
};
|
};
|
||||||
|
|
||||||
TextBlock secondaryLabel = new()
|
TextBlock secondaryLabel = new()
|
||||||
@@ -181,7 +178,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
Text = secondaryText,
|
Text = secondaryText,
|
||||||
Margin = new Thickness(5),
|
Margin = new Thickness(5),
|
||||||
TextWrapping = TextWrapping.Wrap,
|
TextWrapping = TextWrapping.Wrap,
|
||||||
MaxWidth = 450
|
MaxWidth = 450,
|
||||||
};
|
};
|
||||||
|
|
||||||
Grid.SetColumn(primaryLabel, 1);
|
Grid.SetColumn(primaryLabel, 1);
|
||||||
@@ -318,14 +315,14 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
Window parent = GetMainWindow();
|
Window parent = GetMainWindow();
|
||||||
|
|
||||||
if (parent != null && parent.IsActive && (parent as MainWindow).ViewModel.IsGameRunning)
|
if (parent is { IsActive: true } and MainWindow window && window.ViewModel.IsGameRunning)
|
||||||
{
|
{
|
||||||
contentDialogOverlayWindow = new()
|
contentDialogOverlayWindow = new()
|
||||||
{
|
{
|
||||||
Height = parent.Bounds.Height,
|
Height = parent.Bounds.Height,
|
||||||
Width = parent.Bounds.Width,
|
Width = parent.Bounds.Width,
|
||||||
Position = parent.PointToScreen(new Point()),
|
Position = parent.PointToScreen(new Point()),
|
||||||
ShowInTaskbar = false
|
ShowInTaskbar = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
parent.PositionChanged += OverlayOnPositionChanged;
|
parent.PositionChanged += OverlayOnPositionChanged;
|
||||||
@@ -389,7 +386,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
private static Window GetMainWindow()
|
private static Window GetMainWindow()
|
||||||
{
|
{
|
||||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
|
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
|
||||||
{
|
{
|
||||||
foreach (Window item in al.Windows)
|
foreach (Window item in al.Windows)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -8,13 +9,13 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
public class GlyphValueConverter : MarkupExtension
|
public class GlyphValueConverter : MarkupExtension
|
||||||
{
|
{
|
||||||
private string _key;
|
private readonly string _key;
|
||||||
|
|
||||||
private static Dictionary<Glyph, string> _glyphs = new Dictionary<Glyph, string>
|
private static readonly Dictionary<Glyph, string> _glyphs = new()
|
||||||
{
|
{
|
||||||
{ Glyph.List, char.ConvertFromUtf32((int)Symbol.List).ToString() },
|
{ Glyph.List, char.ConvertFromUtf32((int)Symbol.List) },
|
||||||
{ Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll).ToString() },
|
{ Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll) },
|
||||||
{ Glyph.Chip, char.ConvertFromUtf32(59748).ToString() }
|
{ Glyph.Chip, char.ConvertFromUtf32(59748) },
|
||||||
};
|
};
|
||||||
|
|
||||||
public GlyphValueConverter(string key)
|
public GlyphValueConverter(string key)
|
||||||
@@ -37,10 +38,10 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension binding = new($"[{_key}]")
|
ReflectionBindingExtension binding = new($"[{_key}]")
|
||||||
{
|
{
|
||||||
Mode = BindingMode.OneWay,
|
Mode = BindingMode.OneWay,
|
||||||
Source = this
|
Source = this,
|
||||||
};
|
};
|
||||||
|
|
||||||
return binding.ProvideValue(serviceProvider);
|
return binding.ProvideValue(serviceProvider);
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
|
using Avalonia.Logging;
|
||||||
using Avalonia.Utilities;
|
using Avalonia.Utilities;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
using AvaLogger = Avalonia.Logging.Logger;
|
using AvaLogger = Avalonia.Logging.Logger;
|
||||||
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
using AvaLogLevel = LogEventLevel;
|
||||||
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
|
using RyuLogClass = LogClass;
|
||||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||||
|
|
||||||
internal class LoggerAdapter : Avalonia.Logging.ILogSink
|
internal class LoggerAdapter : ILogSink
|
||||||
{
|
{
|
||||||
public static void Register()
|
public static void Register()
|
||||||
{
|
{
|
||||||
@@ -26,7 +28,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
AvaLogLevel.Warning => RyuLogger.Debug,
|
AvaLogLevel.Warning => RyuLogger.Debug,
|
||||||
AvaLogLevel.Error => RyuLogger.Error,
|
AvaLogLevel.Error => RyuLogger.Error,
|
||||||
AvaLogLevel.Fatal => RyuLogger.Error,
|
AvaLogLevel.Fatal => RyuLogger.Error,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
private readonly Action<T> _callback;
|
private readonly Action<T> _callback;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
private Func<T, Task> _asyncCallback;
|
private readonly Func<T, Task> _asyncCallback;
|
||||||
|
|
||||||
public MiniCommand(Action<T> callback)
|
public MiniCommand(Action<T> callback)
|
||||||
{
|
{
|
||||||
|
@@ -25,7 +25,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
Position = NotificationPosition.BottomRight,
|
Position = NotificationPosition.BottomRight,
|
||||||
MaxItems = MaxNotifications,
|
MaxItems = MaxNotifications,
|
||||||
Margin = new Thickness(0, 0, 15, 40)
|
Margin = new Thickness(0, 0, 15, 40),
|
||||||
};
|
};
|
||||||
|
|
||||||
var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
|
var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
|
||||||
|
@@ -6,12 +6,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
public class OffscreenTextBox : TextBox
|
public class OffscreenTextBox : TextBox
|
||||||
{
|
{
|
||||||
public RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent()
|
public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent()
|
||||||
{
|
{
|
||||||
return KeyDownEvent;
|
return KeyDownEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoutedEvent<KeyEventArgs> GetKeyUpRoutedEvent()
|
public static RoutedEvent<KeyEventArgs> GetKeyUpRoutedEvent()
|
||||||
{
|
{
|
||||||
return KeyUpEvent;
|
return KeyUpEvent;
|
||||||
}
|
}
|
||||||
@@ -28,12 +28,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
OnTextInput(new TextInputEventArgs()
|
OnTextInput(new TextInputEventArgs
|
||||||
{
|
{
|
||||||
Text = text,
|
Text = text,
|
||||||
Device = KeyboardDevice.Instance,
|
Device = KeyboardDevice.Instance,
|
||||||
Source = this,
|
Source = this,
|
||||||
RoutedEvent = TextInputEvent
|
RoutedEvent = TextInputEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -24,7 +23,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed],
|
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed],
|
||||||
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound],
|
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound],
|
||||||
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown],
|
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown],
|
||||||
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined]
|
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription],
|
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription],
|
||||||
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription],
|
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription],
|
||||||
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription],
|
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription],
|
||||||
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription]
|
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
UserError.NoKeys or
|
UserError.NoKeys or
|
||||||
UserError.NoFirmware or
|
UserError.NoFirmware or
|
||||||
UserError.FirmwareParsingFailed => true,
|
UserError.FirmwareParsingFailed => true,
|
||||||
_ => false
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +62,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys",
|
UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys",
|
||||||
UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware",
|
UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware",
|
||||||
_ => SetupGuideUrl
|
_ => SetupGuideUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ShowUserErrorDialog(UserError error, StyleableWindow owner)
|
public static async Task ShowUserErrorDialog(UserError error)
|
||||||
{
|
{
|
||||||
string errorCode = GetErrorCode(error);
|
string errorCode = GetErrorCode(error);
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
@@ -10,46 +11,47 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
[Flags]
|
[Flags]
|
||||||
public enum ClassStyles : uint
|
public enum ClassStyles : uint
|
||||||
{
|
{
|
||||||
CS_CLASSDC = 0x40,
|
CsClassdc = 0x40,
|
||||||
CS_OWNDC = 0x20,
|
CsOwndc = 0x20,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum WindowStyles : uint
|
public enum WindowStyles : uint
|
||||||
{
|
{
|
||||||
WS_CHILD = 0x40000000
|
WsChild = 0x40000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Cursors : uint
|
public enum Cursors : uint
|
||||||
{
|
{
|
||||||
IDC_ARROW = 32512
|
IdcArrow = 32512,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
|
||||||
public enum WindowsMessages : uint
|
public enum WindowsMessages : uint
|
||||||
{
|
{
|
||||||
MOUSEMOVE = 0x0200,
|
Mousemove = 0x0200,
|
||||||
LBUTTONDOWN = 0x0201,
|
Lbuttondown = 0x0201,
|
||||||
LBUTTONUP = 0x0202,
|
Lbuttonup = 0x0202,
|
||||||
LBUTTONDBLCLK = 0x0203,
|
Lbuttondblclk = 0x0203,
|
||||||
RBUTTONDOWN = 0x0204,
|
Rbuttondown = 0x0204,
|
||||||
RBUTTONUP = 0x0205,
|
Rbuttonup = 0x0205,
|
||||||
RBUTTONDBLCLK = 0x0206,
|
Rbuttondblclk = 0x0206,
|
||||||
MBUTTONDOWN = 0x0207,
|
Mbuttondown = 0x0207,
|
||||||
MBUTTONUP = 0x0208,
|
Mbuttonup = 0x0208,
|
||||||
MBUTTONDBLCLK = 0x0209,
|
Mbuttondblclk = 0x0209,
|
||||||
MOUSEWHEEL = 0x020A,
|
Mousewheel = 0x020A,
|
||||||
XBUTTONDOWN = 0x020B,
|
Xbuttondown = 0x020B,
|
||||||
XBUTTONUP = 0x020C,
|
Xbuttonup = 0x020C,
|
||||||
XBUTTONDBLCLK = 0x020D,
|
Xbuttondblclk = 0x020D,
|
||||||
MOUSEHWHEEL = 0x020E,
|
Mousehwheel = 0x020E,
|
||||||
MOUSELAST = 0x020E
|
Mouselast = 0x020E,
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||||
internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam);
|
internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct WNDCLASSEX
|
public struct WndClassEx
|
||||||
{
|
{
|
||||||
public int cbSize;
|
public int cbSize;
|
||||||
public ClassStyles style;
|
public ClassStyles style;
|
||||||
@@ -64,9 +66,9 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
public IntPtr lpszClassName;
|
public IntPtr lpszClassName;
|
||||||
public IntPtr hIconSm;
|
public IntPtr hIconSm;
|
||||||
|
|
||||||
public WNDCLASSEX()
|
public WndClassEx()
|
||||||
{
|
{
|
||||||
cbSize = Marshal.SizeOf<WNDCLASSEX>();
|
cbSize = Marshal.SizeOf<WndClassEx>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,17 +79,17 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public static IntPtr CreateArrowCursor()
|
public static IntPtr CreateArrowCursor()
|
||||||
{
|
{
|
||||||
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW);
|
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IdcArrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
[LibraryImport("user32.dll")]
|
[LibraryImport("user32.dll")]
|
||||||
public static partial IntPtr SetCursor(IntPtr handle);
|
public static partial IntPtr SetCursor(IntPtr handle);
|
||||||
|
|
||||||
[LibraryImport("user32.dll")]
|
[LibraryImport("user32.dll")]
|
||||||
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane);
|
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);
|
||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "UnregisterClassW")]
|
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "UnregisterClassW")]
|
||||||
public static partial short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance);
|
public static partial short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance);
|
||||||
|
@@ -35,6 +35,6 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public string CleanName => Name.Substring(1, Name.Length - 8);
|
public string CleanName => Name[1..^7];
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,6 +4,6 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Controller
|
Controller,
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,16 +7,16 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
internal class InputConfiguration<Key, Stick> : BaseModel
|
internal class InputConfiguration<TKey, TStick> : BaseModel
|
||||||
{
|
{
|
||||||
private float _deadzoneRight;
|
private float _deadzoneRight;
|
||||||
private float _triggerThreshold;
|
private float _triggerThreshold;
|
||||||
private float _deadzoneLeft;
|
private float _deadzoneLeft;
|
||||||
private double _gyroDeadzone;
|
private double _gyroDeadzone;
|
||||||
private int _sensitivity;
|
private int _sensitivity;
|
||||||
private bool enableMotion;
|
private bool _enableMotion;
|
||||||
private float weakRumble;
|
private float _weakRumble;
|
||||||
private float strongRumble;
|
private float _strongRumble;
|
||||||
private float _rangeLeft;
|
private float _rangeLeft;
|
||||||
private float _rangeRight;
|
private float _rangeRight;
|
||||||
|
|
||||||
@@ -37,17 +37,17 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PlayerIndex PlayerIndex { get; set; }
|
public PlayerIndex PlayerIndex { get; set; }
|
||||||
|
|
||||||
public Stick LeftJoystick { get; set; }
|
public TStick LeftJoystick { get; set; }
|
||||||
public bool LeftInvertStickX { get; set; }
|
public bool LeftInvertStickX { get; set; }
|
||||||
public bool LeftInvertStickY { get; set; }
|
public bool LeftInvertStickY { get; set; }
|
||||||
public bool RightRotate90 { get; set; }
|
public bool RightRotate90 { get; set; }
|
||||||
public Key LeftControllerStickButton { get; set; }
|
public TKey LeftControllerStickButton { get; set; }
|
||||||
|
|
||||||
public Stick RightJoystick { get; set; }
|
public TStick RightJoystick { get; set; }
|
||||||
public bool RightInvertStickX { get; set; }
|
public bool RightInvertStickX { get; set; }
|
||||||
public bool RightInvertStickY { get; set; }
|
public bool RightInvertStickY { get; set; }
|
||||||
public bool LeftRotate90 { get; set; }
|
public bool LeftRotate90 { get; set; }
|
||||||
public Key RightControllerStickButton { get; set; }
|
public TKey RightControllerStickButton { get; set; }
|
||||||
|
|
||||||
public float DeadzoneLeft
|
public float DeadzoneLeft
|
||||||
{
|
{
|
||||||
@@ -106,38 +106,37 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public MotionInputBackendType MotionBackend { get; set; }
|
public MotionInputBackendType MotionBackend { get; set; }
|
||||||
|
|
||||||
public Key ButtonMinus { get; set; }
|
public TKey ButtonMinus { get; set; }
|
||||||
public Key ButtonL { get; set; }
|
public TKey ButtonL { get; set; }
|
||||||
public Key ButtonZl { get; set; }
|
public TKey ButtonZl { get; set; }
|
||||||
public Key LeftButtonSl { get; set; }
|
public TKey LeftButtonSl { get; set; }
|
||||||
public Key LeftButtonSr { get; set; }
|
public TKey LeftButtonSr { get; set; }
|
||||||
public Key DpadUp { get; set; }
|
public TKey DpadUp { get; set; }
|
||||||
public Key DpadDown { get; set; }
|
public TKey DpadDown { get; set; }
|
||||||
public Key DpadLeft { get; set; }
|
public TKey DpadLeft { get; set; }
|
||||||
public Key DpadRight { get; set; }
|
public TKey DpadRight { get; set; }
|
||||||
|
|
||||||
public Key ButtonPlus { get; set; }
|
public TKey ButtonPlus { get; set; }
|
||||||
public Key ButtonR { get; set; }
|
public TKey ButtonR { get; set; }
|
||||||
public Key ButtonZr { get; set; }
|
public TKey ButtonZr { get; set; }
|
||||||
public Key RightButtonSl { get; set; }
|
public TKey RightButtonSl { get; set; }
|
||||||
public Key RightButtonSr { get; set; }
|
public TKey RightButtonSr { get; set; }
|
||||||
public Key ButtonX { get; set; }
|
public TKey ButtonX { get; set; }
|
||||||
public Key ButtonB { get; set; }
|
public TKey ButtonB { get; set; }
|
||||||
public Key ButtonY { get; set; }
|
public TKey ButtonY { get; set; }
|
||||||
public Key ButtonA { get; set; }
|
public TKey ButtonA { get; set; }
|
||||||
|
|
||||||
|
public TKey LeftStickUp { get; set; }
|
||||||
|
public TKey LeftStickDown { get; set; }
|
||||||
|
public TKey LeftStickLeft { get; set; }
|
||||||
|
public TKey LeftStickRight { get; set; }
|
||||||
|
public TKey LeftKeyboardStickButton { get; set; }
|
||||||
|
|
||||||
public Key LeftStickUp { get; set; }
|
public TKey RightStickUp { get; set; }
|
||||||
public Key LeftStickDown { get; set; }
|
public TKey RightStickDown { get; set; }
|
||||||
public Key LeftStickLeft { get; set; }
|
public TKey RightStickLeft { get; set; }
|
||||||
public Key LeftStickRight { get; set; }
|
public TKey RightStickRight { get; set; }
|
||||||
public Key LeftKeyboardStickButton { get; set; }
|
public TKey RightKeyboardStickButton { get; set; }
|
||||||
|
|
||||||
public Key RightStickUp { get; set; }
|
|
||||||
public Key RightStickDown { get; set; }
|
|
||||||
public Key RightStickLeft { get; set; }
|
|
||||||
public Key RightStickRight { get; set; }
|
|
||||||
public Key RightKeyboardStickButton { get; set; }
|
|
||||||
|
|
||||||
public int Sensitivity
|
public int Sensitivity
|
||||||
{
|
{
|
||||||
@@ -163,9 +162,9 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public bool EnableMotion
|
public bool EnableMotion
|
||||||
{
|
{
|
||||||
get => enableMotion; set
|
get => _enableMotion; set
|
||||||
{
|
{
|
||||||
enableMotion = value;
|
_enableMotion = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
@@ -181,18 +180,18 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public bool EnableRumble { get; set; }
|
public bool EnableRumble { get; set; }
|
||||||
public float WeakRumble
|
public float WeakRumble
|
||||||
{
|
{
|
||||||
get => weakRumble; set
|
get => _weakRumble; set
|
||||||
{
|
{
|
||||||
weakRumble = value;
|
_weakRumble = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public float StrongRumble
|
public float StrongRumble
|
||||||
{
|
{
|
||||||
get => strongRumble; set
|
get => _strongRumble; set
|
||||||
{
|
{
|
||||||
strongRumble = value;
|
_strongRumble = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
@@ -209,71 +208,71 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
if (config is StandardKeyboardInputConfig keyboardConfig)
|
if (config is StandardKeyboardInputConfig keyboardConfig)
|
||||||
{
|
{
|
||||||
LeftStickUp = (Key)(object)keyboardConfig.LeftJoyconStick.StickUp;
|
LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp;
|
||||||
LeftStickDown = (Key)(object)keyboardConfig.LeftJoyconStick.StickDown;
|
LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown;
|
||||||
LeftStickLeft = (Key)(object)keyboardConfig.LeftJoyconStick.StickLeft;
|
LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft;
|
||||||
LeftStickRight = (Key)(object)keyboardConfig.LeftJoyconStick.StickRight;
|
LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight;
|
||||||
LeftKeyboardStickButton = (Key)(object)keyboardConfig.LeftJoyconStick.StickButton;
|
LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton;
|
||||||
|
|
||||||
RightStickUp = (Key)(object)keyboardConfig.RightJoyconStick.StickUp;
|
RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp;
|
||||||
RightStickDown = (Key)(object)keyboardConfig.RightJoyconStick.StickDown;
|
RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown;
|
||||||
RightStickLeft = (Key)(object)keyboardConfig.RightJoyconStick.StickLeft;
|
RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft;
|
||||||
RightStickRight = (Key)(object)keyboardConfig.RightJoyconStick.StickRight;
|
RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight;
|
||||||
RightKeyboardStickButton = (Key)(object)keyboardConfig.RightJoyconStick.StickButton;
|
RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton;
|
||||||
|
|
||||||
ButtonA = (Key)(object)keyboardConfig.RightJoycon.ButtonA;
|
ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA;
|
||||||
ButtonB = (Key)(object)keyboardConfig.RightJoycon.ButtonB;
|
ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB;
|
||||||
ButtonX = (Key)(object)keyboardConfig.RightJoycon.ButtonX;
|
ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX;
|
||||||
ButtonY = (Key)(object)keyboardConfig.RightJoycon.ButtonY;
|
ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY;
|
||||||
ButtonR = (Key)(object)keyboardConfig.RightJoycon.ButtonR;
|
ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR;
|
||||||
RightButtonSl = (Key)(object)keyboardConfig.RightJoycon.ButtonSl;
|
RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl;
|
||||||
RightButtonSr = (Key)(object)keyboardConfig.RightJoycon.ButtonSr;
|
RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr;
|
||||||
ButtonZr = (Key)(object)keyboardConfig.RightJoycon.ButtonZr;
|
ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr;
|
||||||
ButtonPlus = (Key)(object)keyboardConfig.RightJoycon.ButtonPlus;
|
ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus;
|
||||||
|
|
||||||
DpadUp = (Key)(object)keyboardConfig.LeftJoycon.DpadUp;
|
DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp;
|
||||||
DpadDown = (Key)(object)keyboardConfig.LeftJoycon.DpadDown;
|
DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown;
|
||||||
DpadLeft = (Key)(object)keyboardConfig.LeftJoycon.DpadLeft;
|
DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft;
|
||||||
DpadRight = (Key)(object)keyboardConfig.LeftJoycon.DpadRight;
|
DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight;
|
||||||
ButtonMinus = (Key)(object)keyboardConfig.LeftJoycon.ButtonMinus;
|
ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus;
|
||||||
LeftButtonSl = (Key)(object)keyboardConfig.LeftJoycon.ButtonSl;
|
LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl;
|
||||||
LeftButtonSr = (Key)(object)keyboardConfig.LeftJoycon.ButtonSr;
|
LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr;
|
||||||
ButtonZl = (Key)(object)keyboardConfig.LeftJoycon.ButtonZl;
|
ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl;
|
||||||
ButtonL = (Key)(object)keyboardConfig.LeftJoycon.ButtonL;
|
ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL;
|
||||||
}
|
}
|
||||||
else if (config is StandardControllerInputConfig controllerConfig)
|
else if (config is StandardControllerInputConfig controllerConfig)
|
||||||
{
|
{
|
||||||
LeftJoystick = (Stick)(object)controllerConfig.LeftJoyconStick.Joystick;
|
LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick;
|
||||||
LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX;
|
LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX;
|
||||||
LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY;
|
LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY;
|
||||||
LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW;
|
LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW;
|
||||||
LeftControllerStickButton = (Key)(object)controllerConfig.LeftJoyconStick.StickButton;
|
LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton;
|
||||||
|
|
||||||
RightJoystick = (Stick)(object)controllerConfig.RightJoyconStick.Joystick;
|
RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick;
|
||||||
RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX;
|
RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX;
|
||||||
RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY;
|
RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY;
|
||||||
RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW;
|
RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW;
|
||||||
RightControllerStickButton = (Key)(object)controllerConfig.RightJoyconStick.StickButton;
|
RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton;
|
||||||
|
|
||||||
ButtonA = (Key)(object)controllerConfig.RightJoycon.ButtonA;
|
ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA;
|
||||||
ButtonB = (Key)(object)controllerConfig.RightJoycon.ButtonB;
|
ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB;
|
||||||
ButtonX = (Key)(object)controllerConfig.RightJoycon.ButtonX;
|
ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX;
|
||||||
ButtonY = (Key)(object)controllerConfig.RightJoycon.ButtonY;
|
ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY;
|
||||||
ButtonR = (Key)(object)controllerConfig.RightJoycon.ButtonR;
|
ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR;
|
||||||
RightButtonSl = (Key)(object)controllerConfig.RightJoycon.ButtonSl;
|
RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl;
|
||||||
RightButtonSr = (Key)(object)controllerConfig.RightJoycon.ButtonSr;
|
RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr;
|
||||||
ButtonZr = (Key)(object)controllerConfig.RightJoycon.ButtonZr;
|
ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr;
|
||||||
ButtonPlus = (Key)(object)controllerConfig.RightJoycon.ButtonPlus;
|
ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus;
|
||||||
|
|
||||||
DpadUp = (Key)(object)controllerConfig.LeftJoycon.DpadUp;
|
DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp;
|
||||||
DpadDown = (Key)(object)controllerConfig.LeftJoycon.DpadDown;
|
DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown;
|
||||||
DpadLeft = (Key)(object)controllerConfig.LeftJoycon.DpadLeft;
|
DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft;
|
||||||
DpadRight = (Key)(object)controllerConfig.LeftJoycon.DpadRight;
|
DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight;
|
||||||
ButtonMinus = (Key)(object)controllerConfig.LeftJoycon.ButtonMinus;
|
ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus;
|
||||||
LeftButtonSl = (Key)(object)controllerConfig.LeftJoycon.ButtonSl;
|
LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl;
|
||||||
LeftButtonSr = (Key)(object)controllerConfig.LeftJoycon.ButtonSr;
|
LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr;
|
||||||
ButtonZl = (Key)(object)controllerConfig.LeftJoycon.ButtonZl;
|
ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl;
|
||||||
ButtonL = (Key)(object)controllerConfig.LeftJoycon.ButtonL;
|
ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL;
|
||||||
|
|
||||||
DeadzoneLeft = controllerConfig.DeadzoneLeft;
|
DeadzoneLeft = controllerConfig.DeadzoneLeft;
|
||||||
DeadzoneRight = controllerConfig.DeadzoneRight;
|
DeadzoneRight = controllerConfig.DeadzoneRight;
|
||||||
@@ -317,65 +316,66 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
{
|
{
|
||||||
if (Backend == InputBackendType.WindowKeyboard)
|
if (Backend == InputBackendType.WindowKeyboard)
|
||||||
{
|
{
|
||||||
return new StandardKeyboardInputConfig()
|
return new StandardKeyboardInputConfig
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
Backend = Backend,
|
Backend = Backend,
|
||||||
PlayerIndex = PlayerIndex,
|
PlayerIndex = PlayerIndex,
|
||||||
ControllerType = ControllerType,
|
ControllerType = ControllerType,
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<Ryujinx.Common.Configuration.Hid.Key>()
|
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
DpadUp = (Ryujinx.Common.Configuration.Hid.Key)(object)DpadUp,
|
DpadUp = (Key)(object)DpadUp,
|
||||||
DpadDown = (Ryujinx.Common.Configuration.Hid.Key)(object)DpadDown,
|
DpadDown = (Key)(object)DpadDown,
|
||||||
DpadLeft = (Ryujinx.Common.Configuration.Hid.Key)(object)DpadLeft,
|
DpadLeft = (Key)(object)DpadLeft,
|
||||||
DpadRight = (Ryujinx.Common.Configuration.Hid.Key)(object)DpadRight,
|
DpadRight = (Key)(object)DpadRight,
|
||||||
ButtonL = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonL,
|
ButtonL = (Key)(object)ButtonL,
|
||||||
ButtonZl = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonZl,
|
ButtonZl = (Key)(object)ButtonZl,
|
||||||
ButtonSl = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftButtonSl,
|
ButtonSl = (Key)(object)LeftButtonSl,
|
||||||
ButtonSr = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftButtonSr,
|
ButtonSr = (Key)(object)LeftButtonSr,
|
||||||
ButtonMinus = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonMinus
|
ButtonMinus = (Key)(object)ButtonMinus,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<Ryujinx.Common.Configuration.Hid.Key>()
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
ButtonA = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonA,
|
ButtonA = (Key)(object)ButtonA,
|
||||||
ButtonB = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonB,
|
ButtonB = (Key)(object)ButtonB,
|
||||||
ButtonX = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonX,
|
ButtonX = (Key)(object)ButtonX,
|
||||||
ButtonY = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonY,
|
ButtonY = (Key)(object)ButtonY,
|
||||||
ButtonPlus = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonPlus,
|
ButtonPlus = (Key)(object)ButtonPlus,
|
||||||
ButtonSl = (Ryujinx.Common.Configuration.Hid.Key)(object)RightButtonSl,
|
ButtonSl = (Key)(object)RightButtonSl,
|
||||||
ButtonSr = (Ryujinx.Common.Configuration.Hid.Key)(object)RightButtonSr,
|
ButtonSr = (Key)(object)RightButtonSr,
|
||||||
ButtonR = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonR,
|
ButtonR = (Key)(object)ButtonR,
|
||||||
ButtonZr = (Ryujinx.Common.Configuration.Hid.Key)(object)ButtonZr
|
ButtonZr = (Key)(object)ButtonZr,
|
||||||
},
|
},
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Ryujinx.Common.Configuration.Hid.Key>()
|
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftStickUp,
|
StickUp = (Key)(object)LeftStickUp,
|
||||||
StickDown = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftStickDown,
|
StickDown = (Key)(object)LeftStickDown,
|
||||||
StickRight = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftStickRight,
|
StickRight = (Key)(object)LeftStickRight,
|
||||||
StickLeft = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftStickLeft,
|
StickLeft = (Key)(object)LeftStickLeft,
|
||||||
StickButton = (Ryujinx.Common.Configuration.Hid.Key)(object)LeftKeyboardStickButton
|
StickButton = (Key)(object)LeftKeyboardStickButton,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<Ryujinx.Common.Configuration.Hid.Key>()
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = (Ryujinx.Common.Configuration.Hid.Key)(object)RightStickUp,
|
StickUp = (Key)(object)RightStickUp,
|
||||||
StickDown = (Ryujinx.Common.Configuration.Hid.Key)(object)RightStickDown,
|
StickDown = (Key)(object)RightStickDown,
|
||||||
StickLeft = (Ryujinx.Common.Configuration.Hid.Key)(object)RightStickLeft,
|
StickLeft = (Key)(object)RightStickLeft,
|
||||||
StickRight = (Ryujinx.Common.Configuration.Hid.Key)(object)RightStickRight,
|
StickRight = (Key)(object)RightStickRight,
|
||||||
StickButton = (Ryujinx.Common.Configuration.Hid.Key)(object)RightKeyboardStickButton
|
StickButton = (Key)(object)RightKeyboardStickButton,
|
||||||
},
|
},
|
||||||
Version = InputConfig.CurrentVersion
|
Version = InputConfig.CurrentVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (Backend == InputBackendType.GamepadSDL2)
|
|
||||||
|
if (Backend == InputBackendType.GamepadSDL2)
|
||||||
{
|
{
|
||||||
var config = new StandardControllerInputConfig()
|
var config = new StandardControllerInputConfig
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
Backend = Backend,
|
Backend = Backend,
|
||||||
PlayerIndex = PlayerIndex,
|
PlayerIndex = PlayerIndex,
|
||||||
ControllerType = ControllerType,
|
ControllerType = ControllerType,
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>()
|
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
|
||||||
{
|
{
|
||||||
DpadUp = (GamepadInputId)(object)DpadUp,
|
DpadUp = (GamepadInputId)(object)DpadUp,
|
||||||
DpadDown = (GamepadInputId)(object)DpadDown,
|
DpadDown = (GamepadInputId)(object)DpadDown,
|
||||||
@@ -387,7 +387,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
ButtonSr = (GamepadInputId)(object)LeftButtonSr,
|
ButtonSr = (GamepadInputId)(object)LeftButtonSr,
|
||||||
ButtonMinus = (GamepadInputId)(object)ButtonMinus,
|
ButtonMinus = (GamepadInputId)(object)ButtonMinus,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>()
|
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
|
||||||
{
|
{
|
||||||
ButtonA = (GamepadInputId)(object)ButtonA,
|
ButtonA = (GamepadInputId)(object)ButtonA,
|
||||||
ButtonB = (GamepadInputId)(object)ButtonB,
|
ButtonB = (GamepadInputId)(object)ButtonB,
|
||||||
@@ -399,7 +399,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
ButtonR = (GamepadInputId)(object)ButtonR,
|
ButtonR = (GamepadInputId)(object)ButtonR,
|
||||||
ButtonZr = (GamepadInputId)(object)ButtonZr,
|
ButtonZr = (GamepadInputId)(object)ButtonZr,
|
||||||
},
|
},
|
||||||
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>()
|
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
|
||||||
{
|
{
|
||||||
Joystick = (StickInputId)(object)LeftJoystick,
|
Joystick = (StickInputId)(object)LeftJoystick,
|
||||||
InvertStickX = LeftInvertStickX,
|
InvertStickX = LeftInvertStickX,
|
||||||
@@ -407,7 +407,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
Rotate90CW = LeftRotate90,
|
Rotate90CW = LeftRotate90,
|
||||||
StickButton = (GamepadInputId)(object)LeftControllerStickButton,
|
StickButton = (GamepadInputId)(object)LeftControllerStickButton,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>()
|
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
|
||||||
{
|
{
|
||||||
Joystick = (StickInputId)(object)RightJoystick,
|
Joystick = (StickInputId)(object)RightJoystick,
|
||||||
InvertStickX = RightInvertStickX,
|
InvertStickX = RightInvertStickX,
|
||||||
@@ -415,11 +415,11 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
Rotate90CW = RightRotate90,
|
Rotate90CW = RightRotate90,
|
||||||
StickButton = (GamepadInputId)(object)RightControllerStickButton,
|
StickButton = (GamepadInputId)(object)RightControllerStickButton,
|
||||||
},
|
},
|
||||||
Rumble = new RumbleConfigController()
|
Rumble = new RumbleConfigController
|
||||||
{
|
{
|
||||||
EnableRumble = EnableRumble,
|
EnableRumble = EnableRumble,
|
||||||
WeakRumble = WeakRumble,
|
WeakRumble = WeakRumble,
|
||||||
StrongRumble = StrongRumble
|
StrongRumble = StrongRumble,
|
||||||
},
|
},
|
||||||
Version = InputConfig.CurrentVersion,
|
Version = InputConfig.CurrentVersion,
|
||||||
DeadzoneLeft = DeadzoneLeft,
|
DeadzoneLeft = DeadzoneLeft,
|
||||||
@@ -428,19 +428,19 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
RangeRight = RangeRight,
|
RangeRight = RangeRight,
|
||||||
TriggerThreshold = TriggerThreshold,
|
TriggerThreshold = TriggerThreshold,
|
||||||
Motion = EnableCemuHookMotion
|
Motion = EnableCemuHookMotion
|
||||||
? new CemuHookMotionConfigController()
|
? new CemuHookMotionConfigController
|
||||||
{
|
{
|
||||||
DsuServerHost = DsuServerHost,
|
DsuServerHost = DsuServerHost,
|
||||||
DsuServerPort = DsuServerPort,
|
DsuServerPort = DsuServerPort,
|
||||||
Slot = Slot,
|
Slot = Slot,
|
||||||
AltSlot = AltSlot,
|
AltSlot = AltSlot,
|
||||||
MirrorInput = MirrorInput,
|
MirrorInput = MirrorInput,
|
||||||
MotionBackend = MotionInputBackendType.CemuHook
|
MotionBackend = MotionInputBackendType.CemuHook,
|
||||||
}
|
}
|
||||||
: new StandardMotionConfigController()
|
: new StandardMotionConfigController
|
||||||
{
|
{
|
||||||
MotionBackend = MotionInputBackendType.GamepadDriver
|
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
config.Motion.Sensitivity = Sensitivity;
|
config.Motion.Sensitivity = Sensitivity;
|
||||||
|
@@ -2,12 +2,12 @@ using LibHac.Fs;
|
|||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.FileSystem;
|
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
return "0 KiB";
|
return "0 KiB";
|
||||||
}
|
}
|
||||||
|
|
||||||
public SaveModel(SaveDataInfo info, VirtualFileSystem virtualFileSystem)
|
public SaveModel(SaveDataInfo info)
|
||||||
{
|
{
|
||||||
SaveId = info.SaveDataId;
|
SaveId = info.SaveDataId;
|
||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
@@ -81,10 +81,11 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
var saveRoot = Path.Combine(MainWindow.MainWindowViewModel.VirtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
||||||
|
|
||||||
long total_size = GetDirectorySize(saveRoot);
|
long totalSize = GetDirectorySize(saveRoot);
|
||||||
long GetDirectorySize(string path)
|
|
||||||
|
static long GetDirectorySize(string path)
|
||||||
{
|
{
|
||||||
long size = 0;
|
long size = 0;
|
||||||
if (Directory.Exists(path))
|
if (Directory.Exists(path))
|
||||||
@@ -105,7 +106,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
Size = total_size;
|
Size = totalSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
private string _name = String.Empty;
|
private string _name = String.Empty;
|
||||||
private UserId _userId;
|
private UserId _userId;
|
||||||
|
|
||||||
public uint MaxProfileNameLength => 0x20;
|
public static uint MaxProfileNameLength => 0x20;
|
||||||
|
|
||||||
public byte[] Image
|
public byte[] Image
|
||||||
{
|
{
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
@@ -87,7 +88,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
private void UpdateBackground()
|
private void UpdateBackground()
|
||||||
{
|
{
|
||||||
Avalonia.Application.Current.Styles.TryGetResource("ControlFillColorSecondary", out object color);
|
Application.Current.Styles.TryGetResource("ControlFillColorSecondary", out object color);
|
||||||
|
|
||||||
if (color is not null)
|
if (color is not null)
|
||||||
{
|
{
|
||||||
|
@@ -77,11 +77,13 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
{
|
{
|
||||||
return CreateLinux(control);
|
return CreateLinux(control);
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
return CreateWin32(control);
|
return CreateWin32(control);
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
return CreateMacOS();
|
return CreateMacOS();
|
||||||
}
|
}
|
||||||
@@ -141,21 +143,21 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
{
|
{
|
||||||
if (VisualRoot != null)
|
if (VisualRoot != null)
|
||||||
{
|
{
|
||||||
if (msg == WindowsMessages.LBUTTONDOWN ||
|
if (msg == WindowsMessages.Lbuttondown ||
|
||||||
msg == WindowsMessages.RBUTTONDOWN ||
|
msg == WindowsMessages.Rbuttondown ||
|
||||||
msg == WindowsMessages.LBUTTONUP ||
|
msg == WindowsMessages.Lbuttonup ||
|
||||||
msg == WindowsMessages.RBUTTONUP ||
|
msg == WindowsMessages.Rbuttonup ||
|
||||||
msg == WindowsMessages.MOUSEMOVE)
|
msg == WindowsMessages.Mousemove)
|
||||||
{
|
{
|
||||||
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
||||||
Pointer pointer = new(0, PointerType.Mouse, true);
|
Pointer pointer = new(0, PointerType.Mouse, true);
|
||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case WindowsMessages.LBUTTONDOWN:
|
case WindowsMessages.Lbuttondown:
|
||||||
case WindowsMessages.RBUTTONDOWN:
|
case WindowsMessages.Rbuttondown:
|
||||||
{
|
{
|
||||||
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
bool isLeft = msg == WindowsMessages.Lbuttondown;
|
||||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
||||||
|
|
||||||
@@ -172,10 +174,10 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WindowsMessages.LBUTTONUP:
|
case WindowsMessages.Lbuttonup:
|
||||||
case WindowsMessages.RBUTTONUP:
|
case WindowsMessages.Rbuttonup:
|
||||||
{
|
{
|
||||||
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
bool isLeft = msg == WindowsMessages.Lbuttonup;
|
||||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
||||||
|
|
||||||
@@ -193,7 +195,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WindowsMessages.MOUSEMOVE:
|
case WindowsMessages.Mousemove:
|
||||||
{
|
{
|
||||||
var evnt = new PointerEventArgs(
|
var evnt = new PointerEventArgs(
|
||||||
PointerMovedEvent,
|
PointerMovedEvent,
|
||||||
@@ -216,19 +218,19 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
};
|
};
|
||||||
|
|
||||||
WNDCLASSEX wndClassEx = new()
|
WndClassEx wndClassEx = new()
|
||||||
{
|
{
|
||||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
cbSize = Marshal.SizeOf<WndClassEx>(),
|
||||||
hInstance = GetModuleHandle(null),
|
hInstance = GetModuleHandle(null),
|
||||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||||
style = ClassStyles.CS_OWNDC,
|
style = ClassStyles.CsOwndc,
|
||||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||||
hCursor = CreateArrowCursor()
|
hCursor = CreateArrowCursor(),
|
||||||
};
|
};
|
||||||
|
|
||||||
RegisterClassEx(ref wndClassEx);
|
RegisterClassEx(ref wndClassEx);
|
||||||
|
|
||||||
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WS_CHILD, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
|
||||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||||
|
|
||||||
@@ -280,9 +282,11 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
#pragma warning disable CA1822 // Mark member as static
|
||||||
void DestroyMacOS()
|
void DestroyMacOS()
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA1822
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
|
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SurfaceKHR CreateSurface(Instance instance, Vk api)
|
public SurfaceKHR CreateSurface(Instance instance, Vk _)
|
||||||
{
|
{
|
||||||
return CreateSurface(instance);
|
return CreateSurface(instance);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
@@ -87,7 +88,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
Version = Program.Version;
|
Version = Program.Version;
|
||||||
|
|
||||||
var assets = AvaloniaLocator.Current.GetService<Avalonia.Platform.IAssetLoader>();
|
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
|
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
|
||||||
{
|
{
|
||||||
|
@@ -18,7 +18,6 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@@ -44,7 +43,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _useRandomUuid;
|
private bool _useRandomUuid;
|
||||||
private string _usage;
|
private string _usage;
|
||||||
|
|
||||||
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
||||||
{
|
{
|
||||||
@@ -52,7 +51,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
_httpClient = new HttpClient
|
_httpClient = new HttpClient
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(30)
|
Timeout = TimeSpan.FromSeconds(30),
|
||||||
};
|
};
|
||||||
|
|
||||||
LastScannedAmiiboId = lastScannedAmiiboId;
|
LastScannedAmiiboId = lastScannedAmiiboId;
|
||||||
@@ -185,6 +184,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
_httpClient.Dispose();
|
_httpClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||||
|
|
||||||
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).LastUpdated))
|
||||||
{
|
{
|
||||||
amiiboJsonString = await DownloadAmiiboJson();
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).Amiibo;
|
||||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
ParseAmiiboData();
|
ParseAmiiboData();
|
||||||
@@ -426,8 +426,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
using (MemoryStream memoryStream = new(amiiboPreviewBytes))
|
using MemoryStream memoryStream = new(amiiboPreviewBytes);
|
||||||
{
|
|
||||||
Bitmap bitmap = new(memoryStream);
|
Bitmap bitmap = new(memoryStream);
|
||||||
|
|
||||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
|
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
|
||||||
@@ -438,7 +438,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
|
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
|
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
|
||||||
@@ -447,15 +446,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void ResetAmiiboPreview()
|
private void ResetAmiiboPreview()
|
||||||
{
|
{
|
||||||
using (MemoryStream memoryStream = new(_amiiboLogoBytes))
|
using MemoryStream memoryStream = new(_amiiboLogoBytes);
|
||||||
{
|
|
||||||
Bitmap bitmap = new(memoryStream);
|
Bitmap bitmap = new(memoryStream);
|
||||||
|
|
||||||
AmiiboImage = bitmap;
|
AmiiboImage = bitmap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async void ShowInfoDialog()
|
private static async void ShowInfoDialog()
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
||||||
|
@@ -1,363 +0,0 @@
|
|||||||
using Avalonia.Media;
|
|
||||||
using DynamicData;
|
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Fs;
|
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Ncm;
|
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
|
||||||
using SixLabors.ImageSharp;
|
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using System;
|
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Color = Avalonia.Media.Color;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
|
||||||
{
|
|
||||||
internal class AvatarProfileViewModel : BaseModel, IDisposable
|
|
||||||
{
|
|
||||||
private const int MaxImageTasks = 4;
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, byte[]> _avatarStore = new();
|
|
||||||
private static bool _isPreloading;
|
|
||||||
private static Action _loadCompleteAction;
|
|
||||||
|
|
||||||
private ObservableCollection<ProfileImageModel> _images;
|
|
||||||
private Color _backgroundColor = Colors.White;
|
|
||||||
|
|
||||||
private int _selectedIndex;
|
|
||||||
private int _imagesLoaded;
|
|
||||||
private bool _isActive;
|
|
||||||
private byte[] _selectedImage;
|
|
||||||
private bool _isIndeterminate = true;
|
|
||||||
|
|
||||||
public bool IsActive
|
|
||||||
{
|
|
||||||
get => _isActive;
|
|
||||||
set => _isActive = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarProfileViewModel()
|
|
||||||
{
|
|
||||||
_images = new ObservableCollection<ProfileImageModel>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarProfileViewModel(Action loadCompleteAction)
|
|
||||||
{
|
|
||||||
_images = new ObservableCollection<ProfileImageModel>();
|
|
||||||
|
|
||||||
if (_isPreloading)
|
|
||||||
{
|
|
||||||
_loadCompleteAction = loadCompleteAction;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ReloadImages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color BackgroundColor
|
|
||||||
{
|
|
||||||
get => _backgroundColor;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_backgroundColor = value;
|
|
||||||
|
|
||||||
IsActive = false;
|
|
||||||
|
|
||||||
ReloadImages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<ProfileImageModel> Images
|
|
||||||
{
|
|
||||||
get => _images;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_images = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsIndeterminate
|
|
||||||
{
|
|
||||||
get => _isIndeterminate;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isIndeterminate = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ImageCount => _avatarStore.Count;
|
|
||||||
|
|
||||||
public int ImagesLoaded
|
|
||||||
{
|
|
||||||
get => _imagesLoaded;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_imagesLoaded = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SelectedIndex
|
|
||||||
{
|
|
||||||
get => _selectedIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_selectedIndex = value;
|
|
||||||
|
|
||||||
if (_selectedIndex == -1)
|
|
||||||
{
|
|
||||||
SelectedImage = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedImage = _images[_selectedIndex].Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] SelectedImage
|
|
||||||
{
|
|
||||||
get => _selectedImage;
|
|
||||||
private set => _selectedImage = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadImages()
|
|
||||||
{
|
|
||||||
if (_isPreloading)
|
|
||||||
{
|
|
||||||
IsIndeterminate = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
IsActive = true;
|
|
||||||
|
|
||||||
Images.Clear();
|
|
||||||
int selectedIndex = _selectedIndex;
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
ImagesLoaded = 0;
|
|
||||||
IsIndeterminate = false;
|
|
||||||
|
|
||||||
var keys = _avatarStore.Keys.ToList();
|
|
||||||
|
|
||||||
var newImages = new List<ProfileImageModel>();
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxImageTasks; i++)
|
|
||||||
{
|
|
||||||
var start = i;
|
|
||||||
tasks.Add(Task.Run(() => ImageTask(start)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.WaitAll(tasks.ToArray());
|
|
||||||
|
|
||||||
Images.AddRange(newImages);
|
|
||||||
|
|
||||||
void ImageTask(int start)
|
|
||||||
{
|
|
||||||
for (int i = start; i < keys.Count; i += MaxImageTasks)
|
|
||||||
{
|
|
||||||
if (!IsActive)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = keys[i];
|
|
||||||
var image = _avatarStore[keys[i]];
|
|
||||||
|
|
||||||
var data = ProcessImage(image);
|
|
||||||
newImages.Add(new ProfileImageModel(key, data));
|
|
||||||
if (index++ == selectedIndex)
|
|
||||||
{
|
|
||||||
SelectedImage = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref _imagesLoaded);
|
|
||||||
OnPropertyChanged(nameof(ImagesLoaded));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] ProcessImage(byte[] data)
|
|
||||||
{
|
|
||||||
using (MemoryStream streamJpg = new())
|
|
||||||
{
|
|
||||||
Image avatarImage = Image.Load(data, new PngDecoder());
|
|
||||||
|
|
||||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(BackgroundColor.R,
|
|
||||||
BackgroundColor.G,
|
|
||||||
BackgroundColor.B,
|
|
||||||
BackgroundColor.A)));
|
|
||||||
avatarImage.SaveAsJpeg(streamJpg);
|
|
||||||
|
|
||||||
return streamJpg.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_avatarStore.Count > 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isPreloading = true;
|
|
||||||
|
|
||||||
string contentPath =
|
|
||||||
contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem,
|
|
||||||
NcaContentType.Data);
|
|
||||||
string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(avatarPath))
|
|
||||||
{
|
|
||||||
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
|
|
||||||
{
|
|
||||||
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
|
||||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
|
|
||||||
{
|
|
||||||
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
|
|
||||||
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") &&
|
|
||||||
item.FullPath.Contains("szs"))
|
|
||||||
{
|
|
||||||
using var file = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
|
||||||
.ThrowIfFailure();
|
|
||||||
|
|
||||||
using (MemoryStream stream = new())
|
|
||||||
using (MemoryStream streamPng = new())
|
|
||||||
{
|
|
||||||
file.Get.AsStream().CopyTo(stream);
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
|
||||||
|
|
||||||
avatarImage.SaveAsPng(streamPng);
|
|
||||||
|
|
||||||
_avatarStore.Add(item.FullPath, streamPng.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isPreloading = false;
|
|
||||||
_loadCompleteAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] DecompressYaz0(Stream stream)
|
|
||||||
{
|
|
||||||
using (BinaryReader reader = new(stream))
|
|
||||||
{
|
|
||||||
reader.ReadInt32(); // Magic
|
|
||||||
|
|
||||||
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
|
||||||
|
|
||||||
reader.ReadInt64(); // Padding
|
|
||||||
|
|
||||||
byte[] input = new byte[stream.Length - stream.Position];
|
|
||||||
stream.Read(input, 0, input.Length);
|
|
||||||
|
|
||||||
uint inputOffset = 0;
|
|
||||||
|
|
||||||
byte[] output = new byte[decodedLength];
|
|
||||||
uint outputOffset = 0;
|
|
||||||
|
|
||||||
ushort mask = 0;
|
|
||||||
byte header = 0;
|
|
||||||
|
|
||||||
while (outputOffset < decodedLength)
|
|
||||||
{
|
|
||||||
if ((mask >>= 1) == 0)
|
|
||||||
{
|
|
||||||
header = input[inputOffset++];
|
|
||||||
mask = 0x80;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((header & mask) != 0)
|
|
||||||
{
|
|
||||||
if (outputOffset == output.Length)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
output[outputOffset++] = input[inputOffset++];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byte byte1 = input[inputOffset++];
|
|
||||||
byte byte2 = input[inputOffset++];
|
|
||||||
|
|
||||||
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
|
|
||||||
uint position = outputOffset - (dist + 1);
|
|
||||||
|
|
||||||
uint length = (uint)byte1 >> 4;
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
length = (uint)input[inputOffset++] + 0x12;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
length += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint gap = outputOffset - position;
|
|
||||||
uint nonOverlappingLength = length;
|
|
||||||
|
|
||||||
if (nonOverlappingLength > gap)
|
|
||||||
{
|
|
||||||
nonOverlappingLength = gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
|
|
||||||
outputOffset += nonOverlappingLength;
|
|
||||||
position += nonOverlappingLength;
|
|
||||||
length -= nonOverlappingLength;
|
|
||||||
|
|
||||||
while (length-- > 0)
|
|
||||||
{
|
|
||||||
output[outputOffset++] = output[position++];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_loadCompleteAction = null;
|
|
||||||
IsActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
@@ -44,15 +45,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private PlayerIndex _playerId;
|
private PlayerIndex _playerId;
|
||||||
private int _controller;
|
private int _controller;
|
||||||
private int _controllerNumber = 0;
|
private int _controllerNumber;
|
||||||
private string _controllerImage;
|
private string _controllerImage;
|
||||||
private int _device;
|
private int _device;
|
||||||
private object _configuration;
|
private object _configuration;
|
||||||
private string _profileName;
|
private string _profileName;
|
||||||
private bool _isLoaded;
|
private bool _isLoaded;
|
||||||
private readonly UserControl _owner;
|
|
||||||
|
|
||||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||||
public IGamepad SelectedGamepad { get; private set; }
|
public IGamepad SelectedGamepad { get; private set; }
|
||||||
@@ -176,11 +176,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
SvgImage image = new SvgImage();
|
SvgImage image = new();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
||||||
{
|
{
|
||||||
SvgSource source = new SvgSource();
|
SvgSource source = new();
|
||||||
|
|
||||||
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
||||||
|
|
||||||
@@ -234,22 +234,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public ControllerInputViewModel(UserControl owner) : this()
|
public ControllerInputViewModel(UserControl owner) : this()
|
||||||
{
|
{
|
||||||
_owner = owner;
|
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
_mainWindow =
|
_mainWindow =
|
||||||
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Avalonia.Application.Current
|
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current
|
||||||
.ApplicationLifetime).MainWindow;
|
.ApplicationLifetime).MainWindow;
|
||||||
|
|
||||||
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
|
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
|
||||||
|
|
||||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||||
if (_mainWindow.ViewModel.AppHost != null)
|
|
||||||
{
|
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
|
||||||
_mainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
|
||||||
@@ -351,7 +347,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (type == DeviceType.Keyboard)
|
|
||||||
|
if (type == DeviceType.Keyboard)
|
||||||
{
|
{
|
||||||
if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver)
|
if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver)
|
||||||
{
|
{
|
||||||
@@ -448,7 +445,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
const string Hyphen = "-";
|
const string Hyphen = "-";
|
||||||
const int Offset = 1;
|
const int Offset = 1;
|
||||||
|
|
||||||
return str.Substring(str.IndexOf(Hyphen) + Offset);
|
return str[(str.IndexOf(Hyphen) + Offset)..];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadDevices()
|
public void LoadDevices()
|
||||||
@@ -562,7 +559,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ButtonL = Key.E,
|
ButtonL = Key.E,
|
||||||
ButtonZl = Key.Q,
|
ButtonZl = Key.Q,
|
||||||
ButtonSl = Key.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = Key.Unbound
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
LeftJoyconStick =
|
LeftJoyconStick =
|
||||||
new JoyconConfigKeyboardStick<Key>
|
new JoyconConfigKeyboardStick<Key>
|
||||||
@@ -571,7 +568,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
StickDown = Key.S,
|
StickDown = Key.S,
|
||||||
StickLeft = Key.A,
|
StickLeft = Key.A,
|
||||||
StickRight = Key.D,
|
StickRight = Key.D,
|
||||||
StickButton = Key.F
|
StickButton = Key.F,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
@@ -583,7 +580,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ButtonR = Key.U,
|
ButtonR = Key.U,
|
||||||
ButtonZr = Key.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = Key.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = Key.Unbound
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
@@ -591,8 +588,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
StickDown = Key.K,
|
StickDown = Key.K,
|
||||||
StickLeft = Key.J,
|
StickLeft = Key.J,
|
||||||
StickRight = Key.L,
|
StickRight = Key.L,
|
||||||
StickButton = Key.H
|
StickButton = Key.H,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (activeDevice.Type == DeviceType.Controller)
|
else if (activeDevice.Type == DeviceType.Controller)
|
||||||
@@ -622,14 +619,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
||||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
ButtonSr = ConfigGamepadInputId.Unbound
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
},
|
},
|
||||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
{
|
{
|
||||||
Joystick = ConfigStickInputId.Left,
|
Joystick = ConfigStickInputId.Left,
|
||||||
StickButton = ConfigGamepadInputId.LeftStick,
|
StickButton = ConfigGamepadInputId.LeftStick,
|
||||||
InvertStickX = false,
|
InvertStickX = false,
|
||||||
InvertStickY = false
|
InvertStickY = false,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||||
{
|
{
|
||||||
@@ -641,28 +638,28 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ButtonR = ConfigGamepadInputId.RightShoulder,
|
ButtonR = ConfigGamepadInputId.RightShoulder,
|
||||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
ButtonSr = ConfigGamepadInputId.Unbound
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
{
|
{
|
||||||
Joystick = ConfigStickInputId.Right,
|
Joystick = ConfigStickInputId.Right,
|
||||||
StickButton = ConfigGamepadInputId.RightStick,
|
StickButton = ConfigGamepadInputId.RightStick,
|
||||||
InvertStickX = false,
|
InvertStickX = false,
|
||||||
InvertStickY = false
|
InvertStickY = false,
|
||||||
},
|
},
|
||||||
Motion = new StandardMotionConfigController
|
Motion = new StandardMotionConfigController
|
||||||
{
|
{
|
||||||
MotionBackend = MotionInputBackendType.GamepadDriver,
|
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||||
EnableMotion = true,
|
EnableMotion = true,
|
||||||
Sensitivity = 100,
|
Sensitivity = 100,
|
||||||
GyroDeadzone = 1
|
GyroDeadzone = 1,
|
||||||
},
|
},
|
||||||
Rumble = new RumbleConfigController
|
Rumble = new RumbleConfigController
|
||||||
{
|
{
|
||||||
StrongRumble = 1f,
|
StrongRumble = 1f,
|
||||||
WeakRumble = 1f,
|
WeakRumble = 1f,
|
||||||
EnableRumble = false
|
EnableRumble = false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -709,7 +706,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
|
||||||
}
|
}
|
||||||
catch (JsonException) { }
|
catch (JsonException) { }
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
@@ -754,8 +751,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||||
|
|
||||||
if (validFileName)
|
if (validFileName)
|
||||||
@@ -775,7 +771,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
config.ControllerType = Controllers[_controller].Type;
|
config.ControllerType = Controllers[_controller].Type;
|
||||||
|
|
||||||
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
|
||||||
|
|
||||||
await File.WriteAllTextAsync(path, jsonString);
|
await File.WriteAllTextAsync(path, jsonString);
|
||||||
|
|
||||||
@@ -786,7 +782,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async void RemoveProfile()
|
public async void RemoveProfile()
|
||||||
{
|
{
|
||||||
@@ -887,6 +882,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
|
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
|
||||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
|
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Application = Avalonia.Application;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
@@ -31,16 +32,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
||||||
private readonly string _downloadableContentJsonPath;
|
private readonly string _downloadableContentJsonPath;
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
|
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
|
||||||
private AvaloniaList<DownloadableContentModel> _views = new();
|
private AvaloniaList<DownloadableContentModel> _views = new();
|
||||||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||||
|
|
||||||
private string _search;
|
private string _search;
|
||||||
private ulong _titleId;
|
private readonly ulong _titleId;
|
||||||
private string _titleName;
|
|
||||||
|
|
||||||
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||||
{
|
{
|
||||||
@@ -90,18 +90,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_titleId = titleId;
|
_titleId = titleId;
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
|
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, _serializerContext.ListDownloadableContentContainer);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -196,19 +195,19 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void Add()
|
public async void Add()
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new OpenFileDialog()
|
OpenFileDialog dialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
||||||
AllowMultiple = true
|
AllowMultiple = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
{
|
{
|
||||||
Name = "NSP",
|
Name = "NSP",
|
||||||
Extensions = { "nsp" }
|
Extensions = { "nsp" },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
@@ -314,7 +313,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
container = new DownloadableContentContainer
|
container = new DownloadableContentContainer
|
||||||
{
|
{
|
||||||
ContainerPath = downloadableContent.ContainerPath,
|
ContainerPath = downloadableContent.ContainerPath,
|
||||||
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
DownloadableContentNcaList = new List<DownloadableContentNca>(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +321,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
Enabled = downloadableContent.Enabled,
|
Enabled = downloadableContent.Enabled,
|
||||||
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
||||||
FullPath = downloadableContent.FullPath
|
FullPath = downloadableContent.FullPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +330,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_downloadableContentContainerList.Add(container);
|
_downloadableContentContainerList.Add(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
|
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@@ -12,6 +13,7 @@ using Ryujinx.Ava.Input;
|
|||||||
using Ryujinx.Ava.UI.Controls;
|
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.Ava.UI.Models.Generic;
|
||||||
using Ryujinx.Ava.UI.Renderer;
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@@ -23,6 +25,7 @@ using Ryujinx.HLE.FileSystem;
|
|||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.HLE.Ui;
|
using Ryujinx.HLE.Ui;
|
||||||
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
@@ -34,7 +37,10 @@ using System.Collections.ObjectModel;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Path = System.IO.Path;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||||
|
using Key = Ryujinx.Input.Key;
|
||||||
|
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
||||||
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
@@ -89,7 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private Cursor _cursor;
|
private Cursor _cursor;
|
||||||
private string _title;
|
private string _title;
|
||||||
private string _currentEmulatedGamePath;
|
private string _currentEmulatedGamePath;
|
||||||
private AutoResetEvent _rendererWaitEvent;
|
private readonly AutoResetEvent _rendererWaitEvent;
|
||||||
private WindowState _windowState;
|
private WindowState _windowState;
|
||||||
private double _windowWidth;
|
private double _windowWidth;
|
||||||
private double _windowHeight;
|
private double _windowHeight;
|
||||||
@@ -128,7 +134,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ApplicationLibrary applicationLibrary,
|
ApplicationLibrary applicationLibrary,
|
||||||
VirtualFileSystem virtualFileSystem,
|
VirtualFileSystem virtualFileSystem,
|
||||||
AccountManager accountManager,
|
AccountManager accountManager,
|
||||||
Ryujinx.Input.HLE.InputManager inputManager,
|
InputManager inputManager,
|
||||||
UserChannelPersistence userChannelPersistence,
|
UserChannelPersistence userChannelPersistence,
|
||||||
LibHacHorizonManager libHacHorizonManager,
|
LibHacHorizonManager libHacHorizonManager,
|
||||||
IHostUiHandler uiHandler,
|
IHostUiHandler uiHandler,
|
||||||
@@ -152,7 +158,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
TopLevel = topLevel;
|
TopLevel = topLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
public string SearchText
|
public string SearchText
|
||||||
{
|
{
|
||||||
@@ -177,7 +183,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool CanUpdate
|
public bool CanUpdate
|
||||||
{
|
{
|
||||||
get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false);
|
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_canUpdate = value;
|
_canUpdate = value;
|
||||||
@@ -343,11 +349,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OpenUserSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||||
|
|
||||||
public bool OpenDeviceSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||||
|
|
||||||
public bool OpenBcatSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
|
||||||
public string LoadHeading
|
public string LoadHeading
|
||||||
{
|
{
|
||||||
@@ -888,7 +894,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public ApplicationLibrary ApplicationLibrary { get; private set; }
|
public ApplicationLibrary ApplicationLibrary { get; private set; }
|
||||||
public VirtualFileSystem VirtualFileSystem { get; private set; }
|
public VirtualFileSystem VirtualFileSystem { get; private set; }
|
||||||
public AccountManager AccountManager { get; private set; }
|
public AccountManager AccountManager { get; private set; }
|
||||||
public Ryujinx.Input.HLE.InputManager InputManager { get; private set; }
|
public InputManager InputManager { get; private set; }
|
||||||
public UserChannelPersistence UserChannelPersistence { get; private set; }
|
public UserChannelPersistence UserChannelPersistence { get; private set; }
|
||||||
public Action<bool> ShowLoading { get; private set; }
|
public Action<bool> ShowLoading { get; private set; }
|
||||||
public Action<bool> SwitchToGameControl { get; private set; }
|
public Action<bool> SwitchToGameControl { get; private set; }
|
||||||
@@ -911,15 +917,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
|
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
|
||||||
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
|
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PrivateMethods
|
#region PrivateMethods
|
||||||
|
|
||||||
private IComparer<ApplicationData> GetComparer()
|
private IComparer<ApplicationData> GetComparer()
|
||||||
{
|
{
|
||||||
return SortMode switch
|
return SortMode switch
|
||||||
{
|
{
|
||||||
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
|
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||||
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
|
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
|
||||||
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
|
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
|
||||||
@@ -935,6 +942,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||||
_ => null,
|
_ => null,
|
||||||
|
#pragma warning restore IDE0055
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,7 +1031,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// Purge Applet Cache.
|
// Purge Applet Cache.
|
||||||
|
|
||||||
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
|
DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
|
||||||
|
|
||||||
if (miiEditorCacheFolder.Exists)
|
if (miiEditorCacheFolder.Exists)
|
||||||
{
|
{
|
||||||
@@ -1044,18 +1052,21 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
RefreshFirmwareStatus();
|
RefreshFirmwareStatus();
|
||||||
}
|
}
|
||||||
}) { Name = "GUI.FirmwareInstallerThread" };
|
})
|
||||||
|
{
|
||||||
|
Name = "GUI.FirmwareInstallerThread",
|
||||||
|
};
|
||||||
|
|
||||||
thread.Start();
|
thread.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (LibHac.Common.Keys.MissingKeyException ex)
|
catch (MissingKeyException ex)
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, ex.ToString());
|
Logger.Error?.Print(LogClass.Application, ex.ToString());
|
||||||
|
|
||||||
async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, (desktop.MainWindow as MainWindow));
|
static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(Action);
|
Dispatcher.UIThread.Post(Action);
|
||||||
}
|
}
|
||||||
@@ -1120,7 +1131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private void PrepareLoadScreen()
|
private void PrepareLoadScreen()
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new(SelectedIcon);
|
using MemoryStream stream = new(SelectedIcon);
|
||||||
using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
|
using var gameIconBmp = Image.Load<Bgra32>(stream);
|
||||||
|
|
||||||
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
|
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
|
||||||
|
|
||||||
@@ -1175,7 +1186,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
Avalonia.Application.Current.Styles.TryGetResource(args.VSyncEnabled
|
Application.Current.Styles.TryGetResource(args.VSyncEnabled
|
||||||
? "VsyncEnabled"
|
? "VsyncEnabled"
|
||||||
: "VsyncDisabled", out object color);
|
: "VsyncDisabled", out object color);
|
||||||
|
|
||||||
@@ -1204,11 +1215,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_rendererWaitEvent.Set();
|
_rendererWaitEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PublicMethods
|
#region PublicMethods
|
||||||
|
|
||||||
public void SetUIProgressHandlers(Switch emulationContext)
|
public void SetUiProgressHandlers(Switch emulationContext)
|
||||||
{
|
{
|
||||||
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||||
{
|
{
|
||||||
@@ -1222,17 +1233,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void LoadConfigurableHotKeys()
|
public void LoadConfigurableHotKeys()
|
||||||
{
|
{
|
||||||
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
|
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
|
||||||
{
|
{
|
||||||
ShowUiKey = new KeyGesture(showUiKey);
|
ShowUiKey = new KeyGesture(showUiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
|
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
|
||||||
{
|
{
|
||||||
ScreenshotKey = new KeyGesture(screenshotKey);
|
ScreenshotKey = new KeyGesture(screenshotKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
|
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
|
||||||
{
|
{
|
||||||
PauseKey = new KeyGesture(pauseKey);
|
PauseKey = new KeyGesture(pauseKey);
|
||||||
}
|
}
|
||||||
@@ -1260,7 +1271,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void InstallFirmwareFromFile()
|
public async void InstallFirmwareFromFile()
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new() { AllowMultiple = false };
|
OpenFileDialog dialog = new() { AllowMultiple = false };
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance[LocaleKeys.FileDialogAllTypes], Extensions = { "xci", "zip" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance[LocaleKeys.FileDialogAllTypes], Extensions = { "xci", "zip" } });
|
||||||
@@ -1278,7 +1289,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void InstallFirmwareFromFolder()
|
public async void InstallFirmwareFromFolder()
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
OpenFolderDialog dialog = new();
|
OpenFolderDialog dialog = new();
|
||||||
|
|
||||||
@@ -1327,7 +1338,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeLanguage(object languageCode)
|
public static void ChangeLanguage(object languageCode)
|
||||||
{
|
{
|
||||||
LocaleManager.Instance.LoadLanguage((string)languageCode);
|
LocaleManager.Instance.LoadLanguage((string)languageCode);
|
||||||
|
|
||||||
@@ -1342,6 +1353,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
_ = fileType switch
|
_ = fileType switch
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
|
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
|
||||||
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
|
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
|
||||||
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
|
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
|
||||||
@@ -1349,6 +1361,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
|
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
|
||||||
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
|
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
|
||||||
_ => throw new ArgumentOutOfRangeException(fileType),
|
_ => throw new ArgumentOutOfRangeException(fileType),
|
||||||
|
#pragma warning restore IDE0055
|
||||||
};
|
};
|
||||||
|
|
||||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
@@ -1383,11 +1396,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void OpenFile()
|
public async void OpenFile()
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new()
|
OpenFileDialog dialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle]
|
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
@@ -1400,16 +1413,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
"xci",
|
"xci",
|
||||||
"nca",
|
"nca",
|
||||||
"nro",
|
"nro",
|
||||||
"nso"
|
"nso",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
|
||||||
|
#pragma warning restore IDE0055
|
||||||
|
|
||||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
@@ -1422,11 +1437,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void OpenFolder()
|
public async void OpenFolder()
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
OpenFolderDialog dialog = new()
|
OpenFolderDialog dialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle]
|
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
|
||||||
};
|
};
|
||||||
|
|
||||||
string folder = await dialog.ShowAsync(desktop.MainWindow);
|
string folder = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
@@ -1458,10 +1473,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Logger.RestartTime();
|
Logger.RestartTime();
|
||||||
|
|
||||||
if (SelectedIcon == null)
|
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
|
||||||
{
|
|
||||||
SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
@@ -1521,7 +1533,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateGameMetadata(string titleId)
|
public static void UpdateGameMetadata(string titleId)
|
||||||
{
|
{
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||||
{
|
{
|
||||||
@@ -1675,6 +1687,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ using Ryujinx.HLE.FileSystem;
|
|||||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Configuration.System;
|
using Ryujinx.Ui.Common.Configuration.System;
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -248,7 +247,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public AvaloniaList<string> NetworkInterfaceList
|
public AvaloniaList<string> NetworkInterfaceList
|
||||||
{
|
{
|
||||||
get => new AvaloniaList<string>(_networkInterfaces.Keys);
|
get => new(_networkInterfaces.Keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyboardHotkeys KeyboardHotkeys
|
public KeyboardHotkeys KeyboardHotkeys
|
||||||
@@ -561,7 +560,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_directoryChanged = false;
|
_directoryChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RevertIfNotSaved()
|
private static void RevertIfNotSaved()
|
||||||
{
|
{
|
||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
@@ -16,7 +17,6 @@ using Ryujinx.Common.Configuration;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -29,17 +29,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
public class TitleUpdateViewModel : BaseModel
|
public class TitleUpdateViewModel : BaseModel
|
||||||
{
|
{
|
||||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
public TitleUpdateMetadata TitleUpdateWindowData;
|
||||||
public readonly string _titleUpdateJsonPath;
|
public readonly string TitleUpdateJsonPath;
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
private VirtualFileSystem VirtualFileSystem { get; }
|
||||||
private ulong _titleId { get; }
|
private ulong TitleId { get; }
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||||
private AvaloniaList<object> _views = new();
|
private AvaloniaList<object> _views = new();
|
||||||
private object _selectedUpdate;
|
private object _selectedUpdate;
|
||||||
|
|
||||||
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||||
{
|
{
|
||||||
@@ -71,27 +70,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_titleId = titleId;
|
TitleId = titleId;
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
TitleUpdateWindowData = JsonHelper.DeserializeFromFile(TitleUpdateJsonPath, _serializerContext.TitleUpdateMetadata);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
|
||||||
|
|
||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
TitleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
Selected = "",
|
Selected = "",
|
||||||
Paths = new List<string>()
|
Paths = new List<string>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Save();
|
Save();
|
||||||
@@ -102,12 +100,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void LoadUpdates()
|
private void LoadUpdates()
|
||||||
{
|
{
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
foreach (string path in TitleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
AddUpdate(path);
|
AddUpdate(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == TitleUpdateWindowData.Selected, null);
|
||||||
|
|
||||||
SelectedUpdate = selected;
|
SelectedUpdate = selected;
|
||||||
|
|
||||||
@@ -126,7 +124,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
|
||||||
|
if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -163,7 +162,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
if (controlNca != null && patchNca != null)
|
||||||
{
|
{
|
||||||
@@ -206,16 +205,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OpenFileDialog dialog = new()
|
OpenFileDialog dialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||||
AllowMultiple = true
|
AllowMultiple = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
{
|
{
|
||||||
Name = "NSP",
|
Name = "NSP",
|
||||||
Extensions = { "nsp" }
|
Extensions = { "nsp" },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
@@ -233,20 +232,20 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
TitleUpdateWindowData.Paths.Clear();
|
||||||
_titleUpdateWindowData.Selected = "";
|
TitleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in TitleUpdates)
|
foreach (TitleUpdateModel update in TitleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
TitleUpdateWindowData.Paths.Add(update.Path);
|
||||||
|
|
||||||
if (update == SelectedUpdate)
|
if (update == SelectedUpdate)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Selected = update.Path;
|
TitleUpdateWindowData.Selected = update.Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
JsonHelper.SerializeToFile(TitleUpdateJsonPath, TitleUpdateWindowData, _serializerContext.TitleUpdateMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -28,7 +28,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private Color _backgroundColor = Colors.White;
|
private Color _backgroundColor = Colors.White;
|
||||||
|
|
||||||
private int _selectedIndex;
|
private int _selectedIndex;
|
||||||
private byte[] _selectedImage;
|
|
||||||
|
|
||||||
public UserFirmwareAvatarSelectorViewModel()
|
public UserFirmwareAvatarSelectorViewModel()
|
||||||
{
|
{
|
||||||
@@ -78,11 +77,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] SelectedImage
|
public byte[] SelectedImage { get; private set; }
|
||||||
{
|
|
||||||
get => _selectedImage;
|
|
||||||
private set => _selectedImage = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadImagesFromStore()
|
private void LoadImagesFromStore()
|
||||||
{
|
{
|
||||||
@@ -114,8 +109,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(avatarPath))
|
if (!string.IsNullOrWhiteSpace(avatarPath))
|
||||||
{
|
{
|
||||||
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
|
using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open);
|
||||||
{
|
|
||||||
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
||||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
@@ -128,9 +123,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using (MemoryStream stream = new())
|
using MemoryStream stream = new();
|
||||||
using (MemoryStream streamPng = new())
|
using MemoryStream streamPng = new();
|
||||||
{
|
|
||||||
file.Get.AsStream().CopyTo(stream);
|
file.Get.AsStream().CopyTo(stream);
|
||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
@@ -144,13 +139,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] DecompressYaz0(Stream stream)
|
private static byte[] DecompressYaz0(Stream stream)
|
||||||
{
|
{
|
||||||
using (BinaryReader reader = new(stream))
|
using BinaryReader reader = new(stream);
|
||||||
{
|
|
||||||
reader.ReadInt32(); // Magic
|
reader.ReadInt32(); // Magic
|
||||||
|
|
||||||
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
||||||
@@ -226,5 +219,4 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@@ -20,6 +20,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsEmpty { get; set; }
|
public bool IsEmpty { get; set; }
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private string _search;
|
private string _search;
|
||||||
private ObservableCollection<SaveModel> _saves = new();
|
private ObservableCollection<SaveModel> _saves = new();
|
||||||
private ObservableCollection<SaveModel> _views = new();
|
private ObservableCollection<SaveModel> _views = new();
|
||||||
private AccountManager _accountManager;
|
private readonly AccountManager _accountManager;
|
||||||
|
|
||||||
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
||||||
|
|
||||||
@@ -102,19 +102,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private IComparer<SaveModel> GetComparer()
|
private IComparer<SaveModel> GetComparer()
|
||||||
{
|
{
|
||||||
switch (SortIndex)
|
return SortIndex switch
|
||||||
{
|
{
|
||||||
case 0:
|
0 => OrderIndex == 0
|
||||||
return OrderIndex == 0
|
|
||||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
: SortExpressionComparer<SaveModel>.Descending(save => save.Title),
|
||||||
case 1:
|
1 => OrderIndex == 0
|
||||||
return OrderIndex == 0
|
|
||||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
: SortExpressionComparer<SaveModel>.Descending(save => save.Size),
|
||||||
default:
|
_ => null,
|
||||||
return null;
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -29,7 +29,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
|
|
||||||
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
|
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
|
||||||
{
|
{
|
||||||
if (visual is ToggleButton button && !(visual is CheckBox))
|
if (visual is ToggleButton button && visual is not CheckBox)
|
||||||
{
|
{
|
||||||
button.Checked += Button_Checked;
|
button.Checked += Button_Checked;
|
||||||
button.Unchecked += Button_Unchecked;
|
button.Unchecked += Button_Unchecked;
|
||||||
|
@@ -10,7 +10,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
{
|
{
|
||||||
public partial class MotionInputView : UserControl
|
public partial class MotionInputView : UserControl
|
||||||
{
|
{
|
||||||
private MotionInputViewModel _viewModel;
|
private readonly MotionInputViewModel _viewModel;
|
||||||
|
|
||||||
public MotionInputView()
|
public MotionInputView()
|
||||||
{
|
{
|
||||||
@@ -30,7 +30,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
MirrorInput = config.MirrorInput,
|
MirrorInput = config.MirrorInput,
|
||||||
Sensitivity = config.Sensitivity,
|
Sensitivity = config.Sensitivity,
|
||||||
GyroDeadzone = config.GyroDeadzone,
|
GyroDeadzone = config.GyroDeadzone,
|
||||||
EnableCemuHookMotion = config.EnableCemuHookMotion
|
EnableCemuHookMotion = config.EnableCemuHookMotion,
|
||||||
};
|
};
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -47,7 +47,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
|
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
|
||||||
Content = content
|
Content = content,
|
||||||
};
|
};
|
||||||
contentDialog.PrimaryButtonClick += (sender, args) =>
|
contentDialog.PrimaryButtonClick += (sender, args) =>
|
||||||
{
|
{
|
||||||
|
@@ -10,7 +10,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
{
|
{
|
||||||
public partial class RumbleInputView : UserControl
|
public partial class RumbleInputView : UserControl
|
||||||
{
|
{
|
||||||
private RumbleInputViewModel _viewModel;
|
private readonly RumbleInputViewModel _viewModel;
|
||||||
|
|
||||||
public RumbleInputView()
|
public RumbleInputView()
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
_viewModel = new RumbleInputViewModel
|
_viewModel = new RumbleInputViewModel
|
||||||
{
|
{
|
||||||
StrongRumble = config.StrongRumble,
|
StrongRumble = config.StrongRumble,
|
||||||
WeakRumble = config.WeakRumble
|
WeakRumble = config.WeakRumble,
|
||||||
};
|
};
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@@ -38,21 +38,21 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
{
|
{
|
||||||
List<CheckBox> checkBoxes = new();
|
List<CheckBox> checkBoxes = new();
|
||||||
|
|
||||||
foreach (var item in Enum.GetValues(typeof (FileTypes)))
|
foreach (var item in Enum.GetValues(typeof(FileTypes)))
|
||||||
{
|
{
|
||||||
string fileName = Enum.GetName(typeof (FileTypes), item);
|
string fileName = Enum.GetName(typeof(FileTypes), item);
|
||||||
checkBoxes.Add(new CheckBox()
|
checkBoxes.Add(new CheckBox
|
||||||
{
|
{
|
||||||
Content = $".{fileName}",
|
Content = $".{fileName}",
|
||||||
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
|
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
|
||||||
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName))
|
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkBoxes.ToArray();
|
return checkBoxes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem[] GenerateLanguageMenuItems()
|
private static MenuItem[] GenerateLanguageMenuItems()
|
||||||
{
|
{
|
||||||
List<MenuItem> menuItems = new();
|
List<MenuItem> menuItems = new();
|
||||||
|
|
||||||
@@ -79,8 +79,8 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
Header = languageName,
|
Header = languageName,
|
||||||
Command = MiniCommand.Create(() =>
|
Command = MiniCommand.Create(() =>
|
||||||
{
|
{
|
||||||
ViewModel.ChangeLanguage(languageCode);
|
MainWindowViewModel.ChangeLanguage(languageCode);
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
menuItems.Add(menuItem);
|
menuItems.Add(menuItem);
|
||||||
|
@@ -12,12 +12,12 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
public partial class SettingsHotkeysView : UserControl
|
public partial class SettingsHotkeysView : UserControl
|
||||||
{
|
{
|
||||||
private ButtonKeyAssigner _currentAssigner;
|
private ButtonKeyAssigner _currentAssigner;
|
||||||
private IGamepadDriver AvaloniaKeyboardDriver;
|
private readonly IGamepadDriver _avaloniaKeyboardDriver;
|
||||||
|
|
||||||
public SettingsHotkeysView()
|
public SettingsHotkeysView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
|
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||||
@@ -46,7 +46,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
|
|
||||||
PointerPressed += MouseClick;
|
PointerPressed += MouseClick;
|
||||||
|
|
||||||
var keyboard = (IKeyboard)AvaloniaKeyboardDriver.GetGamepad(AvaloniaKeyboardDriver.GamepadsIds[0]);
|
var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad(_avaloniaKeyboardDriver.GamepadsIds[0]);
|
||||||
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
||||||
|
|
||||||
_currentAssigner.GetInputAndAssign(assigner);
|
_currentAssigner.GetInputAndAssign(assigner);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUIView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUiView"
|
||||||
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"
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
@@ -9,11 +10,11 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.Settings
|
namespace Ryujinx.Ava.UI.Views.Settings
|
||||||
{
|
{
|
||||||
public partial class SettingsUIView : UserControl
|
public partial class SettingsUiView : UserControl
|
||||||
{
|
{
|
||||||
public SettingsViewModel ViewModel;
|
public SettingsViewModel ViewModel;
|
||||||
|
|
||||||
public SettingsUIView()
|
public SettingsUiView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -29,7 +30,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
path = await new OpenFolderDialog().ShowAsync(desktop.MainWindow);
|
path = await new OpenFolderDialog().ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
@@ -60,15 +61,15 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
|
|
||||||
public async void BrowseTheme(object sender, RoutedEventArgs e)
|
public async void BrowseTheme(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var dialog = new OpenFileDialog()
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
|
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
|
||||||
AllowMultiple = false
|
AllowMultiple = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter() { Extensions = { "xaml" }, Name = LocaleManager.Instance[LocaleKeys.SettingsXamlThemeFile] });
|
dialog.Filters.Add(new FileDialogFilter { Extensions = { "xaml" }, Name = LocaleManager.Instance[LocaleKeys.SettingsXamlThemeFile] });
|
||||||
|
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
var file = await dialog.ShowAsync(desktop.MainWindow);
|
var file = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
private bool _isNewUser;
|
private bool _isNewUser;
|
||||||
|
|
||||||
public TempProfile TempProfile { get; set; }
|
public TempProfile TempProfile { get; set; }
|
||||||
public uint MaxProfileNameLength => 0x20;
|
public static uint MaxProfileNameLength => 0x20;
|
||||||
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
|
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
|
||||||
|
|
||||||
public UserEditorView()
|
public UserEditorView()
|
||||||
@@ -39,17 +39,17 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
switch (arg.NavigationMode)
|
switch (arg.NavigationMode)
|
||||||
{
|
{
|
||||||
case NavigationMode.New:
|
case NavigationMode.New:
|
||||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
var (parent, profile, isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||||
_isNewUser = args.isNewUser;
|
_isNewUser = isNewUser;
|
||||||
_profile = args.profile;
|
_profile = profile;
|
||||||
TempProfile = new TempProfile(_profile);
|
TempProfile = new TempProfile(_profile);
|
||||||
|
|
||||||
_parent = args.parent;
|
_parent = parent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
|
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
|
||||||
$"{ (_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
|
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
|
||||||
|
|
||||||
DataContext = TempProfile;
|
DataContext = TempProfile;
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Png;
|
|||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.User
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
@@ -70,7 +71,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
if (ViewModel.SelectedImage != null)
|
if (ViewModel.SelectedImage != null)
|
||||||
{
|
{
|
||||||
MemoryStream streamJpg = new();
|
MemoryStream streamJpg = new();
|
||||||
SixLabors.ImageSharp.Image avatarImage = SixLabors.ImageSharp.Image.Load(ViewModel.SelectedImage, new PngDecoder());
|
Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder());
|
||||||
|
|
||||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
||||||
ViewModel.BackgroundColor.R,
|
ViewModel.BackgroundColor.R,
|
||||||
|
@@ -67,7 +67,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
dialog.Filters.Add(new FileDialogFilter
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
{
|
{
|
||||||
Name = LocaleManager.Instance[LocaleKeys.AllSupportedFormats],
|
Name = LocaleManager.Instance[LocaleKeys.AllSupportedFormats],
|
||||||
Extensions = { "jpg", "jpeg", "png", "bmp" }
|
Extensions = { "jpg", "jpeg", "png", "bmp" },
|
||||||
});
|
});
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } });
|
||||||
@@ -108,17 +108,15 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
|
|
||||||
private static byte[] ProcessProfileImage(byte[] buffer)
|
private static byte[] ProcessProfileImage(byte[] buffer)
|
||||||
{
|
{
|
||||||
using (Image image = Image.Load(buffer))
|
using Image image = Image.Load(buffer);
|
||||||
{
|
|
||||||
image.Mutate(x => x.Resize(256, 256));
|
image.Mutate(x => x.Resize(256, 256));
|
||||||
|
|
||||||
using (MemoryStream streamJpg = new())
|
using MemoryStream streamJpg = new();
|
||||||
{
|
|
||||||
image.SaveAsJpeg(streamJpg);
|
image.SaveAsJpeg(streamJpg);
|
||||||
|
|
||||||
return streamJpg.ToArray();
|
return streamJpg.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -18,6 +18,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Button = Avalonia.Controls.Button;
|
||||||
using UserId = LibHac.Fs.UserId;
|
using UserId = LibHac.Fs.UserId;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.User
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
@@ -47,12 +48,12 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
switch (arg.NavigationMode)
|
switch (arg.NavigationMode)
|
||||||
{
|
{
|
||||||
case NavigationMode.New:
|
case NavigationMode.New:
|
||||||
var args = ((NavigationDialogHost parent, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter;
|
var (parent, accountManager, client, virtualFileSystem) = ((NavigationDialogHost parent, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter;
|
||||||
_accountManager = args.accountManager;
|
_accountManager = accountManager;
|
||||||
_horizonClient = args.client;
|
_horizonClient = client;
|
||||||
_virtualFileSystem = args.virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_parent = args.parent;
|
_parent = parent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
var save = saveDataInfo[i];
|
var save = saveDataInfo[i];
|
||||||
if (save.ProgramId.Value != 0)
|
if (save.ProgramId.Value != 0)
|
||||||
{
|
{
|
||||||
var saveModel = new SaveModel(save, _virtualFileSystem);
|
var saveModel = new SaveModel(save);
|
||||||
saves.Add(saveModel);
|
saves.Add(saveModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
|
|
||||||
private void OpenLocation(object sender, RoutedEventArgs e)
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Avalonia.Controls.Button button)
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
if (button.DataContext is SaveModel saveModel)
|
if (button.DataContext is SaveModel saveModel)
|
||||||
{
|
{
|
||||||
@@ -125,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
|
|
||||||
private async void Delete(object sender, RoutedEventArgs e)
|
private async void Delete(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Avalonia.Controls.Button button)
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
if (button.DataContext is SaveModel saveModel)
|
if (button.DataContext is SaveModel saveModel)
|
||||||
{
|
{
|
||||||
|
@@ -5,8 +5,9 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using FluentAvalonia.UI.Navigation;
|
using FluentAvalonia.UI.Navigation;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.User
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
@@ -101,7 +102,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
|
|
||||||
private void EditUser(object sender, RoutedEventArgs e)
|
private void EditUser(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Avalonia.Controls.Button button)
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
if (button.DataContext is UserProfile userProfile)
|
if (button.DataContext is UserProfile userProfile)
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Layout;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
@@ -28,14 +29,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
||||||
Content = new AboutWindow()
|
Content = new AboutWindow(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style closeButton = new(x => x.Name("CloseButton"));
|
Style closeButton = new(x => x.Name("CloseButton"));
|
||||||
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
|
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
|
||||||
|
|
||||||
Style closeButtonParent = new(x => x.Name("CommandSpace"));
|
Style closeButtonParent = new(x => x.Name("CommandSpace"));
|
||||||
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, Avalonia.Layout.HorizontalAlignment.Right));
|
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Right));
|
||||||
|
|
||||||
contentDialog.Styles.Add(closeButton);
|
contentDialog.Styles.Add(closeButton);
|
||||||
contentDialog.Styles.Add(closeButtonParent);
|
contentDialog.Styles.Add(closeButtonParent);
|
||||||
|
@@ -9,9 +9,10 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
|
public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
|
||||||
{
|
{
|
||||||
ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId);
|
ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId)
|
||||||
|
{
|
||||||
ViewModel.ShowAllAmiibo = showAll;
|
ShowAllAmiibo = showAll,
|
||||||
|
};
|
||||||
|
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
using Avalonia;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Collections;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -41,11 +42,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId);
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId);
|
||||||
ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber);
|
ulong titleIdValue = ulong.Parse(titleId, NumberStyles.HexNumber);
|
||||||
|
|
||||||
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||||
|
|
||||||
string[] enabled = { };
|
string[] enabled = Array.Empty<string>();
|
||||||
|
|
||||||
if (File.Exists(_enabledCheatsPath))
|
if (File.Exists(_enabledCheatsPath))
|
||||||
{
|
{
|
||||||
@@ -60,7 +61,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
string currentCheatFile = string.Empty;
|
string currentCheatFile = string.Empty;
|
||||||
string buildId = string.Empty;
|
string buildId = string.Empty;
|
||||||
string parentPath = string.Empty;
|
|
||||||
|
|
||||||
CheatsList currentGroup = null;
|
CheatsList currentGroup = null;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
if (cheat.Path.FullName != currentCheatFile)
|
if (cheat.Path.FullName != currentCheatFile)
|
||||||
{
|
{
|
||||||
currentCheatFile = cheat.Path.FullName;
|
currentCheatFile = cheat.Path.FullName;
|
||||||
parentPath = currentCheatFile.Replace(titleModsPath, "");
|
string parentPath = currentCheatFile.Replace(titleModsPath, "");
|
||||||
|
|
||||||
buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper();
|
buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper();
|
||||||
currentGroup = new CheatsList(buildId, parentPath);
|
currentGroup = new CheatsList(buildId, parentPath);
|
||||||
@@ -100,7 +100,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> enabledCheats = new List<string>();
|
List<string> enabledCheats = new();
|
||||||
|
|
||||||
foreach (var cheats in LoadedCheats)
|
foreach (var cheats in LoadedCheats)
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
#if DEBUG
|
||||||
|
using Avalonia;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
|
@@ -89,8 +89,8 @@
|
|||||||
<Grid
|
<Grid
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
@@ -24,9 +24,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId, titleName);
|
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
|
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
|
||||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
@@ -68,8 +68,10 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
int w8 = w << 8;
|
int w8 = w << 8;
|
||||||
int h8 = image.Height << 8;
|
int h8 = image.Height << 8;
|
||||||
|
|
||||||
|
#pragma warning disable IDE0059 // Unnecessary assignment
|
||||||
int xStep = w8 / ColorsPerLine;
|
int xStep = w8 / ColorsPerLine;
|
||||||
int yStep = h8 / ColorsPerLine;
|
int yStep = h8 / ColorsPerLine;
|
||||||
|
#pragma warning restore IDE0059
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int maxHitCount = 0;
|
int maxHitCount = 0;
|
||||||
|
@@ -323,7 +323,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ShowKeyErrorOnLoad = false;
|
ShowKeyErrorOnLoad = false;
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () => await
|
Dispatcher.UIThread.Post(async () => await
|
||||||
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
|
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
||||||
@@ -373,7 +373,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
private void SetWindowSizePosition()
|
private void SetWindowSizePosition()
|
||||||
{
|
{
|
||||||
PixelPoint SavedPoint = new PixelPoint(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
|
PixelPoint savedPoint = new(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
|
||||||
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
|
||||||
|
|
||||||
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
|
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
|
||||||
@@ -381,12 +381,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
|
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
|
||||||
|
|
||||||
if (CheckScreenBounds(SavedPoint))
|
if (CheckScreenBounds(savedPoint))
|
||||||
{
|
{
|
||||||
Position = SavedPoint;
|
Position = savedPoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
else WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckScreenBounds(PixelPoint configPoint)
|
private bool CheckScreenBounds(PixelPoint configPoint)
|
||||||
@@ -399,7 +401,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
|
Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,10 +427,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
private void SetMainContent(Control content = null)
|
private void SetMainContent(Control content = null)
|
||||||
{
|
{
|
||||||
if (content == null)
|
content ??= GameLibrary;
|
||||||
{
|
|
||||||
content = GameLibrary;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MainContent.Content != content)
|
if (MainContent.Content != content)
|
||||||
{
|
{
|
||||||
@@ -438,21 +437,25 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public static void UpdateGraphicsConfig()
|
public static void UpdateGraphicsConfig()
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
|
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
|
||||||
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||||
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||||
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
||||||
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
||||||
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
||||||
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadHotKeys()
|
public void LoadHotKeys()
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
||||||
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
||||||
HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
|
HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
|
||||||
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
||||||
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
||||||
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
|
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
KeyboardNavigation.IsTabStop="False"/>
|
KeyboardNavigation.IsTabStop="False"/>
|
||||||
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
||||||
<settings:SettingsUIView Name="UiPage" />
|
<settings:SettingsUiView Name="UiPage" />
|
||||||
<settings:SettingsInputView Name="InputPage" />
|
<settings:SettingsInputView Name="InputPage" />
|
||||||
<settings:SettingsHotkeysView Name="HotkeysPage" />
|
<settings:SettingsHotkeysView Name="HotkeysPage" />
|
||||||
<settings:SettingsSystemView Name="SystemPage" />
|
<settings:SettingsSystemView Name="SystemPage" />
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -17,7 +18,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
TransparencyLevelHint = WindowTransparencyLevel.None;
|
TransparencyLevelHint = WindowTransparencyLevel.None;
|
||||||
|
|
||||||
using Stream stream = Assembly.GetAssembly(typeof(Ryujinx.Ui.Common.Configuration.ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
||||||
|
|
||||||
Icon = new WindowIcon(stream);
|
Icon = new WindowIcon(stream);
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
@@ -24,9 +24,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId, titleName);
|
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
|
Content = new TitleUpdateWindow(virtualFileSystem, titleId),
|
||||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16"))
|
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5311;
|
private const uint CodeGenVersion = 5266;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
public readonly byte Slot;
|
public readonly byte Slot;
|
||||||
public readonly byte SbCbSlot;
|
public readonly byte SbCbSlot;
|
||||||
public readonly ushort SbCbOffset;
|
public readonly ushort SbCbOffset;
|
||||||
public BufferUsageFlags Flags;
|
public readonly BufferUsageFlags Flags;
|
||||||
|
|
||||||
public BufferDescriptor(int binding, int slot)
|
public BufferDescriptor(int binding, int slot)
|
||||||
{
|
{
|
||||||
@@ -16,25 +16,16 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
Slot = (byte)slot;
|
Slot = (byte)slot;
|
||||||
SbCbSlot = 0;
|
SbCbSlot = 0;
|
||||||
SbCbOffset = 0;
|
SbCbOffset = 0;
|
||||||
|
|
||||||
Flags = BufferUsageFlags.None;
|
Flags = BufferUsageFlags.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset)
|
public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
|
||||||
{
|
{
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
Slot = (byte)slot;
|
Slot = (byte)slot;
|
||||||
SbCbSlot = (byte)sbCbSlot;
|
SbCbSlot = (byte)sbCbSlot;
|
||||||
SbCbOffset = (ushort)sbCbOffset;
|
SbCbOffset = (ushort)sbCbOffset;
|
||||||
|
Flags = flags;
|
||||||
Flags = BufferUsageFlags.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferDescriptor SetFlag(BufferUsageFlags flag)
|
|
||||||
{
|
|
||||||
Flags |= flag;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -75,22 +75,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
||||||
DeclareMemories(context, context.Config.Properties.LocalMemories.Values, isShared: false);
|
DeclareMemories(context, context.Config.Properties.LocalMemories.Values, isShared: false);
|
||||||
DeclareMemories(context, context.Config.Properties.SharedMemories.Values, isShared: true);
|
DeclareMemories(context, context.Config.Properties.SharedMemories.Values, isShared: true);
|
||||||
|
DeclareSamplers(context, context.Config.Properties.Textures.Values);
|
||||||
var textureDescriptors = context.Config.GetTextureDescriptors();
|
DeclareImages(context, context.Config.Properties.Images.Values);
|
||||||
if (textureDescriptors.Length != 0)
|
|
||||||
{
|
|
||||||
DeclareSamplers(context, textureDescriptors);
|
|
||||||
|
|
||||||
context.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageDescriptors = context.Config.GetImageDescriptors();
|
|
||||||
if (imageDescriptors.Length != 0)
|
|
||||||
{
|
|
||||||
DeclareImages(context, imageDescriptors);
|
|
||||||
|
|
||||||
context.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.Config.Stage != ShaderStage.Compute)
|
if (context.Config.Stage != ShaderStage.Compute)
|
||||||
{
|
{
|
||||||
@@ -369,80 +355,71 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
||||||
{
|
{
|
||||||
int arraySize = 0;
|
int arraySize = 0;
|
||||||
foreach (var descriptor in descriptors)
|
|
||||||
|
foreach (var definition in definitions)
|
||||||
{
|
{
|
||||||
if (descriptor.Type.HasFlag(SamplerType.Indexed))
|
string indexExpr = string.Empty;
|
||||||
|
|
||||||
|
if (definition.Type.HasFlag(SamplerType.Indexed))
|
||||||
{
|
{
|
||||||
if (arraySize == 0)
|
if (arraySize == 0)
|
||||||
{
|
{
|
||||||
arraySize = ShaderConfig.SamplerArraySize;
|
arraySize = ResourceManager.SamplerArraySize;
|
||||||
}
|
}
|
||||||
else if (--arraySize != 0)
|
else if (--arraySize != 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
string indexExpr = NumberFormatter.FormatInt(arraySize);
|
string samplerTypeName = definition.Type.ToGlslSamplerType();
|
||||||
|
|
||||||
string samplerName = OperandManager.GetSamplerName(
|
|
||||||
context.Config.Stage,
|
|
||||||
descriptor.CbufSlot,
|
|
||||||
descriptor.HandleIndex,
|
|
||||||
descriptor.Type.HasFlag(SamplerType.Indexed),
|
|
||||||
indexExpr);
|
|
||||||
|
|
||||||
string samplerTypeName = descriptor.Type.ToGlslSamplerType();
|
|
||||||
|
|
||||||
string layout = string.Empty;
|
string layout = string.Empty;
|
||||||
|
|
||||||
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
||||||
{
|
{
|
||||||
layout = ", set = 2";
|
layout = $", set = {definition.Set}";
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AppendLine($"layout (binding = {descriptor.Binding}{layout}) uniform {samplerTypeName} {samplerName};");
|
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareImages(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
||||||
{
|
{
|
||||||
int arraySize = 0;
|
int arraySize = 0;
|
||||||
foreach (var descriptor in descriptors)
|
|
||||||
|
foreach (var definition in definitions)
|
||||||
{
|
{
|
||||||
if (descriptor.Type.HasFlag(SamplerType.Indexed))
|
string indexExpr = string.Empty;
|
||||||
|
|
||||||
|
if (definition.Type.HasFlag(SamplerType.Indexed))
|
||||||
{
|
{
|
||||||
if (arraySize == 0)
|
if (arraySize == 0)
|
||||||
{
|
{
|
||||||
arraySize = ShaderConfig.SamplerArraySize;
|
arraySize = ResourceManager.SamplerArraySize;
|
||||||
}
|
}
|
||||||
else if (--arraySize != 0)
|
else if (--arraySize != 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
string indexExpr = NumberFormatter.FormatInt(arraySize);
|
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
|
||||||
|
|
||||||
string imageName = OperandManager.GetImageName(
|
if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
||||||
context.Config.Stage,
|
|
||||||
descriptor.CbufSlot,
|
|
||||||
descriptor.HandleIndex,
|
|
||||||
descriptor.Format,
|
|
||||||
descriptor.Type.HasFlag(SamplerType.Indexed),
|
|
||||||
indexExpr);
|
|
||||||
|
|
||||||
string imageTypeName = descriptor.Type.ToGlslImageType(descriptor.Format.GetComponentType());
|
|
||||||
|
|
||||||
if (descriptor.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
|
||||||
{
|
{
|
||||||
imageTypeName = "coherent " + imageTypeName;
|
imageTypeName = "coherent " + imageTypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
string layout = descriptor.Format.ToGlslFormat();
|
string layout = definition.Format.ToGlslFormat();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(layout))
|
if (!string.IsNullOrEmpty(layout))
|
||||||
{
|
{
|
||||||
@@ -451,10 +428,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
|
|
||||||
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
||||||
{
|
{
|
||||||
layout = $", set = 3{layout}";
|
layout = $", set = {definition.Set}{layout}";
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AppendLine($"layout (binding = {descriptor.Binding}{layout}) uniform {imageTypeName} {imageName};");
|
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,9 +4,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
public const string LocalNamePrefix = "temp";
|
public const string LocalNamePrefix = "temp";
|
||||||
|
|
||||||
public const string SamplerNamePrefix = "tex";
|
|
||||||
public const string ImageNamePrefix = "img";
|
|
||||||
|
|
||||||
public const string PerPatchAttributePrefix = "patch_attr_";
|
public const string PerPatchAttributePrefix = "patch_attr_";
|
||||||
public const string IAttributePrefix = "in_attr";
|
public const string IAttributePrefix = "in_attr";
|
||||||
public const string OAttributePrefix = "out_attr";
|
public const string OAttributePrefix = "out_attr";
|
||||||
|
@@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
indexExpr = Src(AggregateType.S32);
|
indexExpr = Src(AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);
|
string imageName = GetImageName(context.Config, texOp, indexExpr);
|
||||||
|
|
||||||
texCallBuilder.Append('(');
|
texCallBuilder.Append('(');
|
||||||
texCallBuilder.Append(imageName);
|
texCallBuilder.Append(imageName);
|
||||||
@@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
|
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
|
||||||
|
|
||||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||||
|
|
||||||
@@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
indexExpr = Src(AggregateType.S32);
|
indexExpr = Src(AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
|
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
|
||||||
|
|
||||||
texCall += "(" + samplerName;
|
texCall += "(" + samplerName;
|
||||||
|
|
||||||
@@ -538,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
|
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
|
||||||
|
|
||||||
if (texOp.Index == 3)
|
if (texOp.Index == 3)
|
||||||
{
|
{
|
||||||
@@ -546,8 +546,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TextureDescriptor descriptor = context.Config.FindTextureDescriptor(texOp);
|
context.Config.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
|
||||||
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
|
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
|
||||||
string texCall;
|
string texCall;
|
||||||
|
|
||||||
if (hasLod)
|
if (hasLod)
|
||||||
@@ -715,6 +715,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
return varName;
|
return varName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetSamplerName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
|
||||||
|
{
|
||||||
|
string name = config.Properties.Textures[texOp.Binding].Name;
|
||||||
|
|
||||||
|
if (texOp.Type.HasFlag(SamplerType.Indexed))
|
||||||
|
{
|
||||||
|
name = $"{name}[{indexExpr}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetImageName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
|
||||||
|
{
|
||||||
|
string name = config.Properties.Images[texOp.Binding].Name;
|
||||||
|
|
||||||
|
if (texOp.Type.HasFlag(SamplerType.Indexed))
|
||||||
|
{
|
||||||
|
name = $"{name}[{indexExpr}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetMask(int index)
|
private static string GetMask(int index)
|
||||||
{
|
{
|
||||||
return $".{"rgba".AsSpan(index, 1)}";
|
return $".{"rgba".AsSpan(index, 1)}";
|
||||||
|
@@ -11,9 +11,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
class OperandManager
|
class OperandManager
|
||||||
{
|
{
|
||||||
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
|
private Dictionary<AstOperand, string> _locals;
|
||||||
|
|
||||||
private readonly Dictionary<AstOperand, string> _locals;
|
|
||||||
|
|
||||||
public OperandManager()
|
public OperandManager()
|
||||||
{
|
{
|
||||||
@@ -41,60 +39,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
|
|
||||||
{
|
|
||||||
return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetSamplerName(ShaderStage stage, int cbufSlot, int handle, bool indexed, string indexExpr)
|
|
||||||
{
|
|
||||||
string suffix = cbufSlot < 0 ? $"_tcb_{handle:X}" : $"_cb{cbufSlot}_{handle:X}";
|
|
||||||
|
|
||||||
if (indexed)
|
|
||||||
{
|
|
||||||
suffix += $"a[{indexExpr}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
|
|
||||||
{
|
|
||||||
return GetImageName(stage, texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetImageName(
|
|
||||||
ShaderStage stage,
|
|
||||||
int cbufSlot,
|
|
||||||
int handle,
|
|
||||||
TextureFormat format,
|
|
||||||
bool indexed,
|
|
||||||
string indexExpr)
|
|
||||||
{
|
|
||||||
string suffix = cbufSlot < 0
|
|
||||||
? $"_tcb_{handle:X}_{format.ToGlslFormat()}"
|
|
||||||
: $"_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
|
|
||||||
|
|
||||||
if (indexed)
|
|
||||||
{
|
|
||||||
suffix += $"a[{indexExpr}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetShaderStagePrefix(stage) + "_" + DefaultNames.ImageNamePrefix + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
|
||||||
{
|
|
||||||
int index = (int)stage;
|
|
||||||
|
|
||||||
if ((uint)index >= _stagePrefixes.Length)
|
|
||||||
{
|
|
||||||
return "invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
return _stagePrefixes[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetArgumentName(int argIndex)
|
public static string GetArgumentName(int argIndex)
|
||||||
{
|
{
|
||||||
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
|
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
|
||||||
|
@@ -28,9 +28,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
|
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
|
||||||
public Dictionary<int, Instruction> LocalMemories { get; } = new Dictionary<int, Instruction>();
|
public Dictionary<int, Instruction> LocalMemories { get; } = new Dictionary<int, Instruction>();
|
||||||
public Dictionary<int, Instruction> SharedMemories { get; } = new Dictionary<int, Instruction>();
|
public Dictionary<int, Instruction> SharedMemories { get; } = new Dictionary<int, Instruction>();
|
||||||
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
|
public Dictionary<int, SamplerType> SamplersTypes { get; } = new Dictionary<int, SamplerType>();
|
||||||
public Dictionary<TextureMeta, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<TextureMeta, (Instruction, Instruction, Instruction)>();
|
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<int, (Instruction, Instruction, Instruction)>();
|
||||||
public Dictionary<TextureMeta, (Instruction, Instruction)> Images { get; } = new Dictionary<TextureMeta, (Instruction, Instruction)>();
|
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new Dictionary<int, (Instruction, Instruction)>();
|
||||||
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
|
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
|
||||||
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
|
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
|
||||||
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
|
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
|
||||||
|
@@ -72,8 +72,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
||||||
DeclareMemories(context, context.Config.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
|
DeclareMemories(context, context.Config.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
|
||||||
DeclareMemories(context, context.Config.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
|
DeclareMemories(context, context.Config.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
|
||||||
DeclareSamplers(context, context.Config.GetTextureDescriptors());
|
DeclareSamplers(context, context.Config.Properties.Textures.Values);
|
||||||
DeclareImages(context, context.Config.GetImageDescriptors());
|
DeclareImages(context, context.Config.Properties.Images.Values);
|
||||||
DeclareInputsAndOutputs(context, info);
|
DeclareInputsAndOutputs(context, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +110,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
foreach (BufferDefinition buffer in buffers)
|
foreach (BufferDefinition buffer in buffers)
|
||||||
{
|
{
|
||||||
|
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
|
||||||
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
|
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
|
||||||
int alignmentMask = alignment - 1;
|
int alignmentMask = alignment - 1;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
@@ -163,7 +164,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
var variable = context.Variable(pointerType, StorageClass.Uniform);
|
var variable = context.Variable(pointerType, StorageClass.Uniform);
|
||||||
|
|
||||||
context.Name(variable, buffer.Name);
|
context.Name(variable, buffer.Name);
|
||||||
context.Decorate(variable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
|
context.Decorate(variable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||||
context.Decorate(variable, Decoration.Binding, (LiteralInteger)buffer.Binding);
|
context.Decorate(variable, Decoration.Binding, (LiteralInteger)buffer.Binding);
|
||||||
context.AddGlobalVariable(variable);
|
context.AddGlobalVariable(variable);
|
||||||
|
|
||||||
@@ -178,92 +179,72 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> samplers)
|
||||||
{
|
{
|
||||||
foreach (var descriptor in descriptors)
|
foreach (var sampler in samplers)
|
||||||
{
|
{
|
||||||
var meta = new TextureMeta(descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Format);
|
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
|
||||||
|
|
||||||
if (context.Samplers.ContainsKey(meta))
|
var dim = (sampler.Type & SamplerType.Mask) switch
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? 2 : 0;
|
|
||||||
|
|
||||||
var dim = (descriptor.Type & SamplerType.Mask) switch
|
|
||||||
{
|
{
|
||||||
SamplerType.Texture1D => Dim.Dim1D,
|
SamplerType.Texture1D => Dim.Dim1D,
|
||||||
SamplerType.Texture2D => Dim.Dim2D,
|
SamplerType.Texture2D => Dim.Dim2D,
|
||||||
SamplerType.Texture3D => Dim.Dim3D,
|
SamplerType.Texture3D => Dim.Dim3D,
|
||||||
SamplerType.TextureCube => Dim.Cube,
|
SamplerType.TextureCube => Dim.Cube,
|
||||||
SamplerType.TextureBuffer => Dim.Buffer,
|
SamplerType.TextureBuffer => Dim.Buffer,
|
||||||
_ => throw new InvalidOperationException($"Invalid sampler type \"{descriptor.Type & SamplerType.Mask}\"."),
|
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\".")
|
||||||
};
|
};
|
||||||
|
|
||||||
var imageType = context.TypeImage(
|
var imageType = context.TypeImage(
|
||||||
context.TypeFP32(),
|
context.TypeFP32(),
|
||||||
dim,
|
dim,
|
||||||
descriptor.Type.HasFlag(SamplerType.Shadow),
|
sampler.Type.HasFlag(SamplerType.Shadow),
|
||||||
descriptor.Type.HasFlag(SamplerType.Array),
|
sampler.Type.HasFlag(SamplerType.Array),
|
||||||
descriptor.Type.HasFlag(SamplerType.Multisample),
|
sampler.Type.HasFlag(SamplerType.Multisample),
|
||||||
1,
|
1,
|
||||||
ImageFormat.Unknown);
|
ImageFormat.Unknown);
|
||||||
|
|
||||||
var nameSuffix = meta.CbufSlot < 0 ? $"_tcb_{meta.Handle:X}" : $"_cb{meta.CbufSlot}_{meta.Handle:X}";
|
|
||||||
|
|
||||||
var sampledImageType = context.TypeSampledImage(imageType);
|
var sampledImageType = context.TypeSampledImage(imageType);
|
||||||
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
|
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
|
||||||
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant);
|
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant);
|
||||||
|
|
||||||
context.Samplers.Add(meta, (imageType, sampledImageType, sampledImageVariable));
|
context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable));
|
||||||
context.SamplersTypes.Add(meta, descriptor.Type);
|
context.SamplersTypes.Add(sampler.Binding, sampler.Type);
|
||||||
|
|
||||||
context.Name(sampledImageVariable, $"{GetStagePrefix(context.Config.Stage)}_tex{nameSuffix}");
|
context.Name(sampledImageVariable, sampler.Name);
|
||||||
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||||
context.Decorate(sampledImageVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
|
context.Decorate(sampledImageVariable, Decoration.Binding, (LiteralInteger)sampler.Binding);
|
||||||
context.AddGlobalVariable(sampledImageVariable);
|
context.AddGlobalVariable(sampledImageVariable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareImages(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> images)
|
||||||
{
|
{
|
||||||
foreach (var descriptor in descriptors)
|
foreach (var image in images)
|
||||||
{
|
{
|
||||||
var meta = new TextureMeta(descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Format);
|
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? image.Set : 0;
|
||||||
|
|
||||||
if (context.Images.ContainsKey(meta))
|
var dim = GetDim(image.Type);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? 3 : 0;
|
|
||||||
|
|
||||||
var dim = GetDim(descriptor.Type);
|
|
||||||
|
|
||||||
var imageType = context.TypeImage(
|
var imageType = context.TypeImage(
|
||||||
context.GetType(meta.Format.GetComponentType()),
|
context.GetType(image.Format.GetComponentType()),
|
||||||
dim,
|
dim,
|
||||||
descriptor.Type.HasFlag(SamplerType.Shadow),
|
image.Type.HasFlag(SamplerType.Shadow),
|
||||||
descriptor.Type.HasFlag(SamplerType.Array),
|
image.Type.HasFlag(SamplerType.Array),
|
||||||
descriptor.Type.HasFlag(SamplerType.Multisample),
|
image.Type.HasFlag(SamplerType.Multisample),
|
||||||
AccessQualifier.ReadWrite,
|
AccessQualifier.ReadWrite,
|
||||||
GetImageFormat(meta.Format));
|
GetImageFormat(image.Format));
|
||||||
|
|
||||||
var nameSuffix = meta.CbufSlot < 0 ?
|
|
||||||
$"_tcb_{meta.Handle:X}_{meta.Format.ToGlslFormat()}" :
|
|
||||||
$"_cb{meta.CbufSlot}_{meta.Handle:X}_{meta.Format.ToGlslFormat()}";
|
|
||||||
|
|
||||||
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
|
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
|
||||||
var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant);
|
var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant);
|
||||||
|
|
||||||
context.Images.Add(meta, (imageType, imageVariable));
|
context.Images.Add(image.Binding, (imageType, imageVariable));
|
||||||
|
|
||||||
context.Name(imageVariable, $"{GetStagePrefix(context.Config.Stage)}_img{nameSuffix}");
|
context.Name(imageVariable, image.Name);
|
||||||
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||||
context.Decorate(imageVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
|
context.Decorate(imageVariable, Decoration.Binding, (LiteralInteger)image.Binding);
|
||||||
|
|
||||||
if (descriptor.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
if (image.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
||||||
{
|
{
|
||||||
context.Decorate(imageVariable, Decoration.Coherent);
|
context.Decorate(imageVariable, Decoration.Coherent);
|
||||||
}
|
}
|
||||||
|
@@ -657,7 +657,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
SpvInstruction value = Src(componentType);
|
SpvInstruction value = Src(componentType);
|
||||||
|
|
||||||
(SpvInstruction imageType, SpvInstruction imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
|
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||||
|
|
||||||
context.Load(imageType, imageVariable);
|
context.Load(imageType, imageVariable);
|
||||||
|
|
||||||
@@ -742,7 +742,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
pCoords = Src(AggregateType.S32);
|
pCoords = Src(AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
var (imageType, imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
|
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||||
|
|
||||||
var image = context.Load(imageType, imageVariable);
|
var image = context.Load(imageType, imageVariable);
|
||||||
var imageComponentType = context.GetType(componentType);
|
var imageComponentType = context.GetType(componentType);
|
||||||
@@ -829,7 +829,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
|
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
|
||||||
|
|
||||||
var (imageType, imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
|
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||||
|
|
||||||
var image = context.Load(imageType, imageVariable);
|
var image = context.Load(imageType, imageVariable);
|
||||||
|
|
||||||
@@ -908,9 +908,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
pCoords = Src(AggregateType.FP32);
|
pCoords = Src(AggregateType.FP32);
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format);
|
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||||
|
|
||||||
var (_, sampledImageType, sampledImageVariable) = context.Samplers[meta];
|
|
||||||
|
|
||||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||||
|
|
||||||
@@ -1511,9 +1509,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
|
var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
|
||||||
|
|
||||||
var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format);
|
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||||
|
|
||||||
var (imageType, sampledImageType, sampledImageVariable) = context.Samplers[meta];
|
|
||||||
|
|
||||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||||
|
|
||||||
@@ -1592,9 +1588,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
context.GetS32(texOp.GetSource(0));
|
context.GetS32(texOp.GetSource(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format);
|
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||||
|
|
||||||
(SpvInstruction imageType, SpvInstruction sampledImageType, SpvInstruction sampledImageVariable) = context.Samplers[meta];
|
|
||||||
|
|
||||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||||
image = context.Image(imageType, image);
|
image = context.Image(imageType, image);
|
||||||
@@ -1605,7 +1599,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var type = context.SamplersTypes[meta];
|
var type = context.SamplersTypes[texOp.Binding];
|
||||||
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
|
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
|
||||||
|
|
||||||
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
|
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|
||||||
{
|
|
||||||
readonly record struct TextureMeta(int CbufSlot, int Handle, TextureFormat Format);
|
|
||||||
}
|
|
@@ -218,7 +218,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
return context.Copy(Register(srcB++, RegisterType.Gpr));
|
return context.Copy(Register(srcB++, RegisterType.Gpr));
|
||||||
}
|
}
|
||||||
|
|
||||||
Operand destOperand = dest != RegisterConsts.RegisterZeroIndex ? Register(dest, RegisterType.Gpr) : null;
|
Operand d = dest != RegisterConsts.RegisterZeroIndex ? Register(dest, RegisterType.Gpr) : null;
|
||||||
|
|
||||||
List<Operand> sourcesList = new();
|
List<Operand> sourcesList = new();
|
||||||
|
|
||||||
@@ -277,17 +277,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
flags |= TextureFlags.Bindless;
|
flags |= TextureFlags.Bindless;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageAtomic,
|
Instruction.ImageAtomic,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
flags,
|
flags,
|
||||||
imm,
|
TextureOperation.DefaultCbufSlot,
|
||||||
0,
|
imm);
|
||||||
new[] { destOperand },
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
Operand res = context.ImageAtomic(type, format, flags, binding, sources);
|
||||||
|
|
||||||
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitSuld(
|
private static void EmitSuld(
|
||||||
@@ -383,21 +383,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
Array.Resize(ref dests, outputIndex);
|
Array.Resize(ref dests, outputIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureFormat format = isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormat(handle);
|
||||||
|
|
||||||
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageLoad,
|
Instruction.ImageLoad,
|
||||||
type,
|
type,
|
||||||
|
format,
|
||||||
flags,
|
flags,
|
||||||
handle,
|
TextureOperation.DefaultCbufSlot,
|
||||||
(int)componentMask,
|
handle);
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
if (!isBindless)
|
context.ImageLoad(type, format, flags, binding, (int)componentMask, dests, sources);
|
||||||
{
|
|
||||||
operation.Format = context.Config.GetTextureFormat(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Add(operation);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -430,17 +426,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
Array.Resize(ref dests, outputIndex);
|
Array.Resize(ref dests, outputIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
TextureFormat format = GetTextureFormat(size);
|
||||||
|
|
||||||
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageLoad,
|
Instruction.ImageLoad,
|
||||||
type,
|
type,
|
||||||
GetTextureFormat(size),
|
format,
|
||||||
flags,
|
flags,
|
||||||
handle,
|
TextureOperation.DefaultCbufSlot,
|
||||||
compMask,
|
handle);
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
context.ImageLoad(type, format, flags, binding, compMask, dests, sources);
|
||||||
|
|
||||||
switch (size)
|
switch (size)
|
||||||
{
|
{
|
||||||
@@ -552,17 +548,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
flags |= TextureFlags.Bindless;
|
flags |= TextureFlags.Bindless;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageAtomic,
|
Instruction.ImageAtomic,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
flags,
|
flags,
|
||||||
imm,
|
TextureOperation.DefaultCbufSlot,
|
||||||
0,
|
imm);
|
||||||
null,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
context.ImageAtomic(type, format, flags, binding, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitSust(
|
private static void EmitSust(
|
||||||
@@ -681,17 +675,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
flags |= TextureFlags.Coherent;
|
flags |= TextureFlags.Coherent;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageStore,
|
Instruction.ImageStore,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
flags,
|
flags,
|
||||||
handle,
|
TextureOperation.DefaultCbufSlot,
|
||||||
0,
|
handle);
|
||||||
null,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
context.ImageStore(type, format, flags, binding, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetComponentSizeInBytesLog2(SuatomSize size)
|
private static int GetComponentSizeInBytesLog2(SuatomSize size)
|
||||||
|
@@ -324,16 +324,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
|
|
||||||
int handle = !isBindless ? imm : 0;
|
int handle = !isBindless ? imm : 0;
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
EmitTextureSample(context, type, flags, handle, componentMask, dests, sources);
|
||||||
Instruction.TextureSample,
|
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
handle,
|
|
||||||
componentMask,
|
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitTexs(
|
private static void EmitTexs(
|
||||||
@@ -657,16 +648,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
Array.Resize(ref dests, outputIndex);
|
Array.Resize(ref dests, outputIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
EmitTextureSample(context, type, flags, handle, componentMask, dests, sources);
|
||||||
Instruction.TextureSample,
|
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
handle,
|
|
||||||
componentMask,
|
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
|
||||||
|
|
||||||
if (isF16)
|
if (isF16)
|
||||||
{
|
{
|
||||||
@@ -812,18 +794,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
Array.Resize(ref dests, outputIndex);
|
Array.Resize(ref dests, outputIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int handle = imm;
|
EmitTextureSample(context, type, flags, imm, componentMask, dests, sources);
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
|
||||||
Instruction.TextureSample,
|
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
handle,
|
|
||||||
componentMask,
|
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitTmml(
|
private static void EmitTmml(
|
||||||
@@ -913,15 +884,21 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int handle = imm;
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
Instruction.Lod,
|
||||||
|
type,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
flags,
|
||||||
|
TextureOperation.DefaultCbufSlot,
|
||||||
|
imm);
|
||||||
|
|
||||||
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
|
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
Operand destOperand = GetDest();
|
Operand d = GetDest();
|
||||||
|
|
||||||
if (destOperand == null)
|
if (d == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -930,28 +907,18 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
if (compIndex >= 2)
|
if (compIndex >= 2)
|
||||||
{
|
{
|
||||||
context.Add(new CommentNode("Unsupported component z or w found"));
|
context.Add(new CommentNode("Unsupported component z or w found"));
|
||||||
context.Copy(destOperand, Const(0));
|
context.Copy(d, Const(0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Operand tempDest = Local();
|
// The instruction component order is the inverse of GLSL's.
|
||||||
|
Operand res = context.Lod(type, flags, binding, compIndex ^ 1, sources);
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
res = context.FPMultiply(res, ConstF(256.0f));
|
||||||
Instruction.Lod,
|
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
handle,
|
|
||||||
compIndex ^ 1, // The instruction component order is the inverse of GLSL's.
|
|
||||||
new[] { tempDest },
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
Operand fixedPointValue = context.FP32ConvertToS32(res);
|
||||||
|
|
||||||
tempDest = context.FPMultiply(tempDest, ConstF(256.0f));
|
context.Copy(d, fixedPointValue);
|
||||||
|
|
||||||
Operand fixedPointValue = context.FP32ConvertToS32(tempDest);
|
|
||||||
|
|
||||||
context.Copy(destOperand, fixedPointValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1081,18 +1048,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
Array.Resize(ref dests, outputIndex);
|
Array.Resize(ref dests, outputIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int handle = imm;
|
EmitTextureSample(context, type, flags, imm, componentMask, dests, sources);
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
|
||||||
Instruction.TextureSample,
|
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
handle,
|
|
||||||
componentMask,
|
|
||||||
dests,
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitTxq(
|
private static void EmitTxq(
|
||||||
@@ -1111,10 +1067,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
|
|
||||||
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||||
|
|
||||||
// TODO: Validate and use query.
|
|
||||||
Instruction inst = Instruction.TextureSize;
|
|
||||||
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
|
||||||
|
|
||||||
Operand Ra()
|
Operand Ra()
|
||||||
{
|
{
|
||||||
if (srcA > RegisterConsts.RegisterZeroIndex)
|
if (srcA > RegisterConsts.RegisterZeroIndex)
|
||||||
@@ -1157,31 +1109,55 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
type = context.Config.GpuAccessor.QuerySamplerType(imm);
|
type = context.Config.GpuAccessor.QuerySamplerType(imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
||||||
|
|
||||||
|
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
Instruction.TextureSize,
|
||||||
|
type,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
flags,
|
||||||
|
TextureOperation.DefaultCbufSlot,
|
||||||
|
imm);
|
||||||
|
|
||||||
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
|
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
|
||||||
{
|
{
|
||||||
if ((compMask & 1) != 0)
|
if ((compMask & 1) != 0)
|
||||||
{
|
{
|
||||||
Operand destOperand = GetDest();
|
Operand d = GetDest();
|
||||||
|
|
||||||
if (destOperand == null)
|
if (d == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureOperation operation = context.CreateTextureOperation(
|
// TODO: Validate and use query parameter.
|
||||||
inst,
|
Operand res = context.TextureSize(type, flags, binding, compIndex, sources);
|
||||||
type,
|
|
||||||
flags,
|
|
||||||
imm,
|
|
||||||
compIndex,
|
|
||||||
new[] { destOperand },
|
|
||||||
sources);
|
|
||||||
|
|
||||||
context.Add(operation);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitTextureSample(
|
||||||
|
EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFlags flags,
|
||||||
|
int handle,
|
||||||
|
int componentMask,
|
||||||
|
Operand[] dests,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
Instruction.TextureSample,
|
||||||
|
type,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
flags,
|
||||||
|
TextureOperation.DefaultCbufSlot,
|
||||||
|
handle);
|
||||||
|
|
||||||
|
context.TextureSample(type, flags, binding, componentMask, dests, sources);
|
||||||
|
}
|
||||||
|
|
||||||
private static SamplerType ConvertSamplerType(TexDim dimensions)
|
private static SamplerType ConvertSamplerType(TexDim dimensions)
|
||||||
{
|
{
|
||||||
return dimensions switch
|
return dimensions switch
|
||||||
|
@@ -8,16 +8,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
public TextureFormat Format { get; set; }
|
public TextureFormat Format { get; set; }
|
||||||
public TextureFlags Flags { get; private set; }
|
public TextureFlags Flags { get; private set; }
|
||||||
|
|
||||||
public int CbufSlot { get; private set; }
|
public int Binding { get; private set; }
|
||||||
public int Handle { get; private set; }
|
|
||||||
|
|
||||||
public TextureOperation(
|
public TextureOperation(
|
||||||
Instruction inst,
|
Instruction inst,
|
||||||
SamplerType type,
|
SamplerType type,
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
TextureFlags flags,
|
TextureFlags flags,
|
||||||
int cbufSlot,
|
int binding,
|
||||||
int handle,
|
|
||||||
int compIndex,
|
int compIndex,
|
||||||
Operand[] dests,
|
Operand[] dests,
|
||||||
Operand[] sources) : base(inst, compIndex, dests, sources)
|
Operand[] sources) : base(inst, compIndex, dests, sources)
|
||||||
@@ -25,30 +23,17 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
Type = type;
|
Type = type;
|
||||||
Format = format;
|
Format = format;
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
CbufSlot = cbufSlot;
|
Binding = binding;
|
||||||
Handle = handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextureOperation(
|
public void TurnIntoIndexed(int binding)
|
||||||
Instruction inst,
|
|
||||||
SamplerType type,
|
|
||||||
TextureFormat format,
|
|
||||||
TextureFlags flags,
|
|
||||||
int handle,
|
|
||||||
int compIndex,
|
|
||||||
Operand[] dests,
|
|
||||||
Operand[] sources) : this(inst, type, format, flags, DefaultCbufSlot, handle, compIndex, dests, sources)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TurnIntoIndexed(int handle)
|
|
||||||
{
|
{
|
||||||
Type |= SamplerType.Indexed;
|
Type |= SamplerType.Indexed;
|
||||||
Flags &= ~TextureFlags.Bindless;
|
Flags &= ~TextureFlags.Bindless;
|
||||||
Handle = handle;
|
Binding = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHandle(int handle, int cbufSlot = DefaultCbufSlot)
|
public void SetBinding(int binding)
|
||||||
{
|
{
|
||||||
if ((Flags & TextureFlags.Bindless) != 0)
|
if ((Flags & TextureFlags.Bindless) != 0)
|
||||||
{
|
{
|
||||||
@@ -57,8 +42,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
RemoveSource(0);
|
RemoveSource(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
CbufSlot = cbufSlot;
|
Binding = binding;
|
||||||
Handle = handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLodLevelFlag()
|
public void SetLodLevelFlag()
|
||||||
|
@@ -8,24 +8,21 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
public TextureFormat Format { get; }
|
public TextureFormat Format { get; }
|
||||||
public TextureFlags Flags { get; }
|
public TextureFlags Flags { get; }
|
||||||
|
|
||||||
public int CbufSlot { get; }
|
public int Binding { get; }
|
||||||
public int Handle { get; }
|
|
||||||
|
|
||||||
public AstTextureOperation(
|
public AstTextureOperation(
|
||||||
Instruction inst,
|
Instruction inst,
|
||||||
SamplerType type,
|
SamplerType type,
|
||||||
TextureFormat format,
|
TextureFormat format,
|
||||||
TextureFlags flags,
|
TextureFlags flags,
|
||||||
int cbufSlot,
|
int binding,
|
||||||
int handle,
|
|
||||||
int index,
|
int index,
|
||||||
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
|
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
Format = format;
|
Format = format;
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
CbufSlot = cbufSlot;
|
Binding = binding;
|
||||||
Handle = handle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
|
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
|
||||||
private readonly Dictionary<int, BufferDefinition> _storageBuffers;
|
private readonly Dictionary<int, BufferDefinition> _storageBuffers;
|
||||||
|
private readonly Dictionary<int, TextureDefinition> _textures;
|
||||||
|
private readonly Dictionary<int, TextureDefinition> _images;
|
||||||
private readonly Dictionary<int, MemoryDefinition> _localMemories;
|
private readonly Dictionary<int, MemoryDefinition> _localMemories;
|
||||||
private readonly Dictionary<int, MemoryDefinition> _sharedMemories;
|
private readonly Dictionary<int, MemoryDefinition> _sharedMemories;
|
||||||
|
|
||||||
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
|
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
|
||||||
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers;
|
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers;
|
||||||
|
public IReadOnlyDictionary<int, TextureDefinition> Textures => _textures;
|
||||||
|
public IReadOnlyDictionary<int, TextureDefinition> Images => _images;
|
||||||
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
|
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
|
||||||
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
|
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
|
||||||
|
|
||||||
@@ -18,20 +22,32 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
{
|
{
|
||||||
_constantBuffers = new Dictionary<int, BufferDefinition>();
|
_constantBuffers = new Dictionary<int, BufferDefinition>();
|
||||||
_storageBuffers = new Dictionary<int, BufferDefinition>();
|
_storageBuffers = new Dictionary<int, BufferDefinition>();
|
||||||
|
_textures = new Dictionary<int, TextureDefinition>();
|
||||||
|
_images = new Dictionary<int, TextureDefinition>();
|
||||||
_localMemories = new Dictionary<int, MemoryDefinition>();
|
_localMemories = new Dictionary<int, MemoryDefinition>();
|
||||||
_sharedMemories = new Dictionary<int, MemoryDefinition>();
|
_sharedMemories = new Dictionary<int, MemoryDefinition>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddConstantBuffer(int binding, BufferDefinition definition)
|
public void AddOrUpdateConstantBuffer(int binding, BufferDefinition definition)
|
||||||
{
|
{
|
||||||
_constantBuffers[binding] = definition;
|
_constantBuffers[binding] = definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddStorageBuffer(int binding, BufferDefinition definition)
|
public void AddOrUpdateStorageBuffer(int binding, BufferDefinition definition)
|
||||||
{
|
{
|
||||||
_storageBuffers[binding] = definition;
|
_storageBuffers[binding] = definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateTexture(int binding, TextureDefinition descriptor)
|
||||||
|
{
|
||||||
|
_textures[binding] = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateImage(int binding, TextureDefinition descriptor)
|
||||||
|
{
|
||||||
|
_images[binding] = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
public int AddLocalMemory(MemoryDefinition definition)
|
public int AddLocalMemory(MemoryDefinition definition)
|
||||||
{
|
{
|
||||||
int id = _localMemories.Count;
|
int id = _localMemories.Count;
|
||||||
|
@@ -125,15 +125,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
|
|
||||||
AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
|
AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
|
||||||
{
|
{
|
||||||
return new AstTextureOperation(
|
return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.Index, sources);
|
||||||
inst,
|
|
||||||
texOp.Type,
|
|
||||||
texOp.Format,
|
|
||||||
texOp.Flags,
|
|
||||||
texOp.CbufSlot,
|
|
||||||
texOp.Handle,
|
|
||||||
texOp.Index,
|
|
||||||
sources);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int componentsCount = BitOperations.PopCount((uint)operation.Index);
|
int componentsCount = BitOperations.PopCount((uint)operation.Index);
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Ryujinx.Graphics.Shader
|
||||||
|
{
|
||||||
|
readonly struct TextureDefinition
|
||||||
|
{
|
||||||
|
public int Set { get; }
|
||||||
|
public int Binding { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public SamplerType Type { get; }
|
||||||
|
public TextureFormat Format { get; }
|
||||||
|
public TextureUsageFlags Flags { get; }
|
||||||
|
|
||||||
|
public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags)
|
||||||
|
{
|
||||||
|
Set = set;
|
||||||
|
Binding = binding;
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
Format = format;
|
||||||
|
Flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureDefinition SetFlag(TextureUsageFlags flag)
|
||||||
|
{
|
||||||
|
return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -12,23 +12,16 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
public readonly int CbufSlot;
|
public readonly int CbufSlot;
|
||||||
public readonly int HandleIndex;
|
public readonly int HandleIndex;
|
||||||
|
|
||||||
public TextureUsageFlags Flags;
|
public readonly TextureUsageFlags Flags;
|
||||||
|
|
||||||
public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex)
|
public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex, TextureUsageFlags flags)
|
||||||
{
|
{
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
Type = type;
|
Type = type;
|
||||||
Format = format;
|
Format = format;
|
||||||
CbufSlot = cbufSlot;
|
CbufSlot = cbufSlot;
|
||||||
HandleIndex = handleIndex;
|
HandleIndex = handleIndex;
|
||||||
Flags = TextureUsageFlags.None;
|
Flags = flags;
|
||||||
}
|
|
||||||
|
|
||||||
public TextureDescriptor SetFlag(TextureUsageFlags flag)
|
|
||||||
{
|
|
||||||
Flags |= flag;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -115,36 +115,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
_operations.Add(operation);
|
_operations.Add(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextureOperation CreateTextureOperation(
|
|
||||||
Instruction inst,
|
|
||||||
SamplerType type,
|
|
||||||
TextureFlags flags,
|
|
||||||
int handle,
|
|
||||||
int compIndex,
|
|
||||||
Operand[] dests,
|
|
||||||
params Operand[] sources)
|
|
||||||
{
|
|
||||||
return CreateTextureOperation(inst, type, TextureFormat.Unknown, flags, handle, compIndex, dests, sources);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureOperation CreateTextureOperation(
|
|
||||||
Instruction inst,
|
|
||||||
SamplerType type,
|
|
||||||
TextureFormat format,
|
|
||||||
TextureFlags flags,
|
|
||||||
int handle,
|
|
||||||
int compIndex,
|
|
||||||
Operand[] dests,
|
|
||||||
params Operand[] sources)
|
|
||||||
{
|
|
||||||
if (!flags.HasFlag(TextureFlags.Bindless))
|
|
||||||
{
|
|
||||||
Config.SetUsedTexture(inst, type, format, flags, TextureOperation.DefaultCbufSlot, handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TextureOperation(inst, type, format, flags, handle, compIndex, dests, sources);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FlagAttributeRead(int attribute)
|
public void FlagAttributeRead(int attribute)
|
||||||
{
|
{
|
||||||
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)
|
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)
|
||||||
|
@@ -604,6 +604,45 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return context.Add(Instruction.Subtract, Local(), a, b);
|
return context.Add(Instruction.Subtract, Local(), a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand ImageAtomic(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFormat format,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
Operand dest = Local();
|
||||||
|
|
||||||
|
context.Add(new TextureOperation(Instruction.ImageAtomic, type, format, flags, binding, 0, new[] { dest }, sources));
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ImageLoad(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFormat format,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
int compMask,
|
||||||
|
Operand[] dests,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
context.Add(new TextureOperation(Instruction.ImageLoad, type, format, flags, binding, compMask, dests, sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ImageStore(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFormat format,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
context.Add(new TextureOperation(Instruction.ImageStore, type, format, flags, binding, 0, null, sources));
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(fpType | Instruction.IsNan, Local(), a);
|
return context.Add(fpType | Instruction.IsNan, Local(), a);
|
||||||
@@ -666,6 +705,21 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
|
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand Lod(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
int compIndex,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
Operand dest = Local();
|
||||||
|
|
||||||
|
context.Add(new TextureOperation(Instruction.Lod, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand MemoryBarrier(this EmitterContext context)
|
public static Operand MemoryBarrier(this EmitterContext context)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.MemoryBarrier);
|
return context.Add(Instruction.MemoryBarrier);
|
||||||
@@ -797,6 +851,33 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
|
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void TextureSample(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
int compMask,
|
||||||
|
Operand[] dests,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand TextureSize(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
int compIndex,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
Operand dest = Local();
|
||||||
|
|
||||||
|
context.Add(new TextureOperation(Instruction.TextureSize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand UnpackDouble2x32High(this EmitterContext context, Operand a)
|
public static Operand UnpackDouble2x32High(this EmitterContext context, Operand a)
|
||||||
{
|
{
|
||||||
return UnpackDouble2x32(context, a, 1);
|
return UnpackDouble2x32(context, a, 1);
|
||||||
|
@@ -222,8 +222,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
|
|
||||||
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot, bool rewriteSamplerType, bool isImage)
|
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot, bool rewriteSamplerType, bool isImage)
|
||||||
{
|
{
|
||||||
texOp.SetHandle(cbufOffset, cbufSlot);
|
|
||||||
|
|
||||||
if (rewriteSamplerType)
|
if (rewriteSamplerType)
|
||||||
{
|
{
|
||||||
SamplerType newType = config.GpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
|
SamplerType newType = config.GpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
|
||||||
@@ -234,7 +232,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
}
|
}
|
||||||
else if (texOp.Type == SamplerType.TextureBuffer && newType == SamplerType.Texture1D)
|
else if (texOp.Type == SamplerType.TextureBuffer && newType == SamplerType.Texture1D)
|
||||||
{
|
{
|
||||||
int coordsCount = 1;
|
int coordsCount = 2;
|
||||||
|
|
||||||
if (InstEmit.Sample1DAs2D)
|
if (InstEmit.Sample1DAs2D)
|
||||||
{
|
{
|
||||||
@@ -255,7 +253,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, cbufSlot, cbufOffset);
|
int binding = config.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
texOp.Inst,
|
||||||
|
texOp.Type,
|
||||||
|
texOp.Format,
|
||||||
|
texOp.Flags & ~TextureFlags.Bindless,
|
||||||
|
cbufSlot,
|
||||||
|
cbufOffset);
|
||||||
|
|
||||||
|
texOp.SetBinding(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
{
|
{
|
||||||
static class BindlessToIndexed
|
static class BindlessToIndexed
|
||||||
{
|
{
|
||||||
|
private const int NvnTextureBufferIndex = 2;
|
||||||
|
|
||||||
public static void RunPass(BasicBlock block, ShaderConfig config)
|
public static void RunPass(BasicBlock block, ShaderConfig config)
|
||||||
{
|
{
|
||||||
// We can turn a bindless texture access into a indexed access,
|
// We can turn a bindless texture access into a indexed access,
|
||||||
@@ -43,7 +45,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
|
|
||||||
if (ldcSrc0.Type != OperandType.Constant ||
|
if (ldcSrc0.Type != OperandType.Constant ||
|
||||||
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
|
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
|
||||||
src0CbufSlot != 2)
|
src0CbufSlot != NvnTextureBufferIndex)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -102,8 +104,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
|
|
||||||
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
|
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
|
||||||
{
|
{
|
||||||
texOp.TurnIntoIndexed(handle);
|
int binding = config.ResourceManager.GetTextureOrImageBinding(
|
||||||
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, handle);
|
texOp.Inst,
|
||||||
|
texOp.Type | SamplerType.Indexed,
|
||||||
|
texOp.Format,
|
||||||
|
texOp.Flags & ~TextureFlags.Bindless,
|
||||||
|
NvnTextureBufferIndex,
|
||||||
|
handle);
|
||||||
|
|
||||||
|
texOp.TurnIntoIndexed(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -13,9 +14,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
private const int DefaultLocalMemorySize = 128;
|
private const int DefaultLocalMemorySize = 128;
|
||||||
private const int DefaultSharedMemorySize = 4096;
|
private const int DefaultSharedMemorySize = 4096;
|
||||||
|
|
||||||
private static readonly string[] _stagePrefixes = { "cp", "vp", "tcp", "tep", "gp", "fp" };
|
// TODO: Non-hardcoded array size.
|
||||||
|
public const int SamplerArraySize = 4;
|
||||||
|
|
||||||
|
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
|
||||||
|
|
||||||
private readonly IGpuAccessor _gpuAccessor;
|
private readonly IGpuAccessor _gpuAccessor;
|
||||||
|
private readonly ShaderStage _stage;
|
||||||
private readonly string _stagePrefix;
|
private readonly string _stagePrefix;
|
||||||
|
|
||||||
private readonly int[] _cbSlotToBindingMap;
|
private readonly int[] _cbSlotToBindingMap;
|
||||||
@@ -27,6 +32,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
private readonly HashSet<int> _usedConstantBufferBindings;
|
private readonly HashSet<int> _usedConstantBufferBindings;
|
||||||
|
|
||||||
|
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
|
||||||
|
|
||||||
|
private struct TextureMeta
|
||||||
|
{
|
||||||
|
public int Binding;
|
||||||
|
public bool AccurateType;
|
||||||
|
public SamplerType Type;
|
||||||
|
public TextureUsageFlags UsageFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
||||||
|
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
||||||
|
|
||||||
public int LocalMemoryId { get; private set; }
|
public int LocalMemoryId { get; private set; }
|
||||||
public int SharedMemoryId { get; private set; }
|
public int SharedMemoryId { get; private set; }
|
||||||
|
|
||||||
@@ -36,6 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
_gpuAccessor = gpuAccessor;
|
_gpuAccessor = gpuAccessor;
|
||||||
Properties = properties;
|
Properties = properties;
|
||||||
|
_stage = stage;
|
||||||
_stagePrefix = GetShaderStagePrefix(stage);
|
_stagePrefix = GetShaderStagePrefix(stage);
|
||||||
|
|
||||||
_cbSlotToBindingMap = new int[18];
|
_cbSlotToBindingMap = new int[18];
|
||||||
@@ -48,7 +67,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
_usedConstantBufferBindings = new HashSet<int>();
|
_usedConstantBufferBindings = new HashSet<int>();
|
||||||
|
|
||||||
properties.AddConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
|
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
|
||||||
|
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
|
||||||
|
|
||||||
|
properties.AddOrUpdateConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
|
||||||
|
|
||||||
LocalMemoryId = -1;
|
LocalMemoryId = -1;
|
||||||
SharedMemoryId = -1;
|
SharedMemoryId = -1;
|
||||||
@@ -166,6 +188,198 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetTextureOrImageBinding(
|
||||||
|
Instruction inst,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFormat format,
|
||||||
|
TextureFlags flags,
|
||||||
|
int cbufSlot,
|
||||||
|
int handle)
|
||||||
|
{
|
||||||
|
inst &= Instruction.Mask;
|
||||||
|
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
|
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
|
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
|
||||||
|
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
|
||||||
|
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
||||||
|
|
||||||
|
if (!isImage)
|
||||||
|
{
|
||||||
|
format = TextureFormat.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent);
|
||||||
|
|
||||||
|
_gpuAccessor.RegisterTexture(handle, cbufSlot);
|
||||||
|
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetTextureOrImageBinding(
|
||||||
|
int cbufSlot,
|
||||||
|
int handle,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFormat format,
|
||||||
|
bool isImage,
|
||||||
|
bool intCoords,
|
||||||
|
bool write,
|
||||||
|
bool accurateType,
|
||||||
|
bool coherent)
|
||||||
|
{
|
||||||
|
var dimensions = type.GetDimensions();
|
||||||
|
var isIndexed = type.HasFlag(SamplerType.Indexed);
|
||||||
|
var dict = isImage ? _usedImages : _usedTextures;
|
||||||
|
|
||||||
|
var usageFlags = TextureUsageFlags.None;
|
||||||
|
|
||||||
|
if (intCoords)
|
||||||
|
{
|
||||||
|
usageFlags |= TextureUsageFlags.NeedsScaleValue;
|
||||||
|
|
||||||
|
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2;
|
||||||
|
|
||||||
|
if (!canScale)
|
||||||
|
{
|
||||||
|
// Resolution scaling cannot be applied to this texture right now.
|
||||||
|
// Flag so that we know to blacklist scaling on related textures when binding them.
|
||||||
|
usageFlags |= TextureUsageFlags.ResScaleUnsupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
{
|
||||||
|
usageFlags |= TextureUsageFlags.ImageStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coherent)
|
||||||
|
{
|
||||||
|
usageFlags |= TextureUsageFlags.ImageCoherent;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arraySize = isIndexed ? SamplerArraySize : 1;
|
||||||
|
int firstBinding = -1;
|
||||||
|
|
||||||
|
for (int layer = 0; layer < arraySize; layer++)
|
||||||
|
{
|
||||||
|
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
|
||||||
|
var meta = new TextureMeta()
|
||||||
|
{
|
||||||
|
AccurateType = accurateType,
|
||||||
|
Type = type,
|
||||||
|
UsageFlags = usageFlags
|
||||||
|
};
|
||||||
|
|
||||||
|
int binding;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(info, out var existingMeta))
|
||||||
|
{
|
||||||
|
dict[info] = MergeTextureMeta(meta, existingMeta);
|
||||||
|
binding = existingMeta.Binding;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
|
||||||
|
|
||||||
|
binding = isImage
|
||||||
|
? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer)
|
||||||
|
: _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer);
|
||||||
|
|
||||||
|
meta.Binding = binding;
|
||||||
|
|
||||||
|
dict.Add(info, meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
string nameSuffix;
|
||||||
|
|
||||||
|
if (isImage)
|
||||||
|
{
|
||||||
|
nameSuffix = cbufSlot < 0
|
||||||
|
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
|
||||||
|
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var definition = new TextureDefinition(
|
||||||
|
isImage ? 3 : 2,
|
||||||
|
binding,
|
||||||
|
$"{_stagePrefix}_{nameSuffix}",
|
||||||
|
meta.Type,
|
||||||
|
info.Format,
|
||||||
|
meta.UsageFlags);
|
||||||
|
|
||||||
|
if (isImage)
|
||||||
|
{
|
||||||
|
Properties.AddOrUpdateImage(binding, definition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Properties.AddOrUpdateTexture(binding, definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer == 0)
|
||||||
|
{
|
||||||
|
firstBinding = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
|
||||||
|
{
|
||||||
|
meta.Binding = existingMeta.Binding;
|
||||||
|
meta.UsageFlags |= existingMeta.UsageFlags;
|
||||||
|
|
||||||
|
// If the texture we have has inaccurate type information, then
|
||||||
|
// we prefer the most accurate one.
|
||||||
|
if (existingMeta.AccurateType)
|
||||||
|
{
|
||||||
|
meta.AccurateType = true;
|
||||||
|
meta.Type = existingMeta.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUsageFlagsForTextureQuery(int binding, SamplerType type)
|
||||||
|
{
|
||||||
|
TextureInfo selectedInfo = default;
|
||||||
|
TextureMeta selectedMeta = default;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||||
|
{
|
||||||
|
if (meta.Binding == binding)
|
||||||
|
{
|
||||||
|
selectedInfo = info;
|
||||||
|
selectedMeta = meta;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
|
||||||
|
|
||||||
|
var dimensions = type.GetDimensions();
|
||||||
|
var isIndexed = type.HasFlag(SamplerType.Indexed);
|
||||||
|
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
|
||||||
|
|
||||||
|
if (!canScale)
|
||||||
|
{
|
||||||
|
// Resolution scaling cannot be applied to this texture right now.
|
||||||
|
// Flag so that we know to blacklist scaling on related textures when binding them.
|
||||||
|
selectedMeta.UsageFlags |= TextureUsageFlags.ResScaleUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
_usedTextures[selectedInfo] = selectedMeta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetUsedConstantBufferBinding(int binding)
|
public void SetUsedConstantBufferBinding(int binding)
|
||||||
{
|
{
|
||||||
_usedConstantBufferBindings.Add(binding);
|
_usedConstantBufferBindings.Add(binding);
|
||||||
@@ -208,10 +422,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
if (binding >= 0)
|
if (binding >= 0)
|
||||||
{
|
{
|
||||||
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
|
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
|
||||||
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset)
|
BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None;
|
||||||
{
|
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset, flags);
|
||||||
Flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +435,64 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextureDescriptor[] GetTextureDescriptors()
|
||||||
|
{
|
||||||
|
return GetDescriptors(_usedTextures, _usedTextures.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureDescriptor[] GetImageDescriptors()
|
||||||
|
{
|
||||||
|
return GetDescriptors(_usedImages, _usedImages.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, int count)
|
||||||
|
{
|
||||||
|
TextureDescriptor[] descriptors = new TextureDescriptor[count];
|
||||||
|
|
||||||
|
int descriptorIndex = 0;
|
||||||
|
|
||||||
|
foreach ((TextureInfo info, TextureMeta meta) in usedResources)
|
||||||
|
{
|
||||||
|
descriptors[descriptorIndex++] = new TextureDescriptor(
|
||||||
|
meta.Binding,
|
||||||
|
meta.Type,
|
||||||
|
info.Format,
|
||||||
|
info.CbufSlot,
|
||||||
|
info.Handle,
|
||||||
|
meta.UsageFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int, int) GetCbufSlotAndHandleForTexture(int binding)
|
||||||
|
{
|
||||||
|
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||||
|
{
|
||||||
|
if (meta.Binding == binding)
|
||||||
|
{
|
||||||
|
return (info.CbufSlot, info.Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"Binding {binding} is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
|
||||||
|
{
|
||||||
|
return Array.FindIndex(array, x => x.Binding == binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FindTextureDescriptorIndex(int binding)
|
||||||
|
{
|
||||||
|
return FindDescriptorIndex(GetTextureDescriptors(), binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FindImageDescriptorIndex(int binding)
|
||||||
|
{
|
||||||
|
return FindDescriptorIndex(GetImageDescriptors(), binding);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddNewConstantBuffer(int binding, string name)
|
private void AddNewConstantBuffer(int binding, string name)
|
||||||
{
|
{
|
||||||
StructureType type = new(new[]
|
StructureType type = new(new[]
|
||||||
@@ -230,7 +500,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
|
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
|
||||||
});
|
});
|
||||||
|
|
||||||
Properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
|
Properties.AddOrUpdateConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddNewStorageBuffer(int binding, string name)
|
private void AddNewStorageBuffer(int binding, string name)
|
||||||
@@ -240,7 +510,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
|
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
|
||||||
});
|
});
|
||||||
|
|
||||||
Properties.AddStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
|
Properties.AddOrUpdateStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||||
|
@@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
if (texOp.Inst == Instruction.TextureSample)
|
if (texOp.Inst == Instruction.TextureSample)
|
||||||
{
|
{
|
||||||
node = InsertCoordNormalization(node, config);
|
node = InsertCoordNormalization(hfm, node, config);
|
||||||
node = InsertCoordGatherBias(node, config);
|
node = InsertCoordGatherBias(node, config);
|
||||||
node = InsertConstOffsets(node, config);
|
node = InsertConstOffsets(node, config);
|
||||||
|
|
||||||
@@ -285,8 +285,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
|
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
|
||||||
int samplerIndex = isImage
|
int samplerIndex = isImage
|
||||||
? config.GetTextureDescriptors().Length + config.FindImageDescriptorIndex(texOp)
|
? config.ResourceManager.GetTextureDescriptors().Length + config.ResourceManager.FindImageDescriptorIndex(texOp.Binding)
|
||||||
: config.FindTextureDescriptorIndex(texOp);
|
: config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||||
|
|
||||||
for (int index = 0; index < coordsCount; index++)
|
for (int index = 0; index < coordsCount; index++)
|
||||||
{
|
{
|
||||||
@@ -326,7 +326,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
TypeSupportsScale(texOp.Type))
|
TypeSupportsScale(texOp.Type))
|
||||||
{
|
{
|
||||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
|
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
|
||||||
int samplerIndex = config.FindTextureDescriptorIndex(texOp, ignoreType: true);
|
int samplerIndex = config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||||
|
|
||||||
for (int index = texOp.DestsCount - 1; index >= 0; index--)
|
for (int index = texOp.DestsCount - 1; index >= 0; index--)
|
||||||
{
|
{
|
||||||
@@ -368,7 +368,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return (type & SamplerType.Mask) == SamplerType.Texture2D;
|
return (type & SamplerType.Mask) == SamplerType.Texture2D;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedListNode<INode> InsertCoordNormalization(LinkedListNode<INode> node, ShaderConfig config)
|
private static LinkedListNode<INode> InsertCoordNormalization(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
||||||
{
|
{
|
||||||
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
|
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
|
||||||
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
|
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
|
||||||
@@ -378,9 +378,17 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
TextureOperation texOp = (TextureOperation)node.Value;
|
TextureOperation texOp = (TextureOperation)node.Value;
|
||||||
|
|
||||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
|
if (isBindless)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||||
|
|
||||||
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
|
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
|
||||||
|
|
||||||
|
bool isCoordNormalized = config.GpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
|
||||||
|
|
||||||
if (isCoordNormalized || intCoords)
|
if (isCoordNormalized || intCoords)
|
||||||
{
|
{
|
||||||
@@ -411,18 +419,17 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texSizeSources = new Operand[] { Const(0) };
|
texSizeSources = new Operand[] { Const(0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
node.List.AddBefore(node, new TextureOperation(
|
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
|
||||||
Instruction.TextureSize,
|
Instruction.TextureSize,
|
||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
index,
|
index,
|
||||||
new[] { coordSize },
|
new[] { coordSize },
|
||||||
texSizeSources));
|
texSizeSources));
|
||||||
|
|
||||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
config.ResourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
|
||||||
|
|
||||||
Operand source = texOp.GetSource(coordsIndex + index);
|
Operand source = texOp.GetSource(coordsIndex + index);
|
||||||
|
|
||||||
@@ -431,6 +438,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
|
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
|
||||||
|
|
||||||
texOp.SetSource(coordsIndex + index, coordNormalized);
|
texOp.SetSource(coordsIndex + index, coordNormalized);
|
||||||
|
|
||||||
|
InsertTextureSizeUnscale(hfm, textureSizeNode, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
@@ -491,14 +500,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
index,
|
index,
|
||||||
new[] { coordSize },
|
new[] { coordSize },
|
||||||
texSizeSources));
|
texSizeSources));
|
||||||
|
|
||||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
|
||||||
|
|
||||||
node.List.AddBefore(node, new Operation(
|
node.List.AddBefore(node, new Operation(
|
||||||
Instruction.FP32 | Instruction.Multiply,
|
Instruction.FP32 | Instruction.Multiply,
|
||||||
scaledSize,
|
scaledSize,
|
||||||
@@ -686,8 +692,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
for (int index = 0; index < coordsCount; index++)
|
for (int index = 0; index < coordsCount; index++)
|
||||||
{
|
{
|
||||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
|
||||||
|
|
||||||
Operand offset = Local();
|
Operand offset = Local();
|
||||||
|
|
||||||
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
|
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
|
||||||
@@ -712,8 +716,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
1,
|
1,
|
||||||
new[] { dests[destIndex++] },
|
new[] { dests[destIndex++] },
|
||||||
newSources);
|
newSources);
|
||||||
@@ -744,8 +747,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
for (int index = 0; index < coordsCount; index++)
|
for (int index = 0; index < coordsCount; index++)
|
||||||
{
|
{
|
||||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
|
||||||
|
|
||||||
Operand offset = Local();
|
Operand offset = Local();
|
||||||
|
|
||||||
Operand intOffset = offsets[index];
|
Operand intOffset = offsets[index];
|
||||||
@@ -771,8 +772,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
componentIndex,
|
componentIndex,
|
||||||
dests,
|
dests,
|
||||||
sources);
|
sources);
|
||||||
@@ -806,8 +806,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
0,
|
0,
|
||||||
new[] { lod },
|
new[] { lod },
|
||||||
lodSources));
|
lodSources));
|
||||||
@@ -832,8 +831,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
texOp.CbufSlot,
|
texOp.Binding,
|
||||||
texOp.Handle,
|
|
||||||
index,
|
index,
|
||||||
new[] { texSizes[index] },
|
new[] { texSizes[index] },
|
||||||
texSizeSources));
|
texSizeSources));
|
||||||
@@ -853,7 +851,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureFormat format = config.GpuAccessor.QueryTextureFormat(texOp.Handle, texOp.CbufSlot);
|
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
|
||||||
|
|
||||||
|
TextureFormat format = config.GpuAccessor.QueryTextureFormat(handle, cbufSlot);
|
||||||
|
|
||||||
int maxPositive = format switch
|
int maxPositive = format switch
|
||||||
{
|
{
|
||||||
|
@@ -2,16 +2,12 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
class ShaderConfig
|
class ShaderConfig
|
||||||
{
|
{
|
||||||
// TODO: Non-hardcoded array size.
|
|
||||||
public const int SamplerArraySize = 4;
|
|
||||||
|
|
||||||
private const int ThreadsPerWarp = 32;
|
private const int ThreadsPerWarp = 32;
|
||||||
|
|
||||||
public ShaderStage Stage { get; }
|
public ShaderStage Stage { get; }
|
||||||
@@ -110,20 +106,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
public UInt128 NextInputAttributesComponents { get; private set; }
|
public UInt128 NextInputAttributesComponents { get; private set; }
|
||||||
public UInt128 ThisInputAttributesComponents { get; private set; }
|
public UInt128 ThisInputAttributesComponents { get; private set; }
|
||||||
|
|
||||||
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
|
|
||||||
|
|
||||||
private struct TextureMeta
|
|
||||||
{
|
|
||||||
public bool AccurateType;
|
|
||||||
public SamplerType Type;
|
|
||||||
public TextureUsageFlags UsageFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
|
||||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
|
||||||
private TextureDescriptor[] _cachedTextureDescriptors;
|
|
||||||
private TextureDescriptor[] _cachedImageDescriptors;
|
|
||||||
|
|
||||||
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options, int localMemorySize)
|
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options, int localMemorySize)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
@@ -141,9 +123,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
UsedInputAttributesPerPatch = new HashSet<int>();
|
UsedInputAttributesPerPatch = new HashSet<int>();
|
||||||
UsedOutputAttributesPerPatch = new HashSet<int>();
|
UsedOutputAttributesPerPatch = new HashSet<int>();
|
||||||
|
|
||||||
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
|
|
||||||
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
|
|
||||||
|
|
||||||
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
|
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
|
||||||
|
|
||||||
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
|
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
|
||||||
@@ -156,7 +135,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
|
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
|
||||||
|
|
||||||
Properties.AddStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
|
Properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
|
||||||
|
|
||||||
StructureType tfeDataStruct = new(new StructureField[]
|
StructureType tfeDataStruct = new(new StructureField[]
|
||||||
{
|
{
|
||||||
@@ -167,7 +146,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
int binding = Constants.TfeBufferBaseBinding + i;
|
int binding = Constants.TfeBufferBaseBinding + i;
|
||||||
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
||||||
Properties.AddStorageBuffer(binding, tfeDataBuffer);
|
Properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,22 +422,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
UsedInputAttributes |= other.UsedInputAttributes;
|
UsedInputAttributes |= other.UsedInputAttributes;
|
||||||
UsedOutputAttributes |= other.UsedOutputAttributes;
|
UsedOutputAttributes |= other.UsedOutputAttributes;
|
||||||
|
|
||||||
foreach (var kv in other._usedTextures)
|
|
||||||
{
|
|
||||||
if (!_usedTextures.TryAdd(kv.Key, kv.Value))
|
|
||||||
{
|
|
||||||
_usedTextures[kv.Key] = MergeTextureMeta(kv.Value, _usedTextures[kv.Key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var kv in other._usedImages)
|
|
||||||
{
|
|
||||||
if (!_usedImages.TryAdd(kv.Key, kv.Value))
|
|
||||||
{
|
|
||||||
_usedImages[kv.Key] = MergeTextureMeta(kv.Value, _usedImages[kv.Key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLayerOutputAttribute(int attr)
|
public void SetLayerOutputAttribute(int attr)
|
||||||
@@ -642,196 +605,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
UsedFeatures |= flags;
|
UsedFeatures |= flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUsedTexture(
|
|
||||||
Instruction inst,
|
|
||||||
SamplerType type,
|
|
||||||
TextureFormat format,
|
|
||||||
TextureFlags flags,
|
|
||||||
int cbufSlot,
|
|
||||||
int handle)
|
|
||||||
{
|
|
||||||
inst &= Instruction.Mask;
|
|
||||||
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
|
||||||
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
|
||||||
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
|
|
||||||
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
|
||||||
|
|
||||||
if (isImage)
|
|
||||||
{
|
|
||||||
SetUsedTextureOrImage(_usedImages, cbufSlot, handle, type, format, true, isWrite, false, coherent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool intCoords = flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
|
|
||||||
SetUsedTextureOrImage(_usedTextures, cbufSlot, handle, type, TextureFormat.Unknown, intCoords, false, accurateType, coherent);
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuAccessor.RegisterTexture(handle, cbufSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetUsedTextureOrImage(
|
|
||||||
Dictionary<TextureInfo, TextureMeta> dict,
|
|
||||||
int cbufSlot,
|
|
||||||
int handle,
|
|
||||||
SamplerType type,
|
|
||||||
TextureFormat format,
|
|
||||||
bool intCoords,
|
|
||||||
bool write,
|
|
||||||
bool accurateType,
|
|
||||||
bool coherent)
|
|
||||||
{
|
|
||||||
var dimensions = type.GetDimensions();
|
|
||||||
var isIndexed = type.HasFlag(SamplerType.Indexed);
|
|
||||||
|
|
||||||
var usageFlags = TextureUsageFlags.None;
|
|
||||||
|
|
||||||
if (intCoords)
|
|
||||||
{
|
|
||||||
usageFlags |= TextureUsageFlags.NeedsScaleValue;
|
|
||||||
|
|
||||||
var canScale = Stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2;
|
|
||||||
|
|
||||||
if (!canScale)
|
|
||||||
{
|
|
||||||
// Resolution scaling cannot be applied to this texture right now.
|
|
||||||
// Flag so that we know to blacklist scaling on related textures when binding them.
|
|
||||||
usageFlags |= TextureUsageFlags.ResScaleUnsupported;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write)
|
|
||||||
{
|
|
||||||
usageFlags |= TextureUsageFlags.ImageStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coherent)
|
|
||||||
{
|
|
||||||
usageFlags |= TextureUsageFlags.ImageCoherent;
|
|
||||||
}
|
|
||||||
|
|
||||||
int arraySize = isIndexed ? SamplerArraySize : 1;
|
|
||||||
|
|
||||||
for (int layer = 0; layer < arraySize; layer++)
|
|
||||||
{
|
|
||||||
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
|
|
||||||
var meta = new TextureMeta()
|
|
||||||
{
|
|
||||||
AccurateType = accurateType,
|
|
||||||
Type = type,
|
|
||||||
UsageFlags = usageFlags,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dict.TryGetValue(info, out var existingMeta))
|
|
||||||
{
|
|
||||||
dict[info] = MergeTextureMeta(meta, existingMeta);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dict.Add(info, meta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
|
|
||||||
{
|
|
||||||
meta.UsageFlags |= existingMeta.UsageFlags;
|
|
||||||
|
|
||||||
// If the texture we have has inaccurate type information, then
|
|
||||||
// we prefer the most accurate one.
|
|
||||||
if (existingMeta.AccurateType)
|
|
||||||
{
|
|
||||||
meta.AccurateType = true;
|
|
||||||
meta.Type = existingMeta.Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureDescriptor[] GetTextureDescriptors()
|
|
||||||
{
|
|
||||||
return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, GpuAccessor.QueryBindingTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureDescriptor[] GetImageDescriptors()
|
|
||||||
{
|
|
||||||
return _cachedImageDescriptors ??= GetTextureOrImageDescriptors(_usedImages, GpuAccessor.QueryBindingImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TextureDescriptor[] GetTextureOrImageDescriptors(Dictionary<TextureInfo, TextureMeta> dict, Func<int, bool, int> getBindingCallback)
|
|
||||||
{
|
|
||||||
var descriptors = new TextureDescriptor[dict.Count];
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
foreach (var kv in dict.OrderBy(x => x.Key.Indexed).ThenBy(x => x.Key.Handle))
|
|
||||||
{
|
|
||||||
var info = kv.Key;
|
|
||||||
var meta = kv.Value;
|
|
||||||
|
|
||||||
bool isBuffer = (meta.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
|
|
||||||
int binding = getBindingCallback(i, isBuffer);
|
|
||||||
|
|
||||||
descriptors[i] = new TextureDescriptor(binding, meta.Type, info.Format, info.CbufSlot, info.Handle);
|
|
||||||
descriptors[i].SetFlag(meta.UsageFlags);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return descriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp)
|
|
||||||
{
|
|
||||||
TextureDescriptor[] descriptors = GetTextureDescriptors();
|
|
||||||
|
|
||||||
for (int i = 0; i < descriptors.Length; i++)
|
|
||||||
{
|
|
||||||
var descriptor = descriptors[i];
|
|
||||||
|
|
||||||
if (descriptor.CbufSlot == texOp.CbufSlot &&
|
|
||||||
descriptor.HandleIndex == texOp.Handle &&
|
|
||||||
descriptor.Format == texOp.Format)
|
|
||||||
{
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int FindDescriptorIndex(TextureDescriptor[] array, TextureOperation texOp, bool ignoreType = false)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < array.Length; i++)
|
|
||||||
{
|
|
||||||
var descriptor = array[i];
|
|
||||||
|
|
||||||
if ((descriptor.Type == texOp.Type || ignoreType) &&
|
|
||||||
descriptor.CbufSlot == texOp.CbufSlot &&
|
|
||||||
descriptor.HandleIndex == texOp.Handle &&
|
|
||||||
descriptor.Format == texOp.Format)
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int FindTextureDescriptorIndex(TextureOperation texOp, bool ignoreType = false)
|
|
||||||
{
|
|
||||||
return FindDescriptorIndex(GetTextureDescriptors(), texOp, ignoreType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int FindImageDescriptorIndex(TextureOperation texOp)
|
|
||||||
{
|
|
||||||
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
|
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
|
||||||
{
|
{
|
||||||
return new ShaderProgramInfo(
|
return new ShaderProgramInfo(
|
||||||
ResourceManager.GetConstantBufferDescriptors(),
|
ResourceManager.GetConstantBufferDescriptors(),
|
||||||
ResourceManager.GetStorageBufferDescriptors(),
|
ResourceManager.GetStorageBufferDescriptors(),
|
||||||
GetTextureDescriptors(),
|
ResourceManager.GetTextureDescriptors(),
|
||||||
GetImageDescriptors(),
|
ResourceManager.GetImageDescriptors(),
|
||||||
identification,
|
identification,
|
||||||
GpLayerInputAttribute,
|
GpLayerInputAttribute,
|
||||||
Stage,
|
Stage,
|
||||||
|
@@ -315,7 +315,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
features2.Features.ShaderStorageImageMultisample,
|
features2.Features.ShaderStorageImageMultisample,
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||||
features2.Features.MultiViewport,
|
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
|
||||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||||
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
||||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
||||||
|
@@ -21,7 +21,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using RightsId = LibHac.Fs.RightsId;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
|
@@ -151,9 +151,23 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
|||||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||||
SwapBuffers();
|
SwapBuffers();
|
||||||
|
|
||||||
|
if (IsFullscreen)
|
||||||
|
{
|
||||||
|
// NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow.
|
||||||
|
// we might have to amend this if people run this on a non-primary display set to a different resolution.
|
||||||
|
SDL_Rect displayBounds;
|
||||||
|
SDL_GetDisplayBounds(0, out displayBounds);
|
||||||
|
|
||||||
|
Renderer?.Window.SetSize(displayBounds.w, displayBounds.h);
|
||||||
|
MouseDriver.SetClientSize(displayBounds.w, displayBounds.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void InitializeRenderer() { }
|
protected override void InitializeRenderer() { }
|
||||||
|
|
||||||
|
@@ -14,6 +14,9 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
|
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
|
||||||
public string UserProfile { get; set; }
|
public string UserProfile { get; set; }
|
||||||
|
|
||||||
|
[Option("fullscreen", Required = false, HelpText = "Launch the game in fullscreen mode.")]
|
||||||
|
public bool IsFullscreen { get; set; }
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
|
||||||
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
||||||
|
@@ -64,6 +64,9 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
Version = ReleaseInformation.GetVersion();
|
Version = ReleaseInformation.GetVersion();
|
||||||
|
|
||||||
|
// Make process DPI aware for proper window sizing on high-res screens.
|
||||||
|
ForceDpiAware.Windows();
|
||||||
|
|
||||||
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
|
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
|
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
|
||||||
@@ -592,6 +595,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
|
_window.IsFullscreen = options.IsFullscreen;
|
||||||
|
|
||||||
_emulationContext = InitializeEmulationContext(window, renderer, options);
|
_emulationContext = InitializeEmulationContext(window, renderer, options);
|
||||||
|
|
||||||
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
||||||
|
@@ -55,6 +55,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
public IHostUiTheme HostUiTheme { get; }
|
public IHostUiTheme HostUiTheme { get; }
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
|
public bool IsFullscreen { get; set; }
|
||||||
|
|
||||||
protected SDL2MouseDriver MouseDriver;
|
protected SDL2MouseDriver MouseDriver;
|
||||||
private readonly InputManager _inputManager;
|
private readonly InputManager _inputManager;
|
||||||
@@ -158,7 +159,9 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||||
|
|
||||||
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags());
|
SDL_WindowFlags fullscreenFlag = IsFullscreen ? SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
||||||
|
|
||||||
|
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | fullscreenFlag | GetWindowFlags());
|
||||||
|
|
||||||
if (WindowHandle == IntPtr.Zero)
|
if (WindowHandle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
@@ -185,10 +188,16 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
switch (evnt.window.windowEvent)
|
switch (evnt.window.windowEvent)
|
||||||
{
|
{
|
||||||
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
|
||||||
|
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
|
||||||
|
// As we don't need this to fire in either case we can test for isFullscreen.
|
||||||
|
if (!IsFullscreen)
|
||||||
|
{
|
||||||
Width = evnt.window.data1;
|
Width = evnt.window.data1;
|
||||||
Height = evnt.window.data2;
|
Height = evnt.window.data2;
|
||||||
Renderer?.Window.SetSize(Width, Height);
|
Renderer?.Window.SetSize(Width, Height);
|
||||||
MouseDriver.SetClientSize(Width, Height);
|
MouseDriver.SetClientSize(Width, Height);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
|
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
|
||||||
|
Reference in New Issue
Block a user