Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
121296834a | ||
|
bbb24d8c7e | ||
|
4da44e09cb | ||
|
ae13f0ab4d | ||
|
a2a35f1be6 | ||
|
aedfadaaf7 | ||
|
5c0fb0cec3 | ||
|
17a1cab5d2 | ||
|
73aed239c3 | ||
|
9ac66336a2 | ||
|
4965681e06 | ||
|
3868a00206 | ||
|
933e5144a9 | ||
|
73a42c85c4 | ||
|
39ba11054b | ||
|
c250e3392c | ||
|
e56b069081 | ||
|
204c031fef | ||
|
d9053bbe37 |
@@ -89,6 +89,7 @@ csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
struct AllocationResult
|
||||
readonly struct AllocationResult
|
||||
{
|
||||
public int IntUsedRegisters { get; }
|
||||
public int VecUsedRegisters { get; }
|
||||
|
@@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
private class ParallelCopy
|
||||
{
|
||||
private struct Copy
|
||||
private readonly struct Copy
|
||||
{
|
||||
public Register Dest { get; }
|
||||
public Register Source { get; }
|
||||
|
@@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
class HybridAllocator : IRegisterAllocator
|
||||
{
|
||||
private struct BlockInfo
|
||||
private readonly struct BlockInfo
|
||||
{
|
||||
public bool HasCall { get; }
|
||||
|
||||
|
@@ -3,7 +3,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
struct RegisterMasks
|
||||
readonly struct RegisterMasks
|
||||
{
|
||||
public int IntAvailableRegisters { get; }
|
||||
public int VecAvailableRegisters { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
struct IntrinsicInfo
|
||||
readonly struct IntrinsicInfo
|
||||
{
|
||||
public X86Instruction Inst { get; }
|
||||
public IntrinsicType Type { get; }
|
||||
|
@@ -2,7 +2,7 @@ using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
struct InstDescriptor
|
||||
readonly struct InstDescriptor
|
||||
{
|
||||
public static InstDescriptor Undefined => new InstDescriptor(InstName.Und, InstEmit.Und);
|
||||
|
||||
|
@@ -11,7 +11,7 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
private const int FastLookupSize = 0x1000;
|
||||
|
||||
private struct InstInfo
|
||||
private readonly struct InstInfo
|
||||
{
|
||||
public int Mask { get; }
|
||||
public int Value { get; }
|
||||
|
@@ -6,7 +6,7 @@ namespace ARMeilleure.Diagnostics
|
||||
{
|
||||
static class Symbols
|
||||
{
|
||||
private struct RangedSymbol
|
||||
private readonly struct RangedSymbol
|
||||
{
|
||||
public readonly ulong Start;
|
||||
public readonly ulong End;
|
||||
|
@@ -3,7 +3,7 @@ using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
struct PhiOperation
|
||||
readonly struct PhiOperation
|
||||
{
|
||||
private readonly Operation _operation;
|
||||
|
||||
|
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
struct Register : IEquatable<Register>
|
||||
readonly struct Register : IEquatable<Register>
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
|
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
struct CacheEntry : IComparable<CacheEntry>
|
||||
readonly struct CacheEntry : IComparable<CacheEntry>
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
@@ -6,7 +6,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
class CacheMemoryAllocator
|
||||
{
|
||||
private struct MemoryBlock : IComparable<MemoryBlock>
|
||||
private readonly struct MemoryBlock : IComparable<MemoryBlock>
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
@@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
struct CompilerContext
|
||||
readonly struct CompilerContext
|
||||
{
|
||||
public ControlFlowGraph Cfg { get; }
|
||||
|
||||
|
@@ -14,7 +14,7 @@ namespace ARMeilleure.Translation
|
||||
private const int RegsCount = 32;
|
||||
private const int RegsMask = RegsCount - 1;
|
||||
|
||||
private struct RegisterMask : IEquatable<RegisterMask>
|
||||
private readonly struct RegisterMask : IEquatable<RegisterMask>
|
||||
{
|
||||
public long IntMask => Mask.GetElement(0);
|
||||
public long VecMask => Mask.GetElement(1);
|
||||
|
@@ -293,7 +293,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
private struct Range
|
||||
private readonly struct Range
|
||||
{
|
||||
public ulong Start { get; }
|
||||
public ulong End { get; }
|
||||
|
@@ -21,6 +21,10 @@
|
||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://crwd.in/ryujinx">
|
||||
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://discord.com/invite/VkQYXAZ">
|
||||
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
@@ -48,6 +52,8 @@ See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ry
|
||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||
|
||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||
|
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOChannelLayout
|
||||
public readonly struct SoundIOChannelLayout
|
||||
{
|
||||
public static int BuiltInCount
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOSampleRateRange
|
||||
public readonly struct SoundIOSampleRateRange
|
||||
{
|
||||
internal SoundIOSampleRateRange(int min, int max)
|
||||
{
|
||||
|
@@ -596,7 +596,18 @@
|
||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||
"VolumeShort": "Vol",
|
||||
"SettingsEnableMacroHLE": "Enable Macro HLE",
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure."
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
|
||||
"VolumeShort": "Vol",
|
||||
"UserProfilesManageSaves": "Manage Saves",
|
||||
"DeleteUserSave": "Do you want to delete user save for this game?",
|
||||
"IrreversibleActionNote": "This action is not reversible.",
|
||||
"SaveManagerHeading": "Manage Saves for {0}",
|
||||
"SaveManagerTitle": "Save Manager",
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
"Search": "Search",
|
||||
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
|
||||
}
|
||||
|
@@ -41,6 +41,9 @@
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
||||
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
|
||||
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
|
||||
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
||||
@@ -55,5 +58,7 @@
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@@ -50,5 +50,7 @@
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@@ -1,7 +1,6 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Height="2000" Padding="20">
|
||||
@@ -269,13 +268,15 @@
|
||||
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
||||
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
|
||||
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
|
||||
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
|
||||
<sys:Double x:Key="FontSize">12</sys:Double>
|
||||
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
|
||||
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
|
||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||
<x:Double x:Key="FontSize">12</x:Double>
|
||||
<x:Double x:Key="FontSizeLarge">15</x:Double>
|
||||
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
|
||||
<x:Double x:Key="MenuItemHeight">26</x:Double>
|
||||
<x:Double x:Key="TabItemMinHeight">28</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
|
||||
return;
|
||||
}
|
||||
|
||||
OpenSaveDir(saveDataId);
|
||||
}
|
||||
|
||||
public static void OpenSaveDir(ulong saveDataId)
|
||||
{
|
||||
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
||||
|
||||
if (!Directory.Exists(saveRootPath))
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@@ -8,6 +9,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
@@ -94,6 +96,9 @@ namespace Ryujinx.Ava
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
// Initialize SDL2 driver
|
||||
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
ForceDpiAware.Windows();
|
||||
|
@@ -11,7 +11,8 @@
|
||||
Height="340"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -6,7 +6,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Width="400"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -10,7 +10,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@@ -113,8 +114,8 @@
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
@@ -132,27 +133,18 @@
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
@@ -160,57 +152,41 @@
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
<Panel
|
||||
Grid.Row="1"
|
||||
Height="50"
|
||||
Margin="5"
|
||||
Margin="0 10 0 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
Margin="5,5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Yellow"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
@@ -10,7 +10,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@@ -115,8 +116,8 @@
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
@@ -134,6 +135,12 @@
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
@@ -152,9 +159,6 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
@@ -169,7 +173,7 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
Spacing="5" >
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
@@ -214,20 +218,10 @@
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Yellow"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@@ -4,7 +4,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -4,7 +4,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost"
|
||||
Focusable="True">
|
||||
<ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
x:Name="ContentFrame" />
|
||||
</UserControl>
|
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public HorizonClient HorizonClient { get; }
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public NavigationDialogHost()
|
||||
@@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
|
||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem)
|
||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
HorizonClient = horizonClient;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
|
||||
@@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
ContentFrame.Navigate(sourcePageType, parameter);
|
||||
}
|
||||
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||
{
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||
|
@@ -4,7 +4,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
|
||||
Focusable="True">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@@ -3,5 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost"
|
||||
Focusable="True">
|
||||
</UserControl>
|
||||
|
103
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
103
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
@@ -0,0 +1,103 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Height="400"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Name}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderAscending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Descending}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
|
||||
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
<Grid HorizontalAlignment="Stretch" Margin="0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Border Height="42" Margin="2" Width="42" Padding="10"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image IsVisible="{Binding InGameList}"
|
||||
Margin="2"
|
||||
Width="42"
|
||||
Height="42"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
|
||||
Text="{Binding Title}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
|
||||
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
|
||||
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Avalonia.Controls;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class SaveManager : UserControl
|
||||
{
|
||||
private readonly UserProfile _userProfile;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private int _sortIndex;
|
||||
private int _orderIndex;
|
||||
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||
private string _search;
|
||||
|
||||
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||
|
||||
public ObservableCollection<SaveModel> View
|
||||
{
|
||||
get => _view;
|
||||
set => _view = value;
|
||||
}
|
||||
|
||||
public int SortIndex
|
||||
{
|
||||
get => _sortIndex;
|
||||
set
|
||||
{
|
||||
_sortIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public int OrderIndex
|
||||
{
|
||||
get => _orderIndex;
|
||||
set
|
||||
{
|
||||
_orderIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public SaveManager()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_userProfile = userProfile;
|
||||
_horizonClient = horizonClient;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = this;
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
|
||||
public void LoadSaves()
|
||||
{
|
||||
Saves.Clear();
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
Saves.Add(saveModel);
|
||||
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
|
||||
}
|
||||
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
Saves.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Sort(GetComparer())
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_view.Clear();
|
||||
_view.AddRange(view);
|
||||
}
|
||||
|
||||
private IComparer<SaveModel> GetComparer()
|
||||
{
|
||||
switch (SortIndex)
|
||||
{
|
||||
case 0:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
||||
case 1:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is SaveModel save)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,7 +8,8 @@
|
||||
Title="Ryujinx - Waiting"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -10,8 +10,10 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
@@ -63,7 +65,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
@@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
_isNewUser = args.isNewUser;
|
||||
if (!_isNewUser)
|
||||
{
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempProfile = new TempProfile();
|
||||
}
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
@@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
DataContext = TempProfile;
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
IdLabel.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (_profile != null)
|
||||
if (_profile != null && !_isNewUser)
|
||||
{
|
||||
_profile.Name = TempProfile.Name;
|
||||
_profile.Image = TempProfile.Image;
|
||||
@@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
else if (_isNewUser)
|
||||
{
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
71
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
71
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
@@ -0,0 +1,71 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0"
|
||||
Margin="5"
|
||||
Height="30"
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding GoBack}">
|
||||
<ui:SymbolIcon Symbol="Back"/>
|
||||
</Button>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding LostProfiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding Recover}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Locale:Locale Recover}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class UserRecoverer : UserControl
|
||||
{
|
||||
private UserProfileViewModel _viewModel;
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserRecoverer()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
|
||||
|
||||
_viewModel = args.viewModel;
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,8 +10,10 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
@@ -25,6 +27,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DoubleTapped="ProfilesList_DoubleTapped"
|
||||
@@ -88,21 +91,56 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding AddUser}"
|
||||
Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Margin="2"
|
||||
Grid.Column="1"
|
||||
Command="{Binding EditUser}"
|
||||
Content="{Locale:Locale UserProfilesEditProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Content="{Locale:Locale UserProfilesManageSaves}"
|
||||
Command="{Binding ManageSaves}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="2"
|
||||
Command="{Binding DeleteUser}"
|
||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
||||
</StackPanel>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding RecoverLostAccounts}"
|
||||
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class SaveModel : BaseModel
|
||||
{
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private long _size;
|
||||
|
||||
public Action DeleteAction { get; set; }
|
||||
public ulong SaveId { get; }
|
||||
public ProgramId TitleId { get; }
|
||||
public string TitleIdString => $"{TitleId.Value:X16}";
|
||||
public UserId UserId { get; }
|
||||
public bool InGameList { get; }
|
||||
public string Title { get; }
|
||||
public byte[] Icon { get; }
|
||||
|
||||
public long Size
|
||||
{
|
||||
get => _size; set
|
||||
{
|
||||
_size = value;
|
||||
SizeAvailable = true;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SizeString));
|
||||
OnPropertyChanged(nameof(SizeAvailable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SizeAvailable { get; set; }
|
||||
|
||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
||||
|
||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
SaveId = info.SaveDataId;
|
||||
TitleId = info.ProgramId;
|
||||
UserId = info.UserId;
|
||||
|
||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
||||
|
||||
InGameList = appData != null;
|
||||
|
||||
if (InGameList)
|
||||
{
|
||||
Icon = appData.Icon;
|
||||
Title = appData.TitleName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
|
||||
Title = appMetadata.Title ?? TitleIdString;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
||||
|
||||
long total_size = GetDirectorySize(saveRoot);
|
||||
long GetDirectorySize(string path)
|
||||
{
|
||||
long size = 0;
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var directories = Directory.GetDirectories(path);
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
size += GetDirectorySize(directory);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(path);
|
||||
foreach (var file in files)
|
||||
{
|
||||
size += new FileInfo(file).Length;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
Size = total_size;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void OpenLocation()
|
||||
{
|
||||
ApplicationHelper.OpenSaveDir(SaveId);
|
||||
}
|
||||
|
||||
public async void Delete()
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
|
||||
LocaleManager.Instance["IrreversibleActionNote"],
|
||||
LocaleManager.Instance["InputDialogYes"],
|
||||
LocaleManager.Instance["InputDialogNo"], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
|
||||
|
||||
DeleteAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,10 +45,13 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
_profile = profile;
|
||||
|
||||
if (_profile != null)
|
||||
{
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
public TempProfile(){}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
@@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
public class UserProfile : BaseModel
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private readonly NavigationDialogHost _owner;
|
||||
private byte[] _image;
|
||||
private string _name;
|
||||
private UserId _userId;
|
||||
@@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
}
|
||||
}
|
||||
|
||||
public UserProfile(Profile profile)
|
||||
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||
{
|
||||
_profile = profile;
|
||||
_owner = owner;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
@@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
OnPropertyChanged(nameof(IsOpened));
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
public void Recover(UserProfile userProfile)
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
||||
}
|
||||
}
|
||||
}
|
@@ -76,6 +76,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
private bool _showAll;
|
||||
private string _lastScannedAmiiboId;
|
||||
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
||||
public ApplicationLibrary ApplicationLibrary => _owner.ApplicationLibrary;
|
||||
|
||||
public string TitleName { get; internal set; }
|
||||
|
||||
@@ -103,8 +104,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
|
||||
Ptc.PtcStateChanged -= ProgressHandler;
|
||||
Ptc.PtcStateChanged += ProgressHandler;
|
||||
@@ -435,8 +436,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public Thickness GridItemPadding => ShowNames ? new Thickness() : new Thickness(5);
|
||||
|
||||
public bool ShowMenuAndStatusBar
|
||||
{
|
||||
get => _showMenuAndStatusBar;
|
||||
@@ -598,7 +597,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
ConfigurationState.Instance.Ui.ShowNames.Value = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(GridItemPadding));
|
||||
OnPropertyChanged(nameof(GridSizeScale));
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
@@ -715,7 +713,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
OnPropertyChanged(nameof(IsGridLarge));
|
||||
OnPropertyChanged(nameof(IsGridHuge));
|
||||
OnPropertyChanged(nameof(ShowNames));
|
||||
OnPropertyChanged(nameof(GridItemPadding));
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
@@ -779,6 +776,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
_owner.LoadProgressBar.IsVisible = false;
|
||||
}
|
||||
|
||||
if (e.NumAppsLoaded == e.NumAppsFound)
|
||||
{
|
||||
_owner.LoadProgressBar.IsVisible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -817,7 +819,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
Thread thread = new(() =>
|
||||
{
|
||||
_owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
||||
|
||||
_isLoading = false;
|
||||
})
|
||||
@@ -1005,7 +1007,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public async void ManageProfiles()
|
||||
{
|
||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
|
||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient);
|
||||
}
|
||||
|
||||
public async void OpenAboutWindow()
|
||||
@@ -1098,7 +1100,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
selection.Favorite = !selection.Favorite;
|
||||
|
||||
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
{
|
||||
appMetadata.Favorite = selection.Favorite;
|
||||
});
|
||||
|
@@ -1,8 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
@@ -19,6 +25,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = new ObservableCollection<UserProfile>();
|
||||
LostProfiles = new ObservableCollection<UserProfile>();
|
||||
}
|
||||
|
||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
||||
@@ -30,6 +37,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||
|
||||
public UserProfile SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
@@ -65,12 +74,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public void LoadProfiles()
|
||||
{
|
||||
Profiles.Clear();
|
||||
LostProfiles.Clear();
|
||||
|
||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
Profiles.Add(new UserProfile(profile));
|
||||
Profiles.Add(new UserProfile(profile, _owner));
|
||||
}
|
||||
|
||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
||||
@@ -84,6 +94,42 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
default, saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new HashSet<HLE.HOS.Services.Account.Acc.UserId>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
|
||||
{
|
||||
lostAccounts.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var account in lostAccounts)
|
||||
{
|
||||
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUser()
|
||||
@@ -93,6 +139,25 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
||||
}
|
||||
|
||||
public async void ManageSaves()
|
||||
{
|
||||
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
||||
|
||||
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||
Content = manager,
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
|
||||
public void EditUser()
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
||||
@@ -134,5 +199,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_owner.GoBack();
|
||||
}
|
||||
|
||||
public void RecoverLostAccounts()
|
||||
{
|
||||
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,8 @@
|
||||
CanResize="False"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -11,7 +11,8 @@
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="800" MinHeight="650" Height="650"
|
||||
SizeToContent="Manual"
|
||||
MinWidth="600">
|
||||
MinWidth="600"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:AmiiboWindowViewModel />
|
||||
</Design.DataContext>
|
||||
|
@@ -11,7 +11,8 @@
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:AvatarProfileViewModel">
|
||||
x:DataType="viewModels:AvatarProfileViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:AvatarProfileViewModel />
|
||||
</Design.DataContext>
|
||||
|
@@ -12,7 +12,8 @@
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Window.Styles>
|
||||
<Style Selector="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="True" />
|
||||
|
@@ -8,7 +8,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.ContentDialogOverlayWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="ContentDialogOverlayWindow">
|
||||
Title="ContentDialogOverlayWindow"
|
||||
Focusable="True">
|
||||
<window:StyleableWindow.Styles>
|
||||
<Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
|
||||
<Setter Property="Background"
|
||||
|
@@ -13,7 +13,8 @@
|
||||
d:DesignHeight="800"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="False"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:ControllerSettingsViewModel />
|
||||
</Design.DataContext>
|
||||
|
@@ -14,7 +14,8 @@
|
||||
MaxHeight="500"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid Name="DownloadableContentGrid" Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
|
||||
private const int CutOffLuminosity = 64;
|
||||
|
||||
private struct PaletteColor
|
||||
private readonly struct PaletteColor
|
||||
{
|
||||
public int Qck { get; }
|
||||
public byte R { get; }
|
||||
|
@@ -20,7 +20,8 @@
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:MainWindowViewModel"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Window.Styles>
|
||||
<Style Selector="TitleBar:fullscreen">
|
||||
<Setter Property="Background" Value="#000000" />
|
||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public partial class MainWindow : StyleableWindow
|
||||
{
|
||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||
private bool _canUpdate;
|
||||
private bool _isClosing;
|
||||
private bool _isLoading;
|
||||
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
ViewModel = new MainWindowViewModel(this);
|
||||
|
||||
MainWindowViewModel = ViewModel;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
@@ -263,6 +266,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
return;
|
||||
}
|
||||
|
||||
CanUpdate = false;
|
||||
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
|
||||
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
|
||||
|
||||
@@ -368,6 +372,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
ViewModel.ShowContent = true;
|
||||
ViewModel.ShowLoadProgress = false;
|
||||
ViewModel.IsLoadingIndeterminate = false;
|
||||
CanUpdate = true;
|
||||
Cursor = Cursor.Default;
|
||||
|
||||
if (MainContent.Content != _mainViewContent)
|
||||
|
@@ -6,7 +6,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.MotionSettingsWindow">
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.MotionSettingsWindow"
|
||||
Focusable="True">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@@ -6,7 +6,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.RumbleSettingsWindow">
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.RumbleSettingsWindow"
|
||||
Focusable="True">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@@ -18,7 +18,8 @@
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:SettingsViewModel"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
|
@@ -14,7 +14,8 @@
|
||||
MaxHeight="400"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@@ -16,7 +16,7 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
public static event EventHandler<LogEventArgs> Updated;
|
||||
|
||||
public struct Log
|
||||
public readonly struct Log
|
||||
{
|
||||
internal readonly LogLevel Level;
|
||||
|
||||
|
@@ -7,7 +7,7 @@ namespace Ryujinx.Common.Memory
|
||||
/// This is useful to keep the Array representation when possible to avoid copies.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Element Type</typeparam>
|
||||
public ref struct SpanOrArray<T> where T : unmanaged
|
||||
public readonly ref struct SpanOrArray<T> where T : unmanaged
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly ReadOnlySpan<T> Span;
|
||||
|
@@ -28,9 +28,39 @@ namespace Ryujinx.Common.SystemInfo
|
||||
|
||||
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
||||
RamTotal = totalRAM;
|
||||
RamAvailable = GetVMInfoAvailableMemory();
|
||||
}
|
||||
|
||||
[DllImport("libSystem.dylib", CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
static ulong GetVMInfoAvailableMemory()
|
||||
{
|
||||
var port = mach_host_self();
|
||||
|
||||
uint pageSize = 0;
|
||||
var result = host_page_size(port, ref pageSize);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int flavor = 4; // HOST_VM_INFO64
|
||||
uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT
|
||||
VMStatistics64 stats = new();
|
||||
result = host_statistics64(port, flavor, ref stats, ref count);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize;
|
||||
}
|
||||
|
||||
private const string SystemLibraryName = "libSystem.dylib";
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||
|
||||
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
|
||||
@@ -85,5 +115,43 @@ namespace Ryujinx.Common.SystemInfo
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern uint mach_host_self();
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int host_page_size(uint host, ref uint out_page_size);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
struct VMStatistics64
|
||||
{
|
||||
public uint FreeCount;
|
||||
public uint ActiveCount;
|
||||
public uint InactiveCount;
|
||||
public uint WireCount;
|
||||
public ulong ZeroFillCount;
|
||||
public ulong Reactivations;
|
||||
public ulong Pageins;
|
||||
public ulong Pageouts;
|
||||
public ulong Faults;
|
||||
public ulong CowFaults;
|
||||
public ulong Lookups;
|
||||
public ulong Hits;
|
||||
public ulong Purges;
|
||||
public uint PurgeableCount;
|
||||
public uint SpeculativeCount;
|
||||
public ulong Decompressions;
|
||||
public ulong Compressions;
|
||||
public ulong Swapins;
|
||||
public ulong Swapouts;
|
||||
public uint CompressorPageCount;
|
||||
public uint ThrottledCount;
|
||||
public uint ExternalPageCount;
|
||||
public uint InternalPageCount;
|
||||
public ulong TotalUncompressedPagesInCompressor;
|
||||
}
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt);
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Stores handlers for the various CPU exceptions.
|
||||
/// </summary>
|
||||
public struct ExceptionCallbacks
|
||||
public readonly struct ExceptionCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/>.
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Device
|
||||
{
|
||||
public struct RwCallback
|
||||
public readonly struct RwCallback
|
||||
{
|
||||
public Action<int> Write { get; }
|
||||
public Func<int> Read { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct BlendDescriptor
|
||||
public readonly struct BlendDescriptor
|
||||
{
|
||||
public bool Enable { get; }
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct BufferAssignment
|
||||
public readonly struct BufferAssignment
|
||||
{
|
||||
public readonly int Binding;
|
||||
public readonly BufferRange Range;
|
||||
|
@@ -1,22 +1,14 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 8)]
|
||||
public struct BufferHandle : IEquatable<BufferHandle>
|
||||
public readonly record struct BufferHandle
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
public static BufferHandle Null => new BufferHandle(0);
|
||||
|
||||
private BufferHandle(ulong value) => _value = value;
|
||||
|
||||
public override bool Equals(object obj) => obj is BufferHandle handle && Equals(handle);
|
||||
public bool Equals([AllowNull] BufferHandle other) => other._value == _value;
|
||||
public override int GetHashCode() => _value.GetHashCode();
|
||||
public static bool operator ==(BufferHandle left, BufferHandle right) => left.Equals(right);
|
||||
public static bool operator !=(BufferHandle left, BufferHandle right) => !(left == right);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct BufferRange
|
||||
public readonly struct BufferRange
|
||||
{
|
||||
private static readonly BufferRange _empty = new BufferRange(BufferHandle.Null, 0, 0);
|
||||
|
||||
|
@@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Capabilities
|
||||
public readonly struct Capabilities
|
||||
{
|
||||
public readonly TargetApi Api;
|
||||
public readonly string VendorName;
|
||||
@@ -17,6 +17,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool Supports3DTextureCompression;
|
||||
public readonly bool SupportsBgraFormat;
|
||||
public readonly bool SupportsR4G4Format;
|
||||
public readonly bool SupportsSnormBufferTextureFormat;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
@@ -52,6 +53,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supports3DTextureCompression,
|
||||
bool supportsBgraFormat,
|
||||
bool supportsR4G4Format,
|
||||
bool supportsSnormBufferTextureFormat,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsFragmentShaderOrderingIntel,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
@@ -84,6 +86,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
Supports3DTextureCompression = supports3DTextureCompression;
|
||||
SupportsBgraFormat = supportsBgraFormat;
|
||||
SupportsR4G4Format = supportsR4G4Format;
|
||||
SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
|
@@ -1,32 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ColorF : IEquatable<ColorF>
|
||||
{
|
||||
public float Red { get; }
|
||||
public float Green { get; }
|
||||
public float Blue { get; }
|
||||
public float Alpha { get; }
|
||||
|
||||
public ColorF(float red, float green, float blue, float alpha)
|
||||
{
|
||||
Red = red;
|
||||
Green = green;
|
||||
Blue = blue;
|
||||
Alpha = alpha;
|
||||
}
|
||||
|
||||
public bool Equals(ColorF color) => Red == color.Red &&
|
||||
Green == color.Green &&
|
||||
Blue == color.Blue &&
|
||||
Alpha == color.Alpha;
|
||||
|
||||
public override bool Equals(object obj) => (obj is ColorF color) && Equals(color);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Red, Green, Blue, Alpha);
|
||||
|
||||
public static bool operator ==(ColorF l, ColorF r) => l.Equals(r);
|
||||
public static bool operator !=(ColorF l, ColorF r) => !l.Equals(r);
|
||||
}
|
||||
public readonly record struct ColorF(float Red, float Green, float Blue, float Alpha);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct DepthTestDescriptor
|
||||
public readonly struct DepthTestDescriptor
|
||||
{
|
||||
public bool TestEnable { get; }
|
||||
public bool WriteEnable { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct DeviceInfo
|
||||
public readonly struct DeviceInfo
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly string Vendor;
|
||||
|
@@ -2,7 +2,7 @@ using Ryujinx.Common;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Extents2D
|
||||
public readonly struct Extents2D
|
||||
{
|
||||
public int X1 { get; }
|
||||
public int Y1 { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Extents2DF
|
||||
public readonly struct Extents2DF
|
||||
{
|
||||
public float X1 { get; }
|
||||
public float Y1 { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct HardwareInfo
|
||||
public readonly struct HardwareInfo
|
||||
{
|
||||
public string GpuVendor { get; }
|
||||
public string GpuModel { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ImageCrop
|
||||
public readonly struct ImageCrop
|
||||
{
|
||||
public int Left { get; }
|
||||
public int Right { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct MultisampleDescriptor
|
||||
public readonly struct MultisampleDescriptor
|
||||
{
|
||||
public bool AlphaToCoverageEnable { get; }
|
||||
public bool AlphaToCoverageDitherEnable { get; }
|
||||
|
@@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
/// <summary>
|
||||
/// Descriptor for a pipeline buffer binding.
|
||||
/// </summary>
|
||||
public struct BufferPipelineDescriptor
|
||||
public readonly struct BufferPipelineDescriptor
|
||||
{
|
||||
public bool Enable { get; }
|
||||
public int Stride { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Rectangle<T> where T : unmanaged
|
||||
public readonly struct Rectangle<T> where T : unmanaged
|
||||
{
|
||||
public T X { get; }
|
||||
public T Y { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct SamplerCreateInfo
|
||||
public readonly struct SamplerCreateInfo
|
||||
{
|
||||
public MinFilter MinFilter { get; }
|
||||
public MagFilter MagFilter { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ScreenCaptureImageInfo
|
||||
public readonly struct ScreenCaptureImageInfo
|
||||
{
|
||||
public ScreenCaptureImageInfo(int width, int height, bool isBgra, byte[] data, bool flipX, bool flipY)
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ShaderBindings
|
||||
public readonly struct ShaderBindings
|
||||
{
|
||||
public IReadOnlyCollection<int> UniformBufferBindings { get; }
|
||||
public IReadOnlyCollection<int> StorageBufferBindings { get; }
|
||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ShaderSource
|
||||
public readonly struct ShaderSource
|
||||
{
|
||||
public string Code { get; }
|
||||
public byte[] BinaryCode { get; }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct StencilTestDescriptor
|
||||
public readonly struct StencilTestDescriptor
|
||||
{
|
||||
public bool TestEnable { get; }
|
||||
|
||||
|
@@ -4,7 +4,7 @@ using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct TextureCreateInfo : IEquatable<TextureCreateInfo>
|
||||
public readonly struct TextureCreateInfo : IEquatable<TextureCreateInfo>
|
||||
{
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
@@ -62,42 +62,42 @@ namespace Ryujinx.Graphics.GAL
|
||||
SwizzleA = swizzleA;
|
||||
}
|
||||
|
||||
public readonly int GetMipSize(int level)
|
||||
public int GetMipSize(int level)
|
||||
{
|
||||
return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level);
|
||||
}
|
||||
|
||||
public readonly int GetMipSize2D(int level)
|
||||
public int GetMipSize2D(int level)
|
||||
{
|
||||
return GetMipStride(level) * GetLevelHeight(level);
|
||||
}
|
||||
|
||||
public readonly int GetMipStride(int level)
|
||||
public int GetMipStride(int level)
|
||||
{
|
||||
return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4);
|
||||
}
|
||||
|
||||
private readonly int GetLevelWidth(int level)
|
||||
private int GetLevelWidth(int level)
|
||||
{
|
||||
return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth);
|
||||
}
|
||||
|
||||
private readonly int GetLevelHeight(int level)
|
||||
private int GetLevelHeight(int level)
|
||||
{
|
||||
return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight);
|
||||
}
|
||||
|
||||
private readonly int GetLevelDepth(int level)
|
||||
private int GetLevelDepth(int level)
|
||||
{
|
||||
return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers();
|
||||
}
|
||||
|
||||
public readonly int GetDepthOrLayers()
|
||||
public int GetDepthOrLayers()
|
||||
{
|
||||
return Target == Target.Texture3D ? Depth : GetLayers();
|
||||
}
|
||||
|
||||
public readonly int GetLayers()
|
||||
public int GetLayers()
|
||||
{
|
||||
if (Target == Target.Texture2DArray ||
|
||||
Target == Target.Texture2DMultisampleArray ||
|
||||
@@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
return 1;
|
||||
}
|
||||
|
||||
public readonly int GetLevelsClamped()
|
||||
public int GetLevelsClamped()
|
||||
{
|
||||
int maxSize = Width;
|
||||
|
||||
|
@@ -1,40 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct VertexAttribDescriptor : IEquatable<VertexAttribDescriptor>
|
||||
{
|
||||
public int BufferIndex { get; }
|
||||
public int Offset { get; }
|
||||
|
||||
public bool IsZero { get; }
|
||||
|
||||
public Format Format { get; }
|
||||
|
||||
public VertexAttribDescriptor(int bufferIndex, int offset, bool isZero, Format format)
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Offset = offset;
|
||||
IsZero = isZero;
|
||||
Format = format;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is VertexAttribDescriptor other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(VertexAttribDescriptor other)
|
||||
{
|
||||
return BufferIndex == other.BufferIndex &&
|
||||
Offset == other.Offset &&
|
||||
IsZero == other.IsZero &&
|
||||
Format == other.Format;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(BufferIndex, Offset, IsZero, Format);
|
||||
}
|
||||
}
|
||||
public readonly record struct VertexAttribDescriptor(int BufferIndex, int Offset, bool IsZero, Format Format);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct VertexBufferDescriptor
|
||||
public readonly struct VertexBufferDescriptor
|
||||
{
|
||||
public BufferRange Buffer { get; }
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Viewport
|
||||
public readonly struct Viewport
|
||||
{
|
||||
public Rectangle<float> Region { get; }
|
||||
|
||||
|
@@ -202,57 +202,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
_channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
|
||||
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
|
||||
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
_channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
_channel.TextureManager.SetComputeBindings(cs.Bindings);
|
||||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
/// <summary>
|
||||
/// FIFO word.
|
||||
/// </summary>
|
||||
struct FifoWord
|
||||
readonly struct FifoWord
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address where the word is located in memory.
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
/// <summary>
|
||||
/// Macroo High-level implementation table entry.
|
||||
/// </summary>
|
||||
struct TableEntry
|
||||
readonly struct TableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the Macro function.
|
||||
|
@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly DrawState _drawState;
|
||||
private readonly SpecializationStateUpdater _currentSpecState;
|
||||
private bool _topologySet;
|
||||
|
||||
private bool _instancedDrawPending;
|
||||
@@ -44,12 +45,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Channel state</param>
|
||||
/// <param name="drawState">Draw state</param>
|
||||
public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState)
|
||||
/// <param name="spec">Specialization state updater</param>
|
||||
public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState, SpecializationStateUpdater spec)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_drawState = drawState;
|
||||
_currentSpecState = spec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -132,6 +135,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_drawState.FirstIndex = firstIndex;
|
||||
_drawState.IndexCount = indexCount;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(false);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
@@ -256,6 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
if (_drawState.Topology != topology || !_topologySet)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetPrimitiveTopology(topology);
|
||||
_currentSpecState.SetTopology(topology);
|
||||
_drawState.Topology = topology;
|
||||
_topologySet = true;
|
||||
}
|
||||
@@ -452,7 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_state.State.FirstInstance = (uint)firstInstance;
|
||||
|
||||
_drawState.DrawIndexed = indexed;
|
||||
_drawState.HasConstantBufferDrawParameters = true;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(true);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
@@ -469,7 +474,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_state.State.FirstInstance = 0;
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
_drawState.HasConstantBufferDrawParameters = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
@@ -527,7 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_drawState.DrawIndexed = indexed;
|
||||
_drawState.DrawIndirect = true;
|
||||
_drawState.HasConstantBufferDrawParameters = true;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(true);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
@@ -561,7 +565,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
_drawState.DrawIndirect = false;
|
||||
_drawState.HasConstantBufferDrawParameters = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
|
280
Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
Normal file
280
Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a "current" specialiation state, and provides a flag to check if it has changed meaningfully.
|
||||
/// </summary>
|
||||
internal class SpecializationStateUpdater
|
||||
{
|
||||
private GpuChannelGraphicsState _graphics;
|
||||
private GpuChannelPoolState _pool;
|
||||
|
||||
private bool _usesDrawParameters;
|
||||
private bool _usesTopology;
|
||||
|
||||
private bool _changed;
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the specialization state has changed.
|
||||
/// </summary>
|
||||
private void Signal()
|
||||
{
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specialization state has changed since the last check.
|
||||
/// </summary>
|
||||
/// <returns>True if it has changed, false otherwise</returns>
|
||||
public bool HasChanged()
|
||||
{
|
||||
if (_changed)
|
||||
{
|
||||
_changed = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active shader, clearing the dirty state and recording if certain specializations are noteworthy.
|
||||
/// </summary>
|
||||
/// <param name="gs">The active shader</param>
|
||||
public void SetShader(CachedShaderProgram gs)
|
||||
{
|
||||
_usesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||
_usesTopology = gs.SpecializationState.IsPrimitiveTopologyQueried();
|
||||
|
||||
_changed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current graphics state.
|
||||
/// </summary>
|
||||
/// <returns>GPU graphics state</returns>
|
||||
public ref GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
return ref _graphics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current pool state.
|
||||
/// </summary>
|
||||
/// <returns>GPU pool state</returns>
|
||||
public ref GpuChannelPoolState GetPoolState()
|
||||
{
|
||||
return ref _pool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Early Z force enable.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetEarlyZForce(bool value)
|
||||
{
|
||||
_graphics.EarlyZForce = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primitive topology of current draw.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetTopology(PrimitiveTopology value)
|
||||
{
|
||||
if (value != _graphics.Topology)
|
||||
{
|
||||
_graphics.Topology = value;
|
||||
|
||||
if (_usesTopology)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tessellation mode.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetTessellationMode(TessMode value)
|
||||
{
|
||||
if (value.Packed != _graphics.TessellationMode.Packed)
|
||||
{
|
||||
_graphics.TessellationMode = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates alpha-to-coverage state, and sets it as changed.
|
||||
/// </summary>
|
||||
/// <param name="enable">Whether alpha-to-coverage is enabled</param>
|
||||
/// <param name="ditherEnable">Whether alpha-to-coverage dithering is enabled</param>
|
||||
public void SetAlphaToCoverageEnable(bool enable, bool ditherEnable)
|
||||
{
|
||||
_graphics.AlphaToCoverageEnable = enable;
|
||||
_graphics.AlphaToCoverageDitherEnable = ditherEnable;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the viewport transform is disabled.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetViewportTransformDisable(bool value)
|
||||
{
|
||||
if (value != _graphics.ViewportTransformDisable)
|
||||
{
|
||||
_graphics.ViewportTransformDisable = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth mode zero to one or minus one to one.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetDepthMode(bool value)
|
||||
{
|
||||
if (value != _graphics.DepthMode)
|
||||
{
|
||||
_graphics.DepthMode = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the point size is set on the shader or is fixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetProgramPointSizeEnable(bool value)
|
||||
{
|
||||
if (value != _graphics.ProgramPointSizeEnable)
|
||||
{
|
||||
_graphics.ProgramPointSizeEnable = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Point size used if <see cref="SetProgramPointSizeEnable" /> is provided false.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetPointSize(float value)
|
||||
{
|
||||
if (value != _graphics.PointSize)
|
||||
{
|
||||
_graphics.PointSize = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates alpha test specialization state, and sets it as changed.
|
||||
/// </summary>
|
||||
/// <param name="enable">Whether alpha test is enabled</param>
|
||||
/// <param name="reference">The value to compare with the fragment output alpha</param>
|
||||
/// <param name="op">The comparison that decides if the fragment should be discarded</param>
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
{
|
||||
_graphics.AlphaTestEnable = enable;
|
||||
_graphics.AlphaTestReference = reference;
|
||||
_graphics.AlphaTestCompare = op;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the type of the vertex attributes consumed by the shader.
|
||||
/// </summary>
|
||||
/// <param name="state">The new state</param>
|
||||
public void SetAttributeTypes(ref Array32<VertexAttribState> state)
|
||||
{
|
||||
bool changed = false;
|
||||
ref Array32<AttributeType> attributeTypes = ref _graphics.AttributeTypes;
|
||||
|
||||
for (int location = 0; location < state.Length; location++)
|
||||
{
|
||||
VertexAttribType type = state[location].UnpackType();
|
||||
|
||||
AttributeType value = type switch
|
||||
{
|
||||
VertexAttribType.Sint => AttributeType.Sint,
|
||||
VertexAttribType.Uint => AttributeType.Uint,
|
||||
_ => AttributeType.Float
|
||||
};
|
||||
|
||||
if (attributeTypes[location] != value)
|
||||
{
|
||||
attributeTypes[location] = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetHasConstantBufferDrawParameters(bool value)
|
||||
{
|
||||
if (value != _graphics.HasConstantBufferDrawParameters)
|
||||
{
|
||||
_graphics.HasConstantBufferDrawParameters = value;
|
||||
|
||||
if (_usesDrawParameters)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that any storage buffer use is unaligned.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetHasUnalignedStorageBuffer(bool value)
|
||||
{
|
||||
if (value != _graphics.HasUnalignedStorageBuffer)
|
||||
{
|
||||
_graphics.HasUnalignedStorageBuffer = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the GPU pool state.
|
||||
/// </summary>
|
||||
/// <param name="state">The new state</param>
|
||||
public void SetPoolState(GpuChannelPoolState state)
|
||||
{
|
||||
if (!state.Equals(_pool))
|
||||
{
|
||||
_pool = state;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <summary>
|
||||
/// State update callback entry, with the callback function and associated field names.
|
||||
/// </summary>
|
||||
struct StateUpdateCallbackEntry
|
||||
readonly struct StateUpdateCallbackEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback function, to be called if the register was written as the state needs to be updated.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
@@ -16,9 +17,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
class StateUpdater
|
||||
{
|
||||
public const int ShaderStateIndex = 16;
|
||||
public const int ShaderStateIndex = 26;
|
||||
public const int RasterizerStateIndex = 15;
|
||||
public const int ScissorStateIndex = 18;
|
||||
public const int ScissorStateIndex = 16;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
|
||||
@@ -31,6 +32,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||
private ShaderSpecializationState _shaderSpecState;
|
||||
private SpecializationStateUpdater _currentSpecState;
|
||||
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
@@ -54,15 +56,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">3D engine state</param>
|
||||
/// <param name="drawState">Draw state</param>
|
||||
public StateUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState)
|
||||
/// <param name="spec">Specialization state updater</param>
|
||||
public StateUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState, SpecializationStateUpdater spec)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_drawState = drawState;
|
||||
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
||||
_currentSpecState = spec;
|
||||
|
||||
// ShaderState must be updated after other state updates, as pipeline state is sent to the backend when compiling new shaders.
|
||||
// ShaderState must be updated after other state updates, as specialization/pipeline state is used when fetching shaders.
|
||||
// Render target state must appear after shader state as it depends on information from the currently bound shader.
|
||||
// Rasterizer and scissor states are checked by render target clear, their indexes
|
||||
// must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified.
|
||||
@@ -101,6 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.DepthTestFunc)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessMode),
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
nameof(ThreedClassState.PatchVertices)),
|
||||
@@ -138,17 +143,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateShaderState,
|
||||
nameof(ThreedClassState.ShaderBaseAddress),
|
||||
nameof(ThreedClassState.ShaderState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||
nameof(ThreedClassState.RtColorState),
|
||||
nameof(ThreedClassState.RtDepthStencilState),
|
||||
nameof(ThreedClassState.RtControl),
|
||||
nameof(ThreedClassState.RtDepthStencilSize),
|
||||
nameof(ThreedClassState.RtDepthStencilEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||
nameof(ThreedClassState.ScissorState),
|
||||
nameof(ThreedClassState.ScreenScissorState)),
|
||||
@@ -179,7 +173,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateMultisampleState,
|
||||
nameof(ThreedClassState.AlphaToCoverageDitherEnable),
|
||||
nameof(ThreedClassState.MultisampleControl))
|
||||
nameof(ThreedClassState.MultisampleControl)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateEarlyZState,
|
||||
nameof(ThreedClassState.EarlyZForce)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateShaderState,
|
||||
nameof(ThreedClassState.ShaderBaseAddress),
|
||||
nameof(ThreedClassState.ShaderState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||
nameof(ThreedClassState.RtColorState),
|
||||
nameof(ThreedClassState.RtDepthStencilState),
|
||||
nameof(ThreedClassState.RtControl),
|
||||
nameof(ThreedClassState.RtDepthStencilSize),
|
||||
nameof(ThreedClassState.RtDepthStencilEnable)),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,17 +217,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update()
|
||||
{
|
||||
// If any state that the shader depends on changed,
|
||||
// then we may need to compile/bind a different version
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), _vsUsesDrawParameters, false))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// The vertex buffer size is calculated using a different
|
||||
// method when doing indexed draws, so we need to make sure
|
||||
// to update the vertex buffers if we are doing a regular
|
||||
@@ -271,6 +268,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_updateTracker.Update(ulong.MaxValue);
|
||||
|
||||
// If any state that the shader depends on changed,
|
||||
// then we may need to compile/bind a different version
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null && _currentSpecState.HasChanged())
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), _vsUsesDrawParameters, false))
|
||||
{
|
||||
// Shader must be reloaded. _vtgWritesRtLayer should not change.
|
||||
UpdateShaderState();
|
||||
}
|
||||
}
|
||||
|
||||
CommitBindings();
|
||||
|
||||
if (tfEnable && !_prevTfEnable)
|
||||
@@ -302,7 +311,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || (buffers.HasUnalignedStorageBuffers != hasUnaligned))
|
||||
{
|
||||
// Shader must be reloaded.
|
||||
_currentSpecState.SetHasUnalignedStorageBuffer(buffers.HasUnalignedStorageBuffers);
|
||||
// Shader must be reloaded. _vtgWritesRtLayer should not change.
|
||||
UpdateShaderState();
|
||||
}
|
||||
|
||||
@@ -351,6 +361,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_state.State.PatchVertices,
|
||||
_state.State.TessOuterLevel.AsSpan(),
|
||||
_state.State.TessInnerLevel.AsSpan());
|
||||
|
||||
_currentSpecState.SetTessellationMode(_state.State.TessMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -611,6 +623,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_state.State.AlphaTestEnable,
|
||||
_state.State.AlphaTestRef,
|
||||
_state.State.AlphaTestFunc);
|
||||
|
||||
_currentSpecState.SetAlphaTest(
|
||||
_state.State.AlphaTestEnable,
|
||||
_state.State.AlphaTestRef,
|
||||
_state.State.AlphaTestFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -710,6 +727,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
_context.Renderer.Pipeline.SetViewports(viewports, disableTransform);
|
||||
|
||||
_currentSpecState.SetViewportTransformDisable(_state.State.ViewportTransformEnable == 0);
|
||||
_currentSpecState.SetDepthMode(GetDepthMode() == DepthMode.MinusOneToOne);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -847,6 +867,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_channel.TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
|
||||
_channel.TextureManager.SetGraphicsTextureBufferIndex((int)_state.State.TextureBufferIndex);
|
||||
|
||||
_currentSpecState.SetPoolState(GetPoolState());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -887,6 +909,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_pipeline.SetVertexAttribs(vertexAttribs);
|
||||
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
||||
_currentSpecState.SetAttributeTypes(ref _state.State.VertexAttribState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -914,6 +937,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
Origin origin = (_state.State.PointCoordReplace & 4) == 0 ? Origin.LowerLeft : Origin.UpperLeft;
|
||||
|
||||
_context.Renderer.Pipeline.SetPointParameters(size, isProgramPointSize, enablePointSprite, origin);
|
||||
|
||||
_currentSpecState.SetProgramPointSizeEnable(isProgramPointSize);
|
||||
_currentSpecState.SetPointSize(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1212,6 +1238,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
alphaToCoverageEnable,
|
||||
_state.State.AlphaToCoverageDitherEnable,
|
||||
alphaToOneEnable));
|
||||
|
||||
_currentSpecState.SetAlphaToCoverageEnable(alphaToCoverageEnable, _state.State.AlphaToCoverageDitherEnable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the early z flag, based on guest state.
|
||||
/// </summary>
|
||||
private void UpdateEarlyZState()
|
||||
{
|
||||
_currentSpecState.SetEarlyZForce(_state.State.EarlyZForce);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1239,10 +1275,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
addressesSpan[index] = baseAddress + shader.Offset;
|
||||
}
|
||||
|
||||
GpuChannelPoolState poolState = GetPoolState();
|
||||
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses);
|
||||
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, poolState, graphicsState, addresses);
|
||||
// Consume the modified flag for spec state so that it isn't checked again.
|
||||
_currentSpecState.SetShader(gs);
|
||||
|
||||
_shaderSpecState = gs.SpecializationState;
|
||||
|
||||
@@ -1257,88 +1293,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
UpdateUserClipState();
|
||||
}
|
||||
|
||||
UpdateShaderBindings(gs.Bindings);
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
UpdateStageBindings(stageIndex, gs.Shaders[stageIndex + 1]?.Info);
|
||||
_currentProgramInfo[stageIndex] = gs.Shaders[stageIndex + 1]?.Info;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates bindings consumed by the shader stage on the texture and buffer managers.
|
||||
/// Updates bindings consumed by the shader on the texture and buffer managers.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage to have the bindings updated</param>
|
||||
/// <param name="info">Shader stage bindings info</param>
|
||||
private void UpdateStageBindings(int stage, ShaderProgramInfo info)
|
||||
/// <param name="bindings">Bindings for the active shader</param>
|
||||
private void UpdateShaderBindings(CachedShaderBindings bindings)
|
||||
{
|
||||
_currentProgramInfo[stage] = info;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
_channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
|
||||
_channel.TextureManager.RentGraphicsImageBindings(stage, 0);
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
|
||||
return;
|
||||
}
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
_channel.TextureManager.SetGraphicsBindings(bindings);
|
||||
_channel.BufferManager.SetGraphicsBufferBindings(bindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1353,46 +1325,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
(int)_state.State.TextureBufferIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GPU channel state for shader creation or compatibility verification.
|
||||
/// </summary>
|
||||
/// <returns>Current GPU channel state</returns>
|
||||
private GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
ref var vertexAttribState = ref _state.State.VertexAttribState;
|
||||
|
||||
Array32<AttributeType> attributeTypes = new Array32<AttributeType>();
|
||||
|
||||
for (int location = 0; location < attributeTypes.Length; location++)
|
||||
{
|
||||
VertexAttribType type = vertexAttribState[location].UnpackType();
|
||||
|
||||
attributeTypes[location] = type switch
|
||||
{
|
||||
VertexAttribType.Sint => AttributeType.Sint,
|
||||
VertexAttribType.Uint => AttributeType.Uint,
|
||||
_ => AttributeType.Float
|
||||
};
|
||||
}
|
||||
|
||||
return new GpuChannelGraphicsState(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode,
|
||||
(_state.State.MultisampleControl & 1) != 0,
|
||||
_state.State.AlphaToCoverageDitherEnable,
|
||||
_state.State.ViewportTransformEnable == 0,
|
||||
GetDepthMode() == DepthMode.MinusOneToOne,
|
||||
_state.State.VertexProgramPointSize,
|
||||
_state.State.PointSize,
|
||||
_state.State.AlphaTestEnable,
|
||||
_state.State.AlphaTestFunc,
|
||||
_state.State.AlphaTestRef,
|
||||
ref attributeTypes,
|
||||
_drawState.HasConstantBufferDrawParameters,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the depth mode that is currently being used (zero to one or minus one to one).
|
||||
/// </summary>
|
||||
|
@@ -67,12 +67,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||
|
||||
var spec = new SpecializationStateUpdater();
|
||||
var drawState = new DrawState();
|
||||
|
||||
_drawManager = new DrawManager(context, channel, _state, drawState);
|
||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||
_semaphoreUpdater = new SemaphoreUpdater(context, channel, _state);
|
||||
_cbUpdater = new ConstantBufferUpdater(channel, _state);
|
||||
_stateUpdater = new StateUpdater(context, channel, _state, drawState);
|
||||
_stateUpdater = new StateUpdater(context, channel, _state, drawState, spec);
|
||||
|
||||
// This defaults to "always", even without any register write.
|
||||
// Reads just return 0, regardless of what was set there.
|
||||
|
@@ -5,7 +5,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Represents texture format information.
|
||||
/// </summary>
|
||||
struct FormatInfo
|
||||
readonly struct FormatInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// A default, generic RGBA8 texture format.
|
||||
|
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Texture binding information.
|
||||
/// This is used for textures that needs to be accessed from shaders.
|
||||
/// </summary>
|
||||
struct TextureBindingInfo
|
||||
readonly struct TextureBindingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader sampler target type.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user