Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cc1a933a2f | ||
|
dd574146fb | ||
|
2c94ac455e | ||
|
e18d258fa0 | ||
|
36f10df775 | ||
|
680e548022 | ||
|
21c4176157 | ||
|
3b4ff2d6d9 |
@@ -44,7 +44,7 @@
|
|||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.30.0" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.1" />
|
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
|
@@ -157,7 +157,7 @@ namespace Ryujinx.Ava
|
|||||||
_isFirmwareTitle = true;
|
_isFirmwareTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed;
|
||||||
|
|
||||||
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
||||||
|
|
||||||
@@ -468,9 +468,9 @@ namespace Ryujinx.Ava
|
|||||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
private void HideCursorState_Changed(object sender, ReactiveEventArgs<HideCursorMode> state)
|
||||||
{
|
{
|
||||||
if (state.NewValue)
|
if (state.NewValue == HideCursorMode.OnIdle)
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
}
|
}
|
||||||
@@ -965,30 +965,38 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (_viewModel.IsActive)
|
if (_viewModel.IsActive)
|
||||||
{
|
{
|
||||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
if (_isCursorInRenderer)
|
||||||
{
|
{
|
||||||
if (_isCursorInRenderer)
|
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||||
{
|
{
|
||||||
HideCursor();
|
HideCursor();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShowCursor();
|
switch (ConfigurationState.Instance.HideCursor.Value)
|
||||||
|
{
|
||||||
|
case HideCursorMode.Never:
|
||||||
|
ShowCursor();
|
||||||
|
break;
|
||||||
|
case HideCursorMode.OnIdle:
|
||||||
|
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HideCursorMode.Always:
|
||||||
|
HideCursor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (ConfigurationState.Instance.HideCursorOnIdle)
|
ShowCursor();
|
||||||
{
|
|
||||||
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
|
||||||
{
|
|
||||||
HideCursor();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowCursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
@@ -1133,4 +1141,4 @@ namespace Ryujinx.Ava
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -80,7 +80,10 @@
|
|||||||
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
|
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
|
||||||
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
|
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
|
||||||
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
|
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
|
||||||
"SettingsTabGeneralHideCursorOnIdle": "Hide Cursor on Idle",
|
"SettingsTabGeneralHideCursor": "Hide Cursor:",
|
||||||
|
"SettingsTabGeneralHideCursorNever": "Never",
|
||||||
|
"SettingsTabGeneralHideCursorOnIdle": "On Idle",
|
||||||
|
"SettingsTabGeneralHideCursorAlways": "Always",
|
||||||
"SettingsTabGeneralGameDirectories": "Game Directories",
|
"SettingsTabGeneralGameDirectories": "Game Directories",
|
||||||
"SettingsTabGeneralAdd": "Add",
|
"SettingsTabGeneralAdd": "Add",
|
||||||
"SettingsTabGeneralRemove": "Remove",
|
"SettingsTabGeneralRemove": "Remove",
|
||||||
|
@@ -183,6 +183,18 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if HideCursor was overridden.
|
||||||
|
if (CommandLineState.OverrideHideCursor is not null)
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
|
||||||
|
{
|
||||||
|
"never" => HideCursorMode.Never,
|
||||||
|
"onidle" => HideCursorMode.OnIdle,
|
||||||
|
"always" => HideCursorMode.Always,
|
||||||
|
_ => ConfigurationState.Instance.HideCursor.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintSystemInfo()
|
private static void PrintSystemInfo()
|
||||||
@@ -226,4 +238,4 @@ namespace Ryujinx.Ava
|
|||||||
Logger.Shutdown();
|
Logger.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -95,6 +95,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private string _currentEmulatedGamePath;
|
private string _currentEmulatedGamePath;
|
||||||
private AutoResetEvent _rendererWaitEvent;
|
private AutoResetEvent _rendererWaitEvent;
|
||||||
private WindowState _windowState;
|
private WindowState _windowState;
|
||||||
|
private double _windowWidth;
|
||||||
|
private double _windowHeight;
|
||||||
|
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
|
|
||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
@@ -622,6 +625,28 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double WindowWidth
|
||||||
|
{
|
||||||
|
get => _windowWidth;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_windowWidth = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double WindowHeight
|
||||||
|
{
|
||||||
|
get => _windowHeight;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_windowHeight = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsGrid => Glyph == Glyph.Grid;
|
public bool IsGrid => Glyph == Glyph.Grid;
|
||||||
public bool IsList => Glyph == Glyph.List;
|
public bool IsList => Glyph == Glyph.List;
|
||||||
@@ -1551,8 +1576,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (SelectedApplication != null)
|
if (SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@@ -1562,8 +1587,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (SelectedApplication != null)
|
if (SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@@ -132,7 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
public bool HideCursorOnIdle { get; set; }
|
public int HideCursor { get; set; }
|
||||||
public bool EnableDockedMode { get; set; }
|
public bool EnableDockedMode { get; set; }
|
||||||
public bool EnableKeyboard { get; set; }
|
public bool EnableKeyboard { get; set; }
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
@@ -375,7 +375,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||||
ShowConfirmExit = config.ShowConfirmExit;
|
ShowConfirmExit = config.ShowConfirmExit;
|
||||||
HideCursorOnIdle = config.HideCursorOnIdle;
|
HideCursor = (int)config.HideCursor.Value;
|
||||||
|
|
||||||
GameDirectories.Clear();
|
GameDirectories.Clear();
|
||||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||||
@@ -458,7 +458,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||||
|
|
||||||
if (_directoryChanged)
|
if (_directoryChanged)
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUIView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUIView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<viewModels:SettingsViewModel />
|
<viewModels:SettingsViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Name="UiPage"
|
Name="UiPage"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
@@ -37,9 +37,24 @@
|
|||||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
|
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
|
<TextBlock VerticalAlignment="Center"
|
||||||
</CheckBox>
|
Text="{locale:Locale SettingsTabGeneralHideCursor}"
|
||||||
|
Width="150" />
|
||||||
|
<ComboBox SelectedIndex="{Binding HideCursor}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
MinWidth="100">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorNever}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorAlways}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
|
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
|
||||||
@@ -105,7 +120,7 @@
|
|||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding EnableCustomTheme}"
|
IsChecked="{Binding EnableCustomTheme}"
|
||||||
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
|
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
|
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
|
||||||
@@ -122,7 +137,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Text="{Binding CustomThemePath}" />
|
Text="{Binding CustomThemePath}" />
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="10,10,0,0"
|
Margin="10,10,0,0"
|
||||||
@@ -153,4 +168,4 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -35,8 +35,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId);
|
||||||
ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber);
|
ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber);
|
||||||
|
|
||||||
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||||
|
@@ -12,15 +12,14 @@
|
|||||||
Cursor="{Binding Cursor}"
|
Cursor="{Binding Cursor}"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
WindowState="{Binding WindowState}"
|
WindowState="{Binding WindowState}"
|
||||||
Width="1280"
|
Width="{Binding WindowWidth}"
|
||||||
Height="777"
|
Height="{Binding WindowHeight}"
|
||||||
MinWidth="1092"
|
MinWidth="1092"
|
||||||
MinHeight="672"
|
MinHeight="672"
|
||||||
d:DesignHeight="720"
|
d:DesignHeight="720"
|
||||||
d:DesignWidth="1280"
|
d:DesignWidth="1280"
|
||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
x:DataType="viewModels:MainWindowViewModel"
|
x:DataType="viewModels:MainWindowViewModel"
|
||||||
WindowStartupLocation="CenterScreen"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
|
@@ -62,6 +62,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
|
||||||
|
SetWindowSizePosition();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Load();
|
Load();
|
||||||
|
|
||||||
@@ -297,6 +299,51 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
LoadHotKeys();
|
LoadHotKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetWindowSizePosition()
|
||||||
|
{
|
||||||
|
PixelPoint SavedPoint = new PixelPoint(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
|
||||||
|
|
||||||
|
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
|
||||||
|
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
|
||||||
|
|
||||||
|
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
|
||||||
|
|
||||||
|
if (CheckScreenBounds(SavedPoint))
|
||||||
|
{
|
||||||
|
Position = SavedPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
else WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckScreenBounds(PixelPoint configPoint)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Screens.ScreenCount; i++)
|
||||||
|
{
|
||||||
|
if (Screens.All[i].Bounds.Contains(configPoint))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveWindowSizePosition()
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = (int)Height;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = (int)Width;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = Position.X;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = Position.Y;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
|
||||||
|
|
||||||
|
MainWindowViewModel.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnOpened(EventArgs e)
|
protected override void OnOpened(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnOpened(e);
|
base.OnOpened(e);
|
||||||
@@ -388,6 +435,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SaveWindowSizePosition();
|
||||||
|
|
||||||
ApplicationLibrary.CancelLoading();
|
ApplicationLibrary.CancelLoading();
|
||||||
InputManager.Dispose();
|
InputManager.Dispose();
|
||||||
Program.Exit();
|
Program.Exit();
|
||||||
|
9
src/Ryujinx.Common/Configuration/HideCursorMode.cs
Normal file
9
src/Ryujinx.Common/Configuration/HideCursorMode.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
public enum HideCursorMode
|
||||||
|
{
|
||||||
|
Never,
|
||||||
|
OnIdle,
|
||||||
|
Always
|
||||||
|
}
|
||||||
|
}
|
8
src/Ryujinx.Graphics.GAL/BufferAccess.cs
Normal file
8
src/Ryujinx.Graphics.GAL/BufferAccess.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public enum BufferAccess
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
FlushPersistent
|
||||||
|
}
|
||||||
|
}
|
@@ -21,11 +21,14 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
{
|
{
|
||||||
return CreateBuffer(size, BufferHandle.Null);
|
return CreateBuffer(size, BufferHandle.Null);
|
||||||
}
|
}
|
||||||
|
BufferHandle CreateBuffer(nint pointer, int size);
|
||||||
|
BufferHandle CreateBuffer(int size, BufferAccess access);
|
||||||
|
|
||||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||||
|
|
||||||
ISampler CreateSampler(SamplerCreateInfo info);
|
ISampler CreateSampler(SamplerCreateInfo info);
|
||||||
ITexture CreateTexture(TextureCreateInfo info, float scale);
|
ITexture CreateTexture(TextureCreateInfo info, float scale);
|
||||||
|
bool PrepareHostMapping(nint address, ulong size);
|
||||||
|
|
||||||
void CreateSync(ulong id, bool strict);
|
void CreateSync(ulong id, bool strict);
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
void CopyTo(ITexture destination, int firstLayer, int firstLevel);
|
void CopyTo(ITexture destination, int firstLayer, int firstLevel);
|
||||||
void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel);
|
void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel);
|
||||||
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
|
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
|
||||||
|
void CopyTo(BufferRange range, int layer, int level, int stride);
|
||||||
|
|
||||||
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
||||||
|
|
||||||
|
@@ -43,6 +43,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
|
|
||||||
Register<ActionCommand>(CommandType.Action);
|
Register<ActionCommand>(CommandType.Action);
|
||||||
Register<CreateBufferCommand>(CommandType.CreateBuffer);
|
Register<CreateBufferCommand>(CommandType.CreateBuffer);
|
||||||
|
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
|
||||||
|
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
|
||||||
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
||||||
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
||||||
Register<CreateSyncCommand>(CommandType.CreateSync);
|
Register<CreateSyncCommand>(CommandType.CreateSync);
|
||||||
@@ -69,6 +71,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
|
Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
|
||||||
Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
|
Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
|
||||||
Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
|
Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
|
||||||
|
Register<TextureCopyToBufferCommand>(CommandType.TextureCopyToBuffer);
|
||||||
Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
|
Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
|
||||||
Register<TextureGetDataCommand>(CommandType.TextureGetData);
|
Register<TextureGetDataCommand>(CommandType.TextureGetData);
|
||||||
Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
|
Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
{
|
{
|
||||||
Action,
|
Action,
|
||||||
CreateBuffer,
|
CreateBuffer,
|
||||||
|
CreateBufferAccess,
|
||||||
|
CreateHostBuffer,
|
||||||
CreateProgram,
|
CreateProgram,
|
||||||
CreateSampler,
|
CreateSampler,
|
||||||
CreateSync,
|
CreateSync,
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
SamplerDispose,
|
SamplerDispose,
|
||||||
|
|
||||||
TextureCopyTo,
|
TextureCopyTo,
|
||||||
|
TextureCopyToBuffer,
|
||||||
TextureCopyToScaled,
|
TextureCopyToScaled,
|
||||||
TextureCopyToSlice,
|
TextureCopyToSlice,
|
||||||
TextureCreateView,
|
TextureCreateView,
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
|
{
|
||||||
|
struct CreateBufferAccessCommand : IGALCommand, IGALCommand<CreateBufferAccessCommand>
|
||||||
|
{
|
||||||
|
public CommandType CommandType => CommandType.CreateBufferAccess;
|
||||||
|
private BufferHandle _threadedHandle;
|
||||||
|
private int _size;
|
||||||
|
private BufferAccess _access;
|
||||||
|
|
||||||
|
public void Set(BufferHandle threadedHandle, int size, BufferAccess access)
|
||||||
|
{
|
||||||
|
_threadedHandle = threadedHandle;
|
||||||
|
_size = size;
|
||||||
|
_access = access;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref CreateBufferAccessCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
|
{
|
||||||
|
struct CreateHostBufferCommand : IGALCommand, IGALCommand<CreateHostBufferCommand>
|
||||||
|
{
|
||||||
|
public CommandType CommandType => CommandType.CreateHostBuffer;
|
||||||
|
private BufferHandle _threadedHandle;
|
||||||
|
private nint _pointer;
|
||||||
|
private int _size;
|
||||||
|
|
||||||
|
public void Set(BufferHandle threadedHandle, nint pointer, int size)
|
||||||
|
{
|
||||||
|
_threadedHandle = threadedHandle;
|
||||||
|
_pointer = pointer;
|
||||||
|
_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref CreateHostBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._pointer, command._size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
|
{
|
||||||
|
struct TextureCopyToBufferCommand : IGALCommand, IGALCommand<TextureCopyToBufferCommand>
|
||||||
|
{
|
||||||
|
public CommandType CommandType => CommandType.TextureCopyToBuffer;
|
||||||
|
private TableRef<ThreadedTexture> _texture;
|
||||||
|
private BufferRange _range;
|
||||||
|
private int _layer;
|
||||||
|
private int _level;
|
||||||
|
private int _stride;
|
||||||
|
|
||||||
|
public void Set(TableRef<ThreadedTexture> texture, BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
_texture = texture;
|
||||||
|
_range = range;
|
||||||
|
_layer = layer;
|
||||||
|
_level = level;
|
||||||
|
_stride = stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref TextureCopyToBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
command._texture.Get(threaded).Base.CopyTo(threaded.Buffers.MapBufferRange(command._range), command._layer, command._level, command._stride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -108,6 +108,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
_renderer.New<TextureCopyToBufferCommand>().Set(Ref(this), range, layer, level, stride);
|
||||||
|
_renderer.QueueCommand();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetData(SpanOrArray<byte> data)
|
public void SetData(SpanOrArray<byte> data)
|
||||||
{
|
{
|
||||||
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));
|
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));
|
||||||
|
@@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
private int _refConsumerPtr;
|
private int _refConsumerPtr;
|
||||||
|
|
||||||
private Action _interruptAction;
|
private Action _interruptAction;
|
||||||
|
private object _interruptLock = new();
|
||||||
|
|
||||||
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||||
|
|
||||||
@@ -274,6 +275,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(nint pointer, int size)
|
||||||
|
{
|
||||||
|
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||||
|
New<CreateHostBufferCommand>().Set(handle, pointer, size);
|
||||||
|
QueueCommand();
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||||
|
{
|
||||||
|
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||||
|
New<CreateBufferAccessCommand>().Set(handle, size, access);
|
||||||
|
QueueCommand();
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
var program = new ThreadedProgram(this);
|
var program = new ThreadedProgram(this);
|
||||||
@@ -448,11 +467,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) { }
|
lock (_interruptLock)
|
||||||
|
{
|
||||||
|
while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) { }
|
||||||
|
|
||||||
_galWorkAvailable.Set();
|
_galWorkAvailable.Set();
|
||||||
|
|
||||||
_interruptRun.WaitOne();
|
_interruptRun.WaitOne();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,6 +483,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
// Threaded renderer ignores given interrupt action, as it provides its own to the child renderer.
|
// Threaded renderer ignores given interrupt action, as it provides its own to the child renderer.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PrepareHostMapping(nint address, ulong size)
|
||||||
|
{
|
||||||
|
return _baseRenderer.PrepareHostMapping(address, size);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Dispose must happen from the render thread, after all commands have completed.
|
// Dispose must happen from the render thread, after all commands have completed.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Graphics.Device;
|
using Ryujinx.Graphics.Device;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.MME;
|
using Ryujinx.Graphics.Gpu.Engine.MME;
|
||||||
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -59,7 +60,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
|||||||
if (_createSyncPending)
|
if (_createSyncPending)
|
||||||
{
|
{
|
||||||
_createSyncPending = false;
|
_createSyncPending = false;
|
||||||
_context.CreateHostSyncIfNeeded(false, false);
|
_context.CreateHostSyncIfNeeded(HostSyncFlags.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
|||||||
}
|
}
|
||||||
else if (operation == SyncpointbOperation.Incr)
|
else if (operation == SyncpointbOperation.Incr)
|
||||||
{
|
{
|
||||||
_context.CreateHostSyncIfNeeded(true, true);
|
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
|
||||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
|||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.CommandBufferBarrier();
|
_context.Renderer.Pipeline.CommandBufferBarrier();
|
||||||
|
|
||||||
_context.CreateHostSyncIfNeeded(false, true);
|
_context.CreateHostSyncIfNeeded(HostSyncFlags.Strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -252,6 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
|||||||
public void Interrupt()
|
public void Interrupt()
|
||||||
{
|
{
|
||||||
_interrupt = true;
|
_interrupt = true;
|
||||||
|
_event.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
|
|||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||||
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -254,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
uint syncpointId = (uint)argument & 0xFFFF;
|
uint syncpointId = (uint)argument & 0xFFFF;
|
||||||
|
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
_context.CreateHostSyncIfNeeded(true, true);
|
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
|
||||||
_context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result.
|
_context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result.
|
||||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||||
}
|
}
|
||||||
|
@@ -60,14 +60,14 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
||||||
/// and the SyncNumber will be incremented.
|
/// and the SyncNumber will be incremented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal List<Action> SyncActions { get; }
|
internal List<ISyncActionHandler> SyncActions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Actions to be performed when a CPU waiting syncpoint is triggered.
|
/// Actions to be performed when a CPU waiting syncpoint is triggered.
|
||||||
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
||||||
/// and the SyncNumber will be incremented.
|
/// and the SyncNumber will be incremented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal List<Action> SyncpointActions { get; }
|
internal List<ISyncActionHandler> SyncpointActions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
|
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
|
||||||
@@ -114,8 +114,8 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
HostInitalized = new ManualResetEvent(false);
|
HostInitalized = new ManualResetEvent(false);
|
||||||
|
|
||||||
SyncActions = new List<Action>();
|
SyncActions = new List<ISyncActionHandler>();
|
||||||
SyncpointActions = new List<Action>();
|
SyncpointActions = new List<ISyncActionHandler>();
|
||||||
BufferMigrations = new List<BufferMigration>();
|
BufferMigrations = new List<BufferMigration>();
|
||||||
|
|
||||||
DeferredActions = new Queue<Action>();
|
DeferredActions = new Queue<Action>();
|
||||||
@@ -296,9 +296,9 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// Registers an action to be performed the next time a syncpoint is incremented.
|
/// Registers an action to be performed the next time a syncpoint is incremented.
|
||||||
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
|
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to be performed on sync object creation</param>
|
/// <param name="action">The resource with action to be performed on sync object creation</param>
|
||||||
/// <param name="syncpointOnly">True if the sync action should only run when syncpoints are incremented</param>
|
/// <param name="syncpointOnly">True if the sync action should only run when syncpoints are incremented</param>
|
||||||
public void RegisterSyncAction(Action action, bool syncpointOnly = false)
|
internal void RegisterSyncAction(ISyncActionHandler action, bool syncpointOnly = false)
|
||||||
{
|
{
|
||||||
if (syncpointOnly)
|
if (syncpointOnly)
|
||||||
{
|
{
|
||||||
@@ -315,10 +315,13 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// Creates a host sync object if there are any pending sync actions. The actions will then be called.
|
/// Creates a host sync object if there are any pending sync actions. The actions will then be called.
|
||||||
/// If no actions are present, a host sync object is not created.
|
/// If no actions are present, a host sync object is not created.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="syncpoint">True if host sync is being created by a syncpoint</param>
|
/// <param name="flags">Modifiers for how host sync should be created</param>
|
||||||
/// <param name="strict">True if the sync should signal as soon as possible</param>
|
internal void CreateHostSyncIfNeeded(HostSyncFlags flags)
|
||||||
public void CreateHostSyncIfNeeded(bool syncpoint, bool strict)
|
|
||||||
{
|
{
|
||||||
|
bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint);
|
||||||
|
bool strict = flags.HasFlag(HostSyncFlags.Strict);
|
||||||
|
bool force = flags.HasFlag(HostSyncFlags.Force);
|
||||||
|
|
||||||
if (BufferMigrations.Count > 0)
|
if (BufferMigrations.Count > 0)
|
||||||
{
|
{
|
||||||
ulong currentSyncNumber = Renderer.GetCurrentSync();
|
ulong currentSyncNumber = Renderer.GetCurrentSync();
|
||||||
@@ -336,24 +339,17 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
||||||
{
|
{
|
||||||
Renderer.CreateSync(SyncNumber, strict);
|
Renderer.CreateSync(SyncNumber, strict);
|
||||||
|
|
||||||
|
SyncActions.ForEach(action => action.SyncPreAction(syncpoint));
|
||||||
|
SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint));
|
||||||
|
|
||||||
SyncNumber++;
|
SyncNumber++;
|
||||||
|
|
||||||
foreach (Action action in SyncActions)
|
SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
|
||||||
{
|
SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint));
|
||||||
action();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Action action in SyncpointActions)
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncActions.Clear();
|
|
||||||
SyncpointActions.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pendingSync = false;
|
_pendingSync = false;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
@@ -9,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class ShortTextureCacheEntry
|
class ShortTextureCacheEntry
|
||||||
{
|
{
|
||||||
|
public bool IsAutoDelete;
|
||||||
public readonly TextureDescriptor Descriptor;
|
public readonly TextureDescriptor Descriptor;
|
||||||
public readonly int InvalidatedSequence;
|
public readonly int InvalidatedSequence;
|
||||||
public readonly Texture Texture;
|
public readonly Texture Texture;
|
||||||
@@ -24,6 +24,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
InvalidatedSequence = texture.InvalidatedSequence;
|
InvalidatedSequence = texture.InvalidatedSequence;
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new entry on the short duration texture cache from the auto delete cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture</param>
|
||||||
|
public ShortTextureCacheEntry(Texture texture)
|
||||||
|
{
|
||||||
|
IsAutoDelete = true;
|
||||||
|
InvalidatedSequence = texture.InvalidatedSequence;
|
||||||
|
Texture = texture;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -199,7 +210,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
texture.DecrementReferenceCount();
|
texture.DecrementReferenceCount();
|
||||||
|
|
||||||
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
|
if (!texture.ShortCacheEntry.IsAutoDelete)
|
||||||
|
{
|
||||||
|
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
texture.ShortCacheEntry = null;
|
texture.ShortCacheEntry = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,6 +237,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
texture.IncrementReferenceCount();
|
texture.IncrementReferenceCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks.
|
||||||
|
/// On expiry, it will be removed from the AutoDeleteCache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
public void AddShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
if (texture.ShortCacheEntry != null)
|
||||||
|
{
|
||||||
|
var entry = new ShortTextureCacheEntry(texture);
|
||||||
|
|
||||||
|
_shortCacheBuilder.Add(entry);
|
||||||
|
|
||||||
|
texture.ShortCacheEntry = entry;
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete textures from the short duration cache.
|
/// Delete textures from the short duration cache.
|
||||||
/// Moves the builder set to be deleted on next process.
|
/// Moves the builder set to be deleted on next process.
|
||||||
@@ -234,7 +268,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
entry.Texture.DecrementReferenceCount();
|
entry.Texture.DecrementReferenceCount();
|
||||||
|
|
||||||
_shortCacheLookup.Remove(entry.Descriptor);
|
if (entry.IsAutoDelete)
|
||||||
|
{
|
||||||
|
Remove(entry.Texture, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_shortCacheLookup.Remove(entry.Descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
entry.Texture.ShortCacheEntry = null;
|
entry.Texture.ShortCacheEntry = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -144,6 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ShortTextureCacheEntry ShortCacheEntry { get; set; }
|
public ShortTextureCacheEntry ShortCacheEntry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this texture has ever been referenced by a pool.
|
||||||
|
/// </summary>
|
||||||
|
public bool HadPoolOwner { get; private set; }
|
||||||
|
|
||||||
/// Physical memory ranges where the texture data is located.
|
/// Physical memory ranges where the texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiRange Range { get; private set; }
|
public MultiRange Range { get; private set; }
|
||||||
@@ -1423,7 +1428,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_modifiedStale || Group.HasCopyDependencies)
|
if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer)
|
||||||
{
|
{
|
||||||
_modifiedStale = false;
|
_modifiedStale = false;
|
||||||
Group.SignalModifying(this, bound);
|
Group.SignalModifying(this, bound);
|
||||||
@@ -1506,10 +1511,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="gpuVa">GPU VA of the pool reference</param>
|
/// <param name="gpuVa">GPU VA of the pool reference</param>
|
||||||
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
|
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
|
||||||
{
|
{
|
||||||
|
HadPoolOwner = true;
|
||||||
|
|
||||||
lock (_poolOwners)
|
lock (_poolOwners)
|
||||||
{
|
{
|
||||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
|
||||||
}
|
}
|
||||||
|
|
||||||
_referenceCount++;
|
_referenceCount++;
|
||||||
|
|
||||||
if (ShortCacheEntry != null)
|
if (ShortCacheEntry != null)
|
||||||
@@ -1594,7 +1602,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_poolOwners.Clear();
|
_poolOwners.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShortCacheEntry != null && _context.IsGpuThread())
|
if (ShortCacheEntry != null && !ShortCacheEntry.IsAutoDelete && _context.IsGpuThread())
|
||||||
{
|
{
|
||||||
// If this is called from another thread (unmapped), the short cache will
|
// If this is called from another thread (unmapped), the short cache will
|
||||||
// have to remove this texture on a future tick.
|
// have to remove this texture on a future tick.
|
||||||
@@ -1610,6 +1618,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdatePoolMappings()
|
public void UpdatePoolMappings()
|
||||||
{
|
{
|
||||||
|
ChangedMapping = true;
|
||||||
|
|
||||||
lock (_poolOwners)
|
lock (_poolOwners)
|
||||||
{
|
{
|
||||||
ulong address = 0;
|
ulong address = 0;
|
||||||
@@ -1683,10 +1693,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (Group.Storage == this)
|
if (Group.Storage == this)
|
||||||
{
|
{
|
||||||
|
Group.Unmapped();
|
||||||
|
|
||||||
Group.ClearModified(unmapRange);
|
Group.ClearModified(unmapRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePoolMappings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles removal of textures written to a memory region being unmapped.
|
/// Handles marking of textures written to a memory region being (partially) remapped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">Sender object</param>
|
/// <param name="sender">Sender object</param>
|
||||||
/// <param name="e">Event arguments</param>
|
/// <param name="e">Event arguments</param>
|
||||||
@@ -80,26 +80,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
|
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < overlapCount; i++)
|
if (overlapCount > 0)
|
||||||
{
|
{
|
||||||
overlaps[i].Unmapped(unmapped);
|
for (int i = 0; i < overlapCount; i++)
|
||||||
|
{
|
||||||
|
overlaps[i].Unmapped(unmapped);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any range was previously unmapped, we also need to purge
|
lock (_partiallyMappedTextures)
|
||||||
// all partially mapped texture, as they might be fully mapped now.
|
|
||||||
for (int i = 0; i < unmapped.Count; i++)
|
|
||||||
{
|
{
|
||||||
if (unmapped.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
if (overlapCount > 0 || _partiallyMappedTextures.Count > 0)
|
||||||
{
|
{
|
||||||
lock (_partiallyMappedTextures)
|
e.AddRemapAction(() =>
|
||||||
{
|
{
|
||||||
foreach (var texture in _partiallyMappedTextures)
|
lock (_partiallyMappedTextures)
|
||||||
{
|
{
|
||||||
texture.Unmapped(unmapped);
|
if (overlapCount > 0)
|
||||||
}
|
{
|
||||||
}
|
for (int i = 0; i < overlapCount; i++)
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Add(overlaps[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
// Any texture that has been unmapped at any point or is partially unmapped
|
||||||
|
// should update their pool references after the remap completes.
|
||||||
|
|
||||||
|
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||||
|
|
||||||
|
foreach (var texture in _partiallyMappedTextures)
|
||||||
|
{
|
||||||
|
texture.UpdatePoolMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -833,7 +848,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (overlapInCache)
|
if (overlapInCache)
|
||||||
{
|
{
|
||||||
_cache.Remove(overlap, flush);
|
if (flush || overlap.HadPoolOwner || overlap.IsView)
|
||||||
|
{
|
||||||
|
_cache.Remove(overlap, flush);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This texture has only ever been referenced in the AutoDeleteCache.
|
||||||
|
// Keep this texture alive with the short duration cache, as it may be used often but not sampled.
|
||||||
|
|
||||||
|
_cache.AddShortCache(overlap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOverlap = modified;
|
removeOverlap = modified;
|
||||||
@@ -1135,6 +1160,44 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries a texture's memory range and marks it as partially mapped or not.
|
||||||
|
/// Partially mapped textures re-evaluate their memory range after each time GPU memory is mapped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
||||||
|
/// <param name="address">The virtual address of the texture</param>
|
||||||
|
/// <param name="texture">The texture to be marked</param>
|
||||||
|
/// <returns>The physical regions for the texture, found when evaluating whether the texture was partially mapped</returns>
|
||||||
|
public MultiRange UpdatePartiallyMapped(MemoryManager memoryManager, ulong address, Texture texture)
|
||||||
|
{
|
||||||
|
MultiRange range;
|
||||||
|
lock (_partiallyMappedTextures)
|
||||||
|
{
|
||||||
|
range = memoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
bool partiallyMapped = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
{
|
||||||
|
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
partiallyMapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partiallyMapped)
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Add(texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Remove(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1145,6 +1208,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_cache.AddShortCache(texture, ref descriptor);
|
_cache.AddShortCache(texture, ref descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks.
|
||||||
|
/// On expiry, it will be removed from the AutoDeleteCache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
public void AddShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
_cache.AddShortCache(texture);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes a texture from the short duration cache.
|
/// Removes a texture from the short duration cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -57,6 +57,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasCopyDependencies { get; set; }
|
public bool HasCopyDependencies { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the texture group has a pre-emptive flush buffer.
|
||||||
|
/// When one is present, the group must always be notified on unbind.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasFlushBuffer => _flushBuffer != BufferHandle.Null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if this texture has any incompatible overlaps alive.
|
/// Indicates if this texture has any incompatible overlaps alive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -89,6 +95,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
private bool _incompatibleOverlapsDirty = true;
|
private bool _incompatibleOverlapsDirty = true;
|
||||||
private bool _flushIncompatibleOverlaps;
|
private bool _flushIncompatibleOverlaps;
|
||||||
|
|
||||||
|
private BufferHandle _flushBuffer;
|
||||||
|
private bool _flushBufferImported;
|
||||||
|
private bool _flushBufferInvalid;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new texture group.
|
/// Create a new texture group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -464,8 +474,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
||||||
/// <param name="sliceIndex">The index of the slice to flush</param>
|
/// <param name="sliceIndex">The index of the slice to flush</param>
|
||||||
|
/// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
|
||||||
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
||||||
private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, ITexture texture = null)
|
private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, bool inBuffer, ITexture texture = null)
|
||||||
{
|
{
|
||||||
(int layer, int level) = GetLayerLevelForView(sliceIndex);
|
(int layer, int level) = GetLayerLevelForView(sliceIndex);
|
||||||
|
|
||||||
@@ -475,7 +486,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
|
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
|
||||||
|
|
||||||
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
if (inBuffer)
|
||||||
|
{
|
||||||
|
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(_flushBuffer, offset, size);
|
||||||
|
|
||||||
|
Storage.ConvertFromHostCompatibleFormat(region.Memory.Span, data.Get(), level, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -484,12 +504,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
||||||
/// <param name="sliceStart">The first slice to flush</param>
|
/// <param name="sliceStart">The first slice to flush</param>
|
||||||
/// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
|
/// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
|
||||||
|
/// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
|
||||||
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
||||||
private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, ITexture texture = null)
|
private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, bool inBuffer, ITexture texture = null)
|
||||||
{
|
{
|
||||||
for (int i = sliceStart; i < sliceEnd; i++)
|
for (int i = sliceStart; i < sliceEnd; i++)
|
||||||
{
|
{
|
||||||
FlushTextureDataSliceToGuest(tracked, i, texture);
|
FlushTextureDataSliceToGuest(tracked, i, inBuffer, texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
if (endSlice > startSlice)
|
if (endSlice > startSlice)
|
||||||
{
|
{
|
||||||
FlushSliceRange(tracked, startSlice, endSlice);
|
FlushSliceRange(tracked, startSlice, endSlice, false);
|
||||||
flushed = true;
|
flushed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,7 +574,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FlushSliceRange(tracked, startSlice, endSlice);
|
FlushSliceRange(tracked, startSlice, endSlice, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
flushed = true;
|
flushed = true;
|
||||||
@@ -565,6 +586,58 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return flushed;
|
return flushed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flush the texture data into a persistently mapped buffer.
|
||||||
|
/// If the buffer does not exist, this method will create it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">Handle of the texture group to flush slices of</param>
|
||||||
|
public void FlushIntoBuffer(TextureGroupHandle handle)
|
||||||
|
{
|
||||||
|
// Ensure that the buffer exists.
|
||||||
|
|
||||||
|
if (_flushBufferInvalid && _flushBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_flushBufferInvalid = false;
|
||||||
|
_context.Renderer.DeleteBuffer(_flushBuffer);
|
||||||
|
_flushBuffer = BufferHandle.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_flushBuffer == BufferHandle.Null)
|
||||||
|
{
|
||||||
|
if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
|
||||||
|
|
||||||
|
var hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
|
||||||
|
|
||||||
|
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
|
||||||
|
{
|
||||||
|
_flushBuffer = _context.Renderer.CreateBuffer(hostPointer, (int)Storage.Size);
|
||||||
|
_flushBufferImported = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
|
||||||
|
_flushBufferImported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage.BlacklistScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
int sliceStart = handle.BaseSlice;
|
||||||
|
int sliceEnd = sliceStart + handle.SliceCount;
|
||||||
|
|
||||||
|
for (int i = sliceStart; i < sliceEnd; i++)
|
||||||
|
{
|
||||||
|
(int layer, int level) = GetLayerLevelForView(i);
|
||||||
|
|
||||||
|
Storage.GetFlushTexture().CopyTo(new BufferRange(_flushBuffer, _allOffsets[i], _sliceSizes[level]), layer, level, _flushBufferImported ? Storage.Info.Stride : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
|
/// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1570,10 +1643,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
_context.Renderer.BackgroundContextAction(() =>
|
_context.Renderer.BackgroundContextAction(() =>
|
||||||
{
|
{
|
||||||
if (!isGpuThread)
|
bool inBuffer = !isGpuThread && handle.Sync(_context);
|
||||||
{
|
|
||||||
handle.Sync(_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage.SignalModifiedDirty();
|
Storage.SignalModifiedDirty();
|
||||||
|
|
||||||
@@ -1585,13 +1655,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
|
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported))
|
||||||
{
|
{
|
||||||
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, Storage.GetFlushTexture());
|
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called if any part of the storage texture is unmapped.
|
||||||
|
/// </summary>
|
||||||
|
public void Unmapped()
|
||||||
|
{
|
||||||
|
if (_flushBufferImported)
|
||||||
|
{
|
||||||
|
_flushBufferInvalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose this texture group, disposing all related memory tracking handles.
|
/// Dispose this texture group, disposing all related memory tracking handles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1606,6 +1687,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
|
incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_flushBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(_flushBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Cpu.Tracking;
|
using Ryujinx.Cpu.Tracking;
|
||||||
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -13,8 +14,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
||||||
/// in sync with this one before use.
|
/// in sync with this one before use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class TextureGroupHandle : IDisposable
|
class TextureGroupHandle : ISyncActionHandler, IDisposable
|
||||||
{
|
{
|
||||||
|
private const int FlushBalanceIncrement = 6;
|
||||||
|
private const int FlushBalanceWriteCost = 1;
|
||||||
|
private const int FlushBalanceThreshold = 7;
|
||||||
|
private const int FlushBalanceMax = 60;
|
||||||
|
private const int FlushBalanceMin = -10;
|
||||||
|
|
||||||
private TextureGroup _group;
|
private TextureGroup _group;
|
||||||
private int _bindCount;
|
private int _bindCount;
|
||||||
private int _firstLevel;
|
private int _firstLevel;
|
||||||
@@ -26,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// The sync number last registered.
|
/// The sync number last registered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ulong _registeredSync;
|
private ulong _registeredSync;
|
||||||
|
private ulong _registeredBufferSync = ulong.MaxValue;
|
||||||
|
private ulong _registeredBufferGuestSync = ulong.MaxValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sync number when the texture was last modified by GPU.
|
/// The sync number when the texture was last modified by GPU.
|
||||||
@@ -42,6 +51,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _syncActionRegistered;
|
private bool _syncActionRegistered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the balance of synced writes to flushes.
|
||||||
|
/// Used to determine if the texture should always write data to a persistent buffer for flush.
|
||||||
|
/// </summary>
|
||||||
|
private int _flushBalance;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The byte offset from the start of the storage of this handle.
|
/// The byte offset from the start of the storage of this handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -132,6 +147,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handles = handles;
|
Handles = handles;
|
||||||
|
|
||||||
|
if (group.Storage.Info.IsLinear)
|
||||||
|
{
|
||||||
|
// Linear textures are presumed to be used for readback initially.
|
||||||
|
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -159,6 +180,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the next sync will copy into the flush buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it will copy, false otherwise</returns>
|
||||||
|
private bool NextSyncCopies()
|
||||||
|
{
|
||||||
|
return _flushBalance - FlushBalanceWriteCost > FlushBalanceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alters the flush balance by the given value. Should increase significantly with each sync, decrease with each write.
|
||||||
|
/// A flush balance higher than the threshold will cause a texture to repeatedly copy to a flush buffer on each use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modifier">Value to add to the existing flush balance</param>
|
||||||
|
/// <returns>True if the new balance is over the threshold, false otherwise</returns>
|
||||||
|
private bool ModifyFlushBalance(int modifier)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int existingBalance;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
existingBalance = _flushBalance;
|
||||||
|
result = Math.Max(FlushBalanceMin, Math.Min(FlushBalanceMax, existingBalance + modifier));
|
||||||
|
}
|
||||||
|
while (Interlocked.CompareExchange(ref _flushBalance, result, existingBalance) != existingBalance);
|
||||||
|
|
||||||
|
return result > FlushBalanceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a single texture view as an overlap if its range overlaps.
|
/// Adds a single texture view as an overlap if its range overlaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -204,7 +254,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
if (!_syncActionRegistered)
|
if (!_syncActionRegistered)
|
||||||
{
|
{
|
||||||
_modifiedSync = context.SyncNumber;
|
_modifiedSync = context.SyncNumber;
|
||||||
context.RegisterSyncAction(SyncAction, true);
|
context.RegisterSyncAction(this, true);
|
||||||
_syncActionRegistered = true;
|
_syncActionRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
SignalModified(context);
|
SignalModified(context);
|
||||||
|
|
||||||
|
if (!bound && _syncActionRegistered && NextSyncCopies())
|
||||||
|
{
|
||||||
|
// On unbind, textures that flush often should immediately create sync so their result can be obtained as soon as possible.
|
||||||
|
|
||||||
|
context.CreateHostSyncIfNeeded(HostSyncFlags.Force);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
|
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
|
||||||
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
|
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
|
||||||
}
|
}
|
||||||
@@ -266,25 +323,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
|
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The GPU context used to wait for sync</param>
|
/// <param name="context">The GPU context used to wait for sync</param>
|
||||||
public void Sync(GpuContext context)
|
/// <returns>True if the texture data can be read from the flush buffer</returns>
|
||||||
|
public bool Sync(GpuContext context)
|
||||||
{
|
{
|
||||||
ulong registeredSync = _registeredSync;
|
// Currently assumes the calling thread is a guest thread.
|
||||||
long diff = (long)(context.SyncNumber - registeredSync);
|
|
||||||
|
bool inBuffer = _registeredBufferGuestSync != ulong.MaxValue;
|
||||||
|
ulong sync = inBuffer ? _registeredBufferGuestSync : _registeredSync;
|
||||||
|
|
||||||
|
long diff = (long)(context.SyncNumber - sync);
|
||||||
|
|
||||||
|
ModifyFlushBalance(FlushBalanceIncrement);
|
||||||
|
|
||||||
if (diff > 0)
|
if (diff > 0)
|
||||||
{
|
{
|
||||||
context.Renderer.WaitSync(registeredSync);
|
context.Renderer.WaitSync(sync);
|
||||||
|
|
||||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
if ((long)(_modifiedSync - sync) > 0)
|
||||||
{
|
{
|
||||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||||
return;
|
return inBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Modified = false;
|
Modified = false;
|
||||||
|
|
||||||
|
return inBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -296,15 +363,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Interlocked.Exchange(ref _actionRegistered, 0);
|
Interlocked.Exchange(ref _actionRegistered, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Action to perform before a sync number is registered after modification.
|
||||||
|
/// This action will copy the texture data to the flush buffer if this texture
|
||||||
|
/// flushes often enough, which is determined by the flush balance.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SyncPreAction(bool syncpoint)
|
||||||
|
{
|
||||||
|
if (syncpoint || NextSyncCopies())
|
||||||
|
{
|
||||||
|
if (ModifyFlushBalance(0) && _registeredBufferSync != _modifiedSync)
|
||||||
|
{
|
||||||
|
_group.FlushIntoBuffer(this);
|
||||||
|
_registeredBufferSync = _modifiedSync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action to perform when a sync number is registered after modification.
|
/// Action to perform when a sync number is registered after modification.
|
||||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SyncAction()
|
/// <inheritdoc/>
|
||||||
|
public bool SyncAction(bool syncpoint)
|
||||||
{
|
{
|
||||||
// The storage will need to signal modified again to update the sync number in future.
|
// The storage will need to signal modified again to update the sync number in future.
|
||||||
_group.Storage.SignalModifiedDirty();
|
_group.Storage.SignalModifiedDirty();
|
||||||
|
|
||||||
|
bool lastInBuffer = _registeredBufferSync == _modifiedSync;
|
||||||
|
|
||||||
|
if (!lastInBuffer)
|
||||||
|
{
|
||||||
|
_registeredBufferSync = ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
lock (Overlaps)
|
lock (Overlaps)
|
||||||
{
|
{
|
||||||
foreach (Texture texture in Overlaps)
|
foreach (Texture texture in Overlaps)
|
||||||
@@ -314,6 +407,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register region tracking for CPU? (again)
|
// Register region tracking for CPU? (again)
|
||||||
|
|
||||||
_registeredSync = _modifiedSync;
|
_registeredSync = _modifiedSync;
|
||||||
_syncActionRegistered = false;
|
_syncActionRegistered = false;
|
||||||
|
|
||||||
@@ -321,6 +415,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
_group.RegisterAction(this);
|
_group.RegisterAction(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syncpoint)
|
||||||
|
{
|
||||||
|
_registeredBufferGuestSync = _registeredBufferSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last modification is in the buffer, keep this sync action alive until it sees a syncpoint.
|
||||||
|
return syncpoint || !lastInBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -272,7 +272,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
ulong address = descriptor.UnpackAddress();
|
ulong address = descriptor.UnpackAddress();
|
||||||
|
|
||||||
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
if (!descriptor.Equals(ref DescriptorCache[request.ID]))
|
||||||
|
{
|
||||||
|
// If the pool entry has already been replaced, just remove the texture.
|
||||||
|
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||||
|
|
||||||
// If the texture is not mapped at all, delete its reference.
|
// If the texture is not mapped at all, delete its reference.
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Cpu.Tracking;
|
using Ryujinx.Cpu.Tracking;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using Ryujinx.Memory.Tracking;
|
using Ryujinx.Memory.Tracking;
|
||||||
using System;
|
using System;
|
||||||
@@ -11,7 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Buffer : IRange, IDisposable
|
class Buffer : IRange, ISyncActionHandler, IDisposable
|
||||||
{
|
{
|
||||||
private const ulong GranularBufferThreshold = 4096;
|
private const ulong GranularBufferThreshold = 4096;
|
||||||
|
|
||||||
@@ -248,7 +249,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (!_syncActionRegistered)
|
if (!_syncActionRegistered)
|
||||||
{
|
{
|
||||||
_context.RegisterSyncAction(SyncAction);
|
_context.RegisterSyncAction(this);
|
||||||
_syncActionRegistered = true;
|
_syncActionRegistered = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +268,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// Action to be performed when a syncpoint is reached after modification.
|
/// Action to be performed when a syncpoint is reached after modification.
|
||||||
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
|
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SyncAction()
|
/// <inheritdoc/>
|
||||||
|
public bool SyncAction(bool syncpoint)
|
||||||
{
|
{
|
||||||
_syncActionRegistered = false;
|
_syncActionRegistered = false;
|
||||||
|
|
||||||
@@ -284,6 +286,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
_memoryTracking.RegisterAction(_externalFlushDelegate);
|
_memoryTracking.RegisterAction(_externalFlushDelegate);
|
||||||
SynchronizeMemory(Address, Size);
|
SynchronizeMemory(Address, Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -296,7 +300,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||||
{
|
{
|
||||||
_context.RegisterSyncAction(SyncAction);
|
_context.RegisterSyncAction(this);
|
||||||
_syncActionRegistered = true;
|
_syncActionRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -365,6 +365,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs remap actions that are added to an unmap event.
|
||||||
|
/// These must run after the mapping completes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e">Event with remap actions</param>
|
||||||
|
private void RunRemapActions(UnmapEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.RemapActions != null)
|
||||||
|
{
|
||||||
|
foreach (Action action in e.RemapActions)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a given range of pages to the specified CPU virtual address.
|
/// Maps a given range of pages to the specified CPU virtual address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -379,12 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
lock (_pageTable)
|
lock (_pageTable)
|
||||||
{
|
{
|
||||||
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
|
UnmapEventArgs e = new(va, size);
|
||||||
|
MemoryUnmapped?.Invoke(this, e);
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||||
{
|
{
|
||||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RunRemapActions(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,12 +417,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
lock (_pageTable)
|
lock (_pageTable)
|
||||||
{
|
{
|
||||||
// Event handlers are not expected to be thread safe.
|
// Event handlers are not expected to be thread safe.
|
||||||
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
|
UnmapEventArgs e = new(va, size);
|
||||||
|
MemoryUnmapped?.Invoke(this, e);
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||||
{
|
{
|
||||||
SetPte(va + offset, PteUnmapped);
|
SetPte(va + offset, PteUnmapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RunRemapActions(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ using Ryujinx.Memory.Tracking;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
@@ -82,6 +83,34 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a host pointer for a given range of application memory.
|
||||||
|
/// If the memory region is not a single contiguous block, this method returns 0.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Getting a host pointer is unsafe. It should be considered invalid immediately if the GPU memory is unmapped.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="range">Ranges of physical memory where the target data is located</param>
|
||||||
|
/// <returns>Pointer to the range of memory</returns>
|
||||||
|
public nint GetHostPointer(MultiRange range)
|
||||||
|
{
|
||||||
|
if (range.Count == 1)
|
||||||
|
{
|
||||||
|
var singleRange = range.GetSubRange(0);
|
||||||
|
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
var regions = _cpuMemory.GetHostRegions(singleRange.Address, singleRange.Size);
|
||||||
|
|
||||||
|
if (regions != null && regions.Count() == 1)
|
||||||
|
{
|
||||||
|
return (nint)regions.First().Address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a span of data from the application process.
|
/// Gets a span of data from the application process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -1,14 +1,24 @@
|
|||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
public class UnmapEventArgs
|
public class UnmapEventArgs
|
||||||
{
|
{
|
||||||
public ulong Address { get; }
|
public ulong Address { get; }
|
||||||
public ulong Size { get; }
|
public ulong Size { get; }
|
||||||
|
public List<Action> RemapActions { get; private set; }
|
||||||
|
|
||||||
public UnmapEventArgs(ulong address, ulong size)
|
public UnmapEventArgs(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddRemapAction(Action action)
|
||||||
|
{
|
||||||
|
RemapActions ??= new List<Action>();
|
||||||
|
RemapActions.Add(action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs
Normal file
30
src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Modifier flags for creating host sync.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
internal enum HostSyncFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Present if host sync is being created by a syncpoint.
|
||||||
|
/// </summary>
|
||||||
|
Syncpoint = 1 << 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Present if the sync should signal as soon as possible.
|
||||||
|
/// </summary>
|
||||||
|
Strict = 1 << 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Present will force the sync to be created, even if no actions are eligible.
|
||||||
|
/// </summary>
|
||||||
|
Force = 1 << 2,
|
||||||
|
|
||||||
|
StrictSyncpoint = Strict | Syncpoint
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This interface indicates that a class can be registered for a sync action.
|
||||||
|
/// </summary>
|
||||||
|
interface ISyncActionHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Action to be performed when some synchronizing action is reached after modification.
|
||||||
|
/// Generally used to register read/write tracking to flush resources from GPU when their memory is used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||||
|
/// <returns>True if the action is to be removed, false otherwise</returns>
|
||||||
|
bool SyncAction(bool syncpoint);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Action to be performed immediately before sync is created.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||||
|
void SyncPreAction(bool syncpoint) { }
|
||||||
|
}
|
||||||
|
}
|
@@ -42,6 +42,20 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
return Handle.FromInt32<BufferHandle>(handle);
|
return Handle.FromInt32<BufferHandle>(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BufferHandle CreatePersistent(int size)
|
||||||
|
{
|
||||||
|
int handle = GL.GenBuffer();
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
|
||||||
|
GL.BufferStorage(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero,
|
||||||
|
BufferStorageFlags.MapPersistentBit |
|
||||||
|
BufferStorageFlags.MapCoherentBit |
|
||||||
|
BufferStorageFlags.ClientStorageBit |
|
||||||
|
BufferStorageFlags.MapReadBit);
|
||||||
|
|
||||||
|
return Handle.FromInt32<BufferHandle>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||||
{
|
{
|
||||||
GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32());
|
GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32());
|
||||||
@@ -60,7 +74,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
// Data in the persistent buffer and host array is guaranteed to be available
|
// Data in the persistent buffer and host array is guaranteed to be available
|
||||||
// until the next time the host thread requests data.
|
// until the next time the host thread requests data.
|
||||||
|
|
||||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
if (renderer.PersistentBuffers.TryGet(buffer, out IntPtr ptr))
|
||||||
|
{
|
||||||
|
return new PinnedSpan<byte>(IntPtr.Add(ptr, offset).ToPointer(), size);
|
||||||
|
}
|
||||||
|
else if (HwCapabilities.UsePersistentBufferForFlush)
|
||||||
{
|
{
|
||||||
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
|
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
|
||||||
}
|
}
|
||||||
|
@@ -49,6 +49,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
return GetData();
|
return GetData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetData(SpanOrArray<byte> data)
|
public void SetData(SpanOrArray<byte> data)
|
||||||
{
|
{
|
||||||
var dataSpan = data.AsSpan();
|
var dataSpan = data.AsSpan();
|
||||||
|
@@ -3,6 +3,7 @@ using Ryujinx.Common;
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.OpenGL.Image
|
namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
{
|
{
|
||||||
@@ -287,6 +288,26 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
if (stride != 0 && stride != BitUtils.AlignUp(Info.Width * Info.BytesPerPixel, 4))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Stride conversion for texture copy to buffer not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelPackBuffer, range.Handle.ToInt32());
|
||||||
|
|
||||||
|
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
|
||||||
|
if (format.PixelFormat == PixelFormat.DepthStencil)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DepthStencil copy to buffer is not supported for layer/level > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = WriteToPbo2D(range.Offset, layer, level);
|
||||||
|
|
||||||
|
Debug.Assert(offset == 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteToPbo(int offset, bool forceBgra)
|
public void WriteToPbo(int offset, bool forceBgra)
|
||||||
{
|
{
|
||||||
WriteTo(IntPtr.Zero + offset, forceBgra);
|
WriteTo(IntPtr.Zero + offset, forceBgra);
|
||||||
|
@@ -58,10 +58,31 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||||
|
{
|
||||||
|
return CreateBuffer(size, GAL.BufferAccess.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(int size, GAL.BufferAccess access)
|
||||||
{
|
{
|
||||||
BufferCount++;
|
BufferCount++;
|
||||||
|
|
||||||
return Buffer.Create(size);
|
if (access == GAL.BufferAccess.FlushPersistent)
|
||||||
|
{
|
||||||
|
BufferHandle handle = Buffer.CreatePersistent(size);
|
||||||
|
|
||||||
|
PersistentBuffers.Map(handle, size);
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Buffer.Create(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(nint pointer, int size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
@@ -88,6 +109,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
|
|
||||||
public void DeleteBuffer(BufferHandle buffer)
|
public void DeleteBuffer(BufferHandle buffer)
|
||||||
{
|
{
|
||||||
|
PersistentBuffers.Unmap(buffer);
|
||||||
|
|
||||||
Buffer.Delete(buffer);
|
Buffer.Delete(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,5 +295,10 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
{
|
{
|
||||||
ScreenCaptured?.Invoke(this, bitmap);
|
ScreenCaptured?.Invoke(this, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PrepareHostMapping(nint address, ulong size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.OpenGL.Image;
|
using Ryujinx.Graphics.OpenGL.Image;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
private PersistentBuffer _main = new PersistentBuffer();
|
private PersistentBuffer _main = new PersistentBuffer();
|
||||||
private PersistentBuffer _background = new PersistentBuffer();
|
private PersistentBuffer _background = new PersistentBuffer();
|
||||||
|
|
||||||
|
private Dictionary<BufferHandle, IntPtr> _maps = new Dictionary<BufferHandle, IntPtr>();
|
||||||
|
|
||||||
public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main;
|
public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -20,6 +23,30 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
_main?.Dispose();
|
_main?.Dispose();
|
||||||
_background?.Dispose();
|
_background?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Map(BufferHandle handle, int size)
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
|
||||||
|
IntPtr ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
|
||||||
|
|
||||||
|
_maps[handle] = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(BufferHandle handle)
|
||||||
|
{
|
||||||
|
if (_maps.ContainsKey(handle))
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
|
||||||
|
GL.UnmapBuffer(BufferTarget.CopyWriteBuffer);
|
||||||
|
|
||||||
|
_maps.Remove(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(BufferHandle handle, out IntPtr ptr)
|
||||||
|
{
|
||||||
|
return _maps.TryGetValue(handle, out ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PersistentBuffer : IDisposable
|
class PersistentBuffer : IDisposable
|
||||||
|
@@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private MemoryAllocation _allocation;
|
private MemoryAllocation _allocation;
|
||||||
private Auto<DisposableBuffer> _buffer;
|
private Auto<DisposableBuffer> _buffer;
|
||||||
private Auto<MemoryAllocation> _allocationAuto;
|
private Auto<MemoryAllocation> _allocationAuto;
|
||||||
|
private bool _allocationImported;
|
||||||
private ulong _bufferHandle;
|
private ulong _bufferHandle;
|
||||||
|
|
||||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||||
@@ -81,6 +82,26 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_flushLock = new ReaderWriterLock();
|
_flushLock = new ReaderWriterLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto<MemoryAllocation> allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
|
||||||
|
{
|
||||||
|
_gd = gd;
|
||||||
|
_device = device;
|
||||||
|
_allocation = allocation.GetUnsafe();
|
||||||
|
_allocationAuto = allocation;
|
||||||
|
_allocationImported = true;
|
||||||
|
_waitable = new MultiFenceHolder(size);
|
||||||
|
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
|
||||||
|
_bufferHandle = buffer.Handle;
|
||||||
|
Size = size;
|
||||||
|
_map = _allocation.HostPointer + offset;
|
||||||
|
|
||||||
|
_baseType = type;
|
||||||
|
_currentType = currentType;
|
||||||
|
DesiredType = currentType;
|
||||||
|
|
||||||
|
_flushLock = new ReaderWriterLock();
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||||
{
|
{
|
||||||
if (_swapQueued && DesiredType != _currentType)
|
if (_swapQueued && DesiredType != _currentType)
|
||||||
@@ -775,8 +796,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||||
|
|
||||||
_buffer.Dispose();
|
_buffer.Dispose();
|
||||||
_allocationAuto.Dispose();
|
|
||||||
_cachedConvertedBuffers.Dispose();
|
_cachedConvertedBuffers.Dispose();
|
||||||
|
if (_allocationImported)
|
||||||
|
{
|
||||||
|
_allocationAuto.DecrementReferenceCount();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allocationAuto.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
class BufferManager : IDisposable
|
class BufferManager : IDisposable
|
||||||
{
|
{
|
||||||
private const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
public const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
||||||
MemoryPropertyFlags.HostVisibleBit |
|
MemoryPropertyFlags.HostVisibleBit |
|
||||||
MemoryPropertyFlags.HostCoherentBit |
|
MemoryPropertyFlags.HostCoherentBit |
|
||||||
MemoryPropertyFlags.HostCachedBit;
|
MemoryPropertyFlags.HostCachedBit;
|
||||||
@@ -40,6 +40,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
BufferUsageFlags.VertexBufferBit |
|
BufferUsageFlags.VertexBufferBit |
|
||||||
BufferUsageFlags.TransformFeedbackBufferBitExt;
|
BufferUsageFlags.TransformFeedbackBufferBitExt;
|
||||||
|
|
||||||
|
private const BufferUsageFlags HostImportedBufferUsageFlags =
|
||||||
|
BufferUsageFlags.TransferSrcBit |
|
||||||
|
BufferUsageFlags.TransferDstBit;
|
||||||
|
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
|
|
||||||
private readonly IdList<BufferHolder> _buffers;
|
private readonly IdList<BufferHolder> _buffers;
|
||||||
@@ -48,11 +52,47 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public StagingBuffer StagingBuffer { get; }
|
public StagingBuffer StagingBuffer { get; }
|
||||||
|
|
||||||
|
public MemoryRequirements HostImportedBufferMemoryRequirements { get; }
|
||||||
|
|
||||||
public BufferManager(VulkanRenderer gd, Device device)
|
public BufferManager(VulkanRenderer gd, Device device)
|
||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
_buffers = new IdList<BufferHolder>();
|
_buffers = new IdList<BufferHolder>();
|
||||||
StagingBuffer = new StagingBuffer(gd, this);
|
StagingBuffer = new StagingBuffer(gd, this);
|
||||||
|
|
||||||
|
HostImportedBufferMemoryRequirements = GetHostImportedUsageRequirements(gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe BufferHandle CreateHostImported(VulkanRenderer gd, nint pointer, int size)
|
||||||
|
{
|
||||||
|
var usage = HostImportedBufferUsageFlags;
|
||||||
|
|
||||||
|
if (gd.Capabilities.SupportsIndirectParameters)
|
||||||
|
{
|
||||||
|
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufferCreateInfo = new BufferCreateInfo()
|
||||||
|
{
|
||||||
|
SType = StructureType.BufferCreateInfo,
|
||||||
|
Size = (ulong)size,
|
||||||
|
Usage = usage,
|
||||||
|
SharingMode = SharingMode.Exclusive
|
||||||
|
};
|
||||||
|
|
||||||
|
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||||
|
|
||||||
|
(Auto<MemoryAllocation> allocation, ulong offset) = gd.HostMemoryAllocator.GetExistingAllocation(pointer, (ulong)size);
|
||||||
|
|
||||||
|
gd.Api.BindBufferMemory(_device, buffer, allocation.GetUnsafe().Memory, allocation.GetUnsafe().Offset + offset);
|
||||||
|
|
||||||
|
var holder = new BufferHolder(gd, _device, buffer, allocation, size, BufferAllocationType.HostMapped, BufferAllocationType.HostMapped, (int)offset);
|
||||||
|
|
||||||
|
BufferCount++;
|
||||||
|
|
||||||
|
ulong handle64 = (uint)_buffers.Add(holder);
|
||||||
|
|
||||||
|
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||||
@@ -75,6 +115,32 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd)
|
||||||
|
{
|
||||||
|
var usage = HostImportedBufferUsageFlags;
|
||||||
|
|
||||||
|
if (gd.Capabilities.SupportsIndirectParameters)
|
||||||
|
{
|
||||||
|
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufferCreateInfo = new BufferCreateInfo()
|
||||||
|
{
|
||||||
|
SType = StructureType.BufferCreateInfo,
|
||||||
|
Size = (ulong)Environment.SystemPageSize,
|
||||||
|
Usage = usage,
|
||||||
|
SharingMode = SharingMode.Exclusive
|
||||||
|
};
|
||||||
|
|
||||||
|
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||||
|
|
||||||
|
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
|
||||||
|
|
||||||
|
gd.Api.DestroyBuffer(_device, buffer, null);
|
||||||
|
|
||||||
|
return requirements;
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
int size,
|
int size,
|
||||||
|
@@ -364,6 +364,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BufferAllocationType Convert(this BufferAccess access)
|
||||||
|
{
|
||||||
|
return access switch
|
||||||
|
{
|
||||||
|
BufferAccess.FlushPersistent => BufferAllocationType.HostMapped,
|
||||||
|
_ => BufferAllocationType.Auto
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
|
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
|
||||||
|
@@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly bool SupportsPipelineStatisticsQuery;
|
public readonly bool SupportsPipelineStatisticsQuery;
|
||||||
public readonly bool SupportsGeometryShader;
|
public readonly bool SupportsGeometryShader;
|
||||||
public readonly bool SupportsViewportArray2;
|
public readonly bool SupportsViewportArray2;
|
||||||
|
public readonly bool SupportsHostImportedMemory;
|
||||||
public readonly uint MinSubgroupSize;
|
public readonly uint MinSubgroupSize;
|
||||||
public readonly uint MaxSubgroupSize;
|
public readonly uint MaxSubgroupSize;
|
||||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||||
@@ -75,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
bool supportsPipelineStatisticsQuery,
|
bool supportsPipelineStatisticsQuery,
|
||||||
bool supportsGeometryShader,
|
bool supportsGeometryShader,
|
||||||
bool supportsViewportArray2,
|
bool supportsViewportArray2,
|
||||||
|
bool supportsHostImportedMemory,
|
||||||
uint minSubgroupSize,
|
uint minSubgroupSize,
|
||||||
uint maxSubgroupSize,
|
uint maxSubgroupSize,
|
||||||
ShaderStageFlags requiredSubgroupSizeStages,
|
ShaderStageFlags requiredSubgroupSizeStages,
|
||||||
@@ -108,6 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
|
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
|
||||||
SupportsGeometryShader = supportsGeometryShader;
|
SupportsGeometryShader = supportsGeometryShader;
|
||||||
SupportsViewportArray2 = supportsViewportArray2;
|
SupportsViewportArray2 = supportsViewportArray2;
|
||||||
|
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||||
MinSubgroupSize = minSubgroupSize;
|
MinSubgroupSize = minSubgroupSize;
|
||||||
MaxSubgroupSize = maxSubgroupSize;
|
MaxSubgroupSize = maxSubgroupSize;
|
||||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||||
|
@@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly IProgram _programColorClearSI;
|
private readonly IProgram _programColorClearSI;
|
||||||
private readonly IProgram _programColorClearUI;
|
private readonly IProgram _programColorClearUI;
|
||||||
private readonly IProgram _programStrideChange;
|
private readonly IProgram _programStrideChange;
|
||||||
|
private readonly IProgram _programConvertD32S8ToD24S8;
|
||||||
private readonly IProgram _programConvertIndexBuffer;
|
private readonly IProgram _programConvertIndexBuffer;
|
||||||
private readonly IProgram _programConvertIndirectData;
|
private readonly IProgram _programConvertIndirectData;
|
||||||
private readonly IProgram _programColorCopyShortening;
|
private readonly IProgram _programColorCopyShortening;
|
||||||
@@ -158,6 +159,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var convertD32S8ToD24S8Bindings = new ShaderBindings(
|
||||||
|
new[] { 0 },
|
||||||
|
new[] { 1, 2 },
|
||||||
|
Array.Empty<int>(),
|
||||||
|
Array.Empty<int>());
|
||||||
|
|
||||||
|
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
|
{
|
||||||
|
new ShaderSource(ShaderBinaries.ConvertD32S8ToD24S8ShaderSource, convertD32S8ToD24S8Bindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||||
|
});
|
||||||
|
|
||||||
var convertIndexBufferBindings = new ShaderBindings(
|
var convertIndexBufferBindings = new ShaderBindings(
|
||||||
new[] { 0 },
|
new[] { 0 },
|
||||||
new[] { 1, 2 },
|
new[] { 1, 2 },
|
||||||
@@ -1644,6 +1656,82 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_pipeline.Finish(gd, cbs);
|
_pipeline.Finish(gd, cbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void ConvertD32S8ToD24S8(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, Auto<DisposableBuffer> dstBufferAuto, int pixelCount, int dstOffset)
|
||||||
|
{
|
||||||
|
int inSize = pixelCount * 2 * sizeof(int);
|
||||||
|
int outSize = pixelCount * sizeof(int);
|
||||||
|
|
||||||
|
var srcBufferAuto = src.GetBuffer();
|
||||||
|
|
||||||
|
var srcBuffer = srcBufferAuto.Get(cbs, 0, inSize).Value;
|
||||||
|
var dstBuffer = dstBufferAuto.Get(cbs, dstOffset, outSize).Value;
|
||||||
|
|
||||||
|
var access = AccessFlags.ShaderWriteBit;
|
||||||
|
var stage = PipelineStageFlags.ComputeShaderBit;
|
||||||
|
|
||||||
|
BufferHolder.InsertBufferBarrier(
|
||||||
|
gd,
|
||||||
|
cbs.CommandBuffer,
|
||||||
|
srcBuffer,
|
||||||
|
BufferHolder.DefaultAccessFlags,
|
||||||
|
AccessFlags.ShaderReadBit,
|
||||||
|
PipelineStageFlags.AllCommandsBit,
|
||||||
|
stage,
|
||||||
|
0,
|
||||||
|
outSize);
|
||||||
|
|
||||||
|
BufferHolder.InsertBufferBarrier(
|
||||||
|
gd,
|
||||||
|
cbs.CommandBuffer,
|
||||||
|
dstBuffer,
|
||||||
|
BufferHolder.DefaultAccessFlags,
|
||||||
|
access,
|
||||||
|
PipelineStageFlags.AllCommandsBit,
|
||||||
|
stage,
|
||||||
|
0,
|
||||||
|
outSize);
|
||||||
|
|
||||||
|
const int ParamsBufferSize = sizeof(int) * 2;
|
||||||
|
|
||||||
|
Span<int> shaderParams = stackalloc int[2];
|
||||||
|
|
||||||
|
shaderParams[0] = pixelCount;
|
||||||
|
shaderParams[1] = dstOffset;
|
||||||
|
|
||||||
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||||
|
|
||||||
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||||
|
|
||||||
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
|
|
||||||
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
||||||
|
|
||||||
|
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
|
||||||
|
|
||||||
|
sbRanges[0] = srcBufferAuto;
|
||||||
|
sbRanges[1] = dstBufferAuto;
|
||||||
|
|
||||||
|
_pipeline.SetStorageBuffers(1, sbRanges);
|
||||||
|
|
||||||
|
_pipeline.SetProgram(_programConvertD32S8ToD24S8);
|
||||||
|
_pipeline.DispatchCompute(1, 1, 1);
|
||||||
|
|
||||||
|
gd.BufferManager.Delete(bufferHandle);
|
||||||
|
|
||||||
|
_pipeline.Finish(gd, cbs);
|
||||||
|
|
||||||
|
BufferHolder.InsertBufferBarrier(
|
||||||
|
gd,
|
||||||
|
cbs.CommandBuffer,
|
||||||
|
dstBuffer,
|
||||||
|
access,
|
||||||
|
BufferHolder.DefaultAccessFlags,
|
||||||
|
stage,
|
||||||
|
PipelineStageFlags.AllCommandsBit,
|
||||||
|
0,
|
||||||
|
outSize);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
188
src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs
Normal file
188
src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Collections;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal class HostMemoryAllocator
|
||||||
|
{
|
||||||
|
private struct HostMemoryAllocation
|
||||||
|
{
|
||||||
|
public readonly Auto<MemoryAllocation> Allocation;
|
||||||
|
public readonly IntPtr Pointer;
|
||||||
|
public readonly ulong Size;
|
||||||
|
|
||||||
|
public ulong Start => (ulong)Pointer;
|
||||||
|
public ulong End => (ulong)Pointer + Size;
|
||||||
|
|
||||||
|
public HostMemoryAllocation(Auto<MemoryAllocation> allocation, IntPtr pointer, ulong size)
|
||||||
|
{
|
||||||
|
Allocation = allocation;
|
||||||
|
Pointer = pointer;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MemoryAllocator _allocator;
|
||||||
|
private readonly Vk _api;
|
||||||
|
private readonly ExtExternalMemoryHost _hostMemoryApi;
|
||||||
|
private readonly Device _device;
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
private List<HostMemoryAllocation> _allocations;
|
||||||
|
private IntervalTree<ulong, HostMemoryAllocation> _allocationTree;
|
||||||
|
|
||||||
|
public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device)
|
||||||
|
{
|
||||||
|
_allocator = allocator;
|
||||||
|
_api = api;
|
||||||
|
_hostMemoryApi = hostMemoryApi;
|
||||||
|
_device = device;
|
||||||
|
|
||||||
|
_allocations = new List<HostMemoryAllocation>();
|
||||||
|
_allocationTree = new IntervalTree<ulong, HostMemoryAllocation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryImport(
|
||||||
|
MemoryRequirements requirements,
|
||||||
|
MemoryPropertyFlags flags,
|
||||||
|
IntPtr pointer,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
// Does a compatible allocation exist in the tree?
|
||||||
|
var allocations = new HostMemoryAllocation[10];
|
||||||
|
|
||||||
|
ulong start = (ulong)pointer;
|
||||||
|
ulong end = start + size;
|
||||||
|
|
||||||
|
int count = _allocationTree.Get(start, end, ref allocations);
|
||||||
|
|
||||||
|
// A compatible range is one that where the start and end completely cover the requested range.
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
HostMemoryAllocation existing = allocations[i];
|
||||||
|
|
||||||
|
if (start >= existing.Start && end <= existing.End)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
existing.Allocation.IncrementReferenceCount();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Can throw if the allocation has been disposed.
|
||||||
|
// Just continue the search if this happens.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize);
|
||||||
|
nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize);
|
||||||
|
ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer);
|
||||||
|
|
||||||
|
Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties);
|
||||||
|
if (getResult < Result.Success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags);
|
||||||
|
if (memoryTypeIndex < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportMemoryHostPointerInfoEXT importInfo = new ImportMemoryHostPointerInfoEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.ImportMemoryHostPointerInfoExt,
|
||||||
|
HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
|
||||||
|
PHostPointer = (void*)pageAlignedPointer
|
||||||
|
};
|
||||||
|
|
||||||
|
var memoryAllocateInfo = new MemoryAllocateInfo()
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryAllocateInfo,
|
||||||
|
AllocationSize = pageAlignedSize,
|
||||||
|
MemoryTypeIndex = (uint)memoryTypeIndex,
|
||||||
|
PNext = &importInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
|
||||||
|
|
||||||
|
if (result < Result.Success)
|
||||||
|
{
|
||||||
|
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize);
|
||||||
|
var allocAuto = new Auto<MemoryAllocation>(allocation);
|
||||||
|
var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize);
|
||||||
|
|
||||||
|
allocAuto.IncrementReferenceCount();
|
||||||
|
allocAuto.Dispose(); // Kept alive by ref count only.
|
||||||
|
|
||||||
|
// Register this mapping for future use.
|
||||||
|
|
||||||
|
_allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc);
|
||||||
|
_allocations.Add(hostAlloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Auto<MemoryAllocation>, ulong) GetExistingAllocation(IntPtr pointer, ulong size)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
// Does a compatible allocation exist in the tree?
|
||||||
|
var allocations = new HostMemoryAllocation[10];
|
||||||
|
|
||||||
|
ulong start = (ulong)pointer;
|
||||||
|
ulong end = start + size;
|
||||||
|
|
||||||
|
int count = _allocationTree.Get(start, end, ref allocations);
|
||||||
|
|
||||||
|
// A compatible range is one that where the start and end completely cover the requested range.
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
HostMemoryAllocation existing = allocations[i];
|
||||||
|
|
||||||
|
if (start >= existing.Start && end <= existing.End)
|
||||||
|
{
|
||||||
|
return (existing.Allocation, start - existing.Start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(DeviceMemory memory, ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_allocations.RemoveAll(allocation =>
|
||||||
|
{
|
||||||
|
if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle)
|
||||||
|
{
|
||||||
|
_allocationTree.Remove(allocation.Start, allocation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_api.FreeMemory(_device, memory, ReadOnlySpan<AllocationCallbacks>.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
private readonly MemoryAllocatorBlockList _owner;
|
private readonly MemoryAllocatorBlockList _owner;
|
||||||
private readonly MemoryAllocatorBlockList.Block _block;
|
private readonly MemoryAllocatorBlockList.Block _block;
|
||||||
|
private readonly HostMemoryAllocator _hostMemory;
|
||||||
|
|
||||||
public DeviceMemory Memory { get; }
|
public DeviceMemory Memory { get; }
|
||||||
public IntPtr HostPointer { get;}
|
public IntPtr HostPointer { get;}
|
||||||
@@ -29,9 +30,30 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Size = size;
|
Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemoryAllocation(
|
||||||
|
HostMemoryAllocator hostMemory,
|
||||||
|
DeviceMemory memory,
|
||||||
|
IntPtr hostPointer,
|
||||||
|
ulong offset,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
_hostMemory = hostMemory;
|
||||||
|
Memory = memory;
|
||||||
|
HostPointer = hostPointer;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_owner.Free(_block, Offset, Size);
|
if (_hostMemory != null)
|
||||||
|
{
|
||||||
|
_hostMemory.Free(Memory, Offset, Size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_owner.Free(_block, Offset, Size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return newBl.Allocate(size, alignment, map);
|
return newBl.Allocate(size, alignment, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int FindSuitableMemoryTypeIndex(
|
internal int FindSuitableMemoryTypeIndex(
|
||||||
uint memoryTypeBits,
|
uint memoryTypeBits,
|
||||||
MemoryPropertyFlags flags)
|
MemoryPropertyFlags flags)
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
#extension GL_EXT_scalar_block_layout : require
|
||||||
|
|
||||||
|
layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||||
|
|
||||||
|
layout (std430, set = 0, binding = 0) uniform stride_arguments
|
||||||
|
{
|
||||||
|
int pixelCount;
|
||||||
|
int dstStartOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (std430, set = 1, binding = 1) buffer in_s
|
||||||
|
{
|
||||||
|
uint[] in_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (std430, set = 1, binding = 2) buffer out_s
|
||||||
|
{
|
||||||
|
uint[] out_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Determine what slice of the stride copies this invocation will perform.
|
||||||
|
int invocations = int(gl_WorkGroupSize.x);
|
||||||
|
|
||||||
|
int copiesRequired = pixelCount;
|
||||||
|
|
||||||
|
// Find the copies that this invocation should perform.
|
||||||
|
|
||||||
|
// - Copies that all invocations perform.
|
||||||
|
int allInvocationCopies = copiesRequired / invocations;
|
||||||
|
|
||||||
|
// - Extra remainder copy that this invocation performs.
|
||||||
|
int index = int(gl_LocalInvocationID.x);
|
||||||
|
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
|
||||||
|
|
||||||
|
int copyCount = allInvocationCopies + extra;
|
||||||
|
|
||||||
|
// Finally, get the starting offset. Make sure to count extra copies.
|
||||||
|
|
||||||
|
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
|
||||||
|
|
||||||
|
int srcOffset = startCopy * 2;
|
||||||
|
int dstOffset = dstStartOffset + startCopy;
|
||||||
|
|
||||||
|
// Perform the conversion for this region.
|
||||||
|
for (int i = 0; i < copyCount; i++)
|
||||||
|
{
|
||||||
|
float depth = uintBitsToFloat(in_data[srcOffset++]);
|
||||||
|
uint stencil = in_data[srcOffset++];
|
||||||
|
|
||||||
|
uint rescaledDepth = uint(clamp(depth, 0.0, 1.0) * 16777215.0);
|
||||||
|
|
||||||
|
out_data[dstOffset++] = (rescaledDepth << 8) | (stencil & 0xff);
|
||||||
|
}
|
||||||
|
}
|
@@ -1236,6 +1236,213 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
|
|||||||
0x38, 0x00, 0x01, 0x00,
|
0x38, 0x00, 0x01, 0x00,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static readonly byte[] ConvertD32S8ToD24S8ShaderSource = new byte[]
|
||||||
|
{
|
||||||
|
0x03, 0x02, 0x23, 0x07, 0x00, 0x05, 0x01, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x77, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x0F, 0x00, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00,
|
||||||
|
0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x45,
|
||||||
|
0x58, 0x54, 0x5F, 0x73, 0x63, 0x61, 0x6C, 0x61, 0x72, 0x5F, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x5F,
|
||||||
|
0x6C, 0x61, 0x79, 0x6F, 0x75, 0x74, 0x00, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x47, 0x4C, 0x5F, 0x47,
|
||||||
|
0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x63, 0x70, 0x70, 0x5F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x5F,
|
||||||
|
0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00,
|
||||||
|
0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x47, 0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x69, 0x6E,
|
||||||
|
0x63, 0x6C, 0x75, 0x64, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
|
||||||
|
0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69,
|
||||||
|
0x6F, 0x6E, 0x73, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x69,
|
||||||
|
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00,
|
||||||
|
0x0B, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x5F, 0x61, 0x72, 0x67, 0x75, 0x6D,
|
||||||
|
0x65, 0x6E, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x70, 0x69, 0x78, 0x65, 0x6C, 0x43, 0x6F, 0x75, 0x6E, 0x74, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x73, 0x74, 0x53,
|
||||||
|
0x74, 0x61, 0x72, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
|
||||||
|
0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||||
|
0x61, 0x6C, 0x6C, 0x49, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x6F, 0x70,
|
||||||
|
0x69, 0x65, 0x73, 0x00, 0x05, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x64, 0x65,
|
||||||
|
0x78, 0x00, 0x00, 0x00, 0x05, 0x00, 0x08, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x4C,
|
||||||
|
0x6F, 0x63, 0x61, 0x6C, 0x49, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x65, 0x78, 0x74, 0x72,
|
||||||
|
0x61, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x79,
|
||||||
|
0x43, 0x6F, 0x75, 0x6E, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x2D, 0x00, 0x00, 0x00,
|
||||||
|
0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x70, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
|
||||||
|
0x37, 0x00, 0x00, 0x00, 0x73, 0x72, 0x63, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x05, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x64, 0x73, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65,
|
||||||
|
0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x04, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x64, 0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x04, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x05, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x64,
|
||||||
|
0x61, 0x74, 0x61, 0x00, 0x05, 0x00, 0x03, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x73, 0x74, 0x65, 0x6E, 0x63, 0x69, 0x6C, 0x00,
|
||||||
|
0x05, 0x00, 0x06, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x72, 0x65, 0x73, 0x63, 0x61, 0x6C, 0x65, 0x64,
|
||||||
|
0x44, 0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00,
|
||||||
|
0x6F, 0x75, 0x74, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x65, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x03, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
|
||||||
|
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x48, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
|
0x48, 0x00, 0x05, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
|
0x48, 0x00, 0x05, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x65, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x04, 0x00, 0x76, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||||
|
0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x18, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
|
||||||
|
0x49, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x4A, 0x00, 0x00, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x03, 0x00, 0x4C, 0x00, 0x00, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00,
|
||||||
|
0x20, 0x00, 0x04, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
|
||||||
|
0x3B, 0x00, 0x04, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
|
||||||
|
0x20, 0x00, 0x04, 0x00, 0x52, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x20, 0x00, 0x04, 0x00, 0x56, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
|
||||||
|
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x4B,
|
||||||
|
0x1D, 0x00, 0x03, 0x00, 0x64, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00,
|
||||||
|
0x65, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00,
|
||||||
|
0x0C, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00,
|
||||||
|
0x67, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x6B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x6E, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x74, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
0x75, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x06, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||||
|
0x76, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x4A, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||||
|
0x56, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x10, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x0A, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x13, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x87, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x15, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x1C, 0x00, 0x00, 0x00,
|
||||||
|
0x1D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x16, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x21, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x22, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x24, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00,
|
||||||
|
0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
|
||||||
|
0xA9, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||||
|
0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||||
|
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||||
|
0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
|
||||||
|
0x2A, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00,
|
||||||
|
0x2C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00,
|
||||||
|
0x16, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||||
|
0x2E, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x31, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x32, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x33, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x07, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
|
||||||
|
0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x2D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x38, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x3A, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x37, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x3C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00,
|
||||||
|
0xF8, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00, 0x43, 0x00, 0x00, 0x00,
|
||||||
|
0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00,
|
||||||
|
0xF8, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x47, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00, 0x25, 0x00, 0x00, 0x00,
|
||||||
|
0x48, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
|
||||||
|
0x48, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||||
|
0x42, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
|
||||||
|
0x37, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
|
||||||
|
0x50, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00,
|
||||||
|
0x51, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
|
||||||
|
0x4F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00,
|
||||||
|
0x49, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x4B, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x58, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x59, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||||
|
0x37, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00,
|
||||||
|
0x5A, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
|
||||||
|
0x3D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x49, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x08, 0x00,
|
||||||
|
0x49, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||||
|
0x5D, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
|
||||||
|
0x49, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
|
||||||
|
0x6D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x05, 0x00,
|
||||||
|
0x17, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00,
|
||||||
|
0x3D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
|
||||||
|
0xC7, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00,
|
||||||
|
0x6E, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
|
||||||
|
0x6C, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00,
|
||||||
|
0x71, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x71, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||||
|
0x44, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x44, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
|
||||||
|
0x3E, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||||
|
0x41, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x43, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00,
|
||||||
|
0x38, 0x00, 0x01, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly byte[] ConvertIndexBufferShaderSource = new byte[]
|
public static readonly byte[] ConvertIndexBufferShaderSource = new byte[]
|
||||||
{
|
{
|
||||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x91, 0x00, 0x00, 0x00,
|
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x91, 0x00, 0x00, 0x00,
|
||||||
|
@@ -125,6 +125,24 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
if (result.Waitable == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long beforeTicks = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
|
if (result.NeedsFlush(FlushId))
|
||||||
|
{
|
||||||
|
_gd.InterruptAction(() =>
|
||||||
|
{
|
||||||
|
if (result.NeedsFlush(FlushId))
|
||||||
|
{
|
||||||
|
_gd.FlushAllCommands();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
lock (result)
|
lock (result)
|
||||||
{
|
{
|
||||||
if (result.Waitable == null)
|
if (result.Waitable == null)
|
||||||
@@ -132,19 +150,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long beforeTicks = Stopwatch.GetTimestamp();
|
|
||||||
|
|
||||||
if (result.NeedsFlush(FlushId))
|
|
||||||
{
|
|
||||||
_gd.InterruptAction(() =>
|
|
||||||
{
|
|
||||||
if (result.NeedsFlush(FlushId))
|
|
||||||
{
|
|
||||||
_gd.FlushAllCommands();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
|
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
|
||||||
|
|
||||||
if (!signaled)
|
if (!signaled)
|
||||||
|
@@ -67,6 +67,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return GetData();
|
return GetData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void Release()
|
public void Release()
|
||||||
{
|
{
|
||||||
if (_gd.Textures.Remove(this))
|
if (_gd.Textures.Remove(this))
|
||||||
|
@@ -563,6 +563,34 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
|
{
|
||||||
|
_gd.PipelineInternal.EndRenderPass();
|
||||||
|
var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
|
||||||
|
|
||||||
|
int outSize = Info.GetMipSize(level);
|
||||||
|
int hostSize = GetBufferDataLength(outSize);
|
||||||
|
|
||||||
|
var image = GetImage().Get(cbs).Value;
|
||||||
|
int offset = range.Offset;
|
||||||
|
|
||||||
|
Auto<DisposableBuffer> autoBuffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, range.Handle, true);
|
||||||
|
VkBuffer buffer = autoBuffer.Get(cbs, range.Offset, outSize).Value;
|
||||||
|
|
||||||
|
if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder))
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride);
|
||||||
|
|
||||||
|
if (tempCopyHolder != null)
|
||||||
|
{
|
||||||
|
CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset);
|
||||||
|
tempCopyHolder.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
|
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
|
||||||
{
|
{
|
||||||
int size = 0;
|
int size = 0;
|
||||||
@@ -693,6 +721,30 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool PrepareOutputBuffer(CommandBufferScoped cbs, int hostSize, VkBuffer target, out VkBuffer copyTarget, out BufferHolder copyTargetHolder)
|
||||||
|
{
|
||||||
|
if (NeedsD24S8Conversion())
|
||||||
|
{
|
||||||
|
copyTargetHolder = _gd.BufferManager.Create(_gd, hostSize);
|
||||||
|
copyTarget = copyTargetHolder.GetBuffer().Get(cbs, 0, hostSize).Value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTarget = target;
|
||||||
|
copyTargetHolder = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyDataToOutputBuffer(CommandBufferScoped cbs, BufferHolder hostData, Auto<DisposableBuffer> copyTarget, int hostSize, int dstOffset)
|
||||||
|
{
|
||||||
|
if (NeedsD24S8Conversion())
|
||||||
|
{
|
||||||
|
_gd.HelperShader.ConvertD32S8ToD24S8(_gd, cbs, hostData, copyTarget, hostSize / (2 * sizeof(int)), dstOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool NeedsD24S8Conversion()
|
private bool NeedsD24S8Conversion()
|
||||||
{
|
{
|
||||||
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
||||||
@@ -708,7 +760,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
int dstLevel,
|
int dstLevel,
|
||||||
int dstLayers,
|
int dstLayers,
|
||||||
int dstLevels,
|
int dstLevels,
|
||||||
bool singleSlice)
|
bool singleSlice,
|
||||||
|
int offset = 0,
|
||||||
|
int stride = 0)
|
||||||
{
|
{
|
||||||
bool is3D = Info.Target == Target.Texture3D;
|
bool is3D = Info.Target == Target.Texture3D;
|
||||||
int width = Math.Max(1, Info.Width >> dstLevel);
|
int width = Math.Max(1, Info.Width >> dstLevel);
|
||||||
@@ -718,8 +772,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
int layers = dstLayers;
|
int layers = dstLayers;
|
||||||
int levels = dstLevels;
|
int levels = dstLevels;
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
for (int level = 0; level < levels; level++)
|
for (int level = 0; level < levels; level++)
|
||||||
{
|
{
|
||||||
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
|
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
|
||||||
@@ -731,7 +783,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rowLength = (Info.GetMipStride(dstLevel + level) / Info.BytesPerPixel) * Info.BlockWidth;
|
int rowLength = ((stride == 0 ? Info.GetMipStride(dstLevel + level) : stride) / Info.BytesPerPixel) * Info.BlockWidth;
|
||||||
|
|
||||||
var aspectFlags = Info.Format.ConvertAspectFlags();
|
var aspectFlags = Info.Format.ConvertAspectFlags();
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ExtTransformFeedback.ExtensionName,
|
ExtTransformFeedback.ExtensionName,
|
||||||
KhrDrawIndirectCount.ExtensionName,
|
KhrDrawIndirectCount.ExtensionName,
|
||||||
KhrPushDescriptor.ExtensionName,
|
KhrPushDescriptor.ExtensionName,
|
||||||
|
ExtExternalMemoryHost.ExtensionName,
|
||||||
"VK_EXT_blend_operation_advanced",
|
"VK_EXT_blend_operation_advanced",
|
||||||
"VK_EXT_custom_border_color",
|
"VK_EXT_custom_border_color",
|
||||||
"VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
|
"VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
|
||||||
|
@@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal object QueueLock { get; private set; }
|
internal object QueueLock { get; private set; }
|
||||||
|
|
||||||
internal MemoryAllocator MemoryAllocator { get; private set; }
|
internal MemoryAllocator MemoryAllocator { get; private set; }
|
||||||
|
internal HostMemoryAllocator HostMemoryAllocator { get; private set; }
|
||||||
internal CommandBufferPool CommandBufferPool { get; private set; }
|
internal CommandBufferPool CommandBufferPool { get; private set; }
|
||||||
internal DescriptorSetManager DescriptorSetManager { get; private set; }
|
internal DescriptorSetManager DescriptorSetManager { get; private set; }
|
||||||
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
|
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
|
||||||
@@ -307,6 +308,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
|
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
|
||||||
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
||||||
|
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
||||||
propertiesSubgroupSizeControl.MinSubgroupSize,
|
propertiesSubgroupSizeControl.MinSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
||||||
@@ -319,6 +321,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
|
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
|
||||||
|
|
||||||
|
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||||
|
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||||
|
|
||||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||||
|
|
||||||
DescriptorSetManager = new DescriptorSetManager(_device);
|
DescriptorSetManager = new DescriptorSetManager(_device);
|
||||||
@@ -375,11 +380,21 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||||
|
{
|
||||||
|
return BufferManager.CreateWithHandle(this, size, access.Convert());
|
||||||
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||||
{
|
{
|
||||||
return BufferManager.CreateWithHandle(this, size, BufferAllocationType.Auto, storageHint);
|
return BufferManager.CreateWithHandle(this, size, BufferAllocationType.Auto, storageHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferHandle CreateBuffer(nint pointer, int size)
|
||||||
|
{
|
||||||
|
return BufferManager.CreateHostImported(this, pointer, size);
|
||||||
|
}
|
||||||
|
|
||||||
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
||||||
{
|
{
|
||||||
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
||||||
@@ -816,5 +831,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
// Last step destroy the instance
|
// Last step destroy the instance
|
||||||
_instance.Dispose();
|
_instance.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PrepareHostMapping(nint address, ulong size)
|
||||||
|
{
|
||||||
|
return Capabilities.SupportsHostImportedMemory &&
|
||||||
|
HostMemoryAllocator.TryImport(BufferManager.HostImportedBufferMemoryRequirements, BufferManager.DefaultBufferMemoryFlags, address, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -89,7 +89,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Title independent mods
|
// Title independent mods
|
||||||
public class PatchCache
|
private class PatchCache
|
||||||
{
|
{
|
||||||
public List<Mod<DirectoryInfo>> NsoPatches { get; }
|
public List<Mod<DirectoryInfo>> NsoPatches { get; }
|
||||||
public List<Mod<DirectoryInfo>> NroPatches { get; }
|
public List<Mod<DirectoryInfo>> NroPatches { get; }
|
||||||
@@ -107,14 +107,14 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<ulong, ModCache> AppMods; // key is TitleId
|
private readonly Dictionary<ulong, ModCache> _appMods; // key is TitleId
|
||||||
public PatchCache Patches;
|
private PatchCache _patches;
|
||||||
|
|
||||||
private static readonly EnumerationOptions _dirEnumOptions;
|
private static readonly EnumerationOptions DirEnumOptions;
|
||||||
|
|
||||||
static ModLoader()
|
static ModLoader()
|
||||||
{
|
{
|
||||||
_dirEnumOptions = new EnumerationOptions
|
DirEnumOptions = new EnumerationOptions
|
||||||
{
|
{
|
||||||
MatchCasing = MatchCasing.CaseInsensitive,
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
MatchType = MatchType.Simple,
|
MatchType = MatchType.Simple,
|
||||||
@@ -125,37 +125,73 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
public ModLoader()
|
public ModLoader()
|
||||||
{
|
{
|
||||||
AppMods = new Dictionary<ulong, ModCache>();
|
_appMods = new Dictionary<ulong, ModCache>();
|
||||||
Patches = new PatchCache();
|
_patches = new PatchCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
private void Clear()
|
||||||
{
|
{
|
||||||
AppMods.Clear();
|
_appMods.Clear();
|
||||||
Patches = new PatchCache();
|
_patches = new PatchCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
|
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
|
public static string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
|
||||||
public string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
|
public static string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
|
||||||
|
|
||||||
private string EnsureBaseDirStructure(string modsBasePath)
|
private static string EnsureBaseDirStructure(string modsBasePath)
|
||||||
{
|
{
|
||||||
var modsDir = new DirectoryInfo(modsBasePath);
|
var modsDir = new DirectoryInfo(modsBasePath);
|
||||||
|
|
||||||
modsDir.CreateSubdirectory(AmsContentsDir);
|
modsDir.CreateSubdirectory(AmsContentsDir);
|
||||||
modsDir.CreateSubdirectory(AmsNsoPatchDir);
|
modsDir.CreateSubdirectory(AmsNsoPatchDir);
|
||||||
modsDir.CreateSubdirectory(AmsNroPatchDir);
|
modsDir.CreateSubdirectory(AmsNroPatchDir);
|
||||||
// modsDir.CreateSubdirectory(AmsKipPatchDir); // uncomment when KIPs are supported
|
// TODO: uncomment when KIPs are supported
|
||||||
|
// modsDir.CreateSubdirectory(AmsKipPatchDir);
|
||||||
|
|
||||||
return modsDir.FullName;
|
return modsDir.FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId)
|
private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId)
|
||||||
=> contentsDir.EnumerateDirectories($"{titleId}*", _dirEnumOptions).FirstOrDefault();
|
=> contentsDir.EnumerateDirectories($"{titleId}*", DirEnumOptions).FirstOrDefault();
|
||||||
|
|
||||||
public string GetTitleDir(string modsBasePath, string titleId)
|
private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, string titleId)
|
||||||
|
{
|
||||||
|
System.Text.StringBuilder types = new();
|
||||||
|
|
||||||
|
foreach (var modDir in dir.EnumerateDirectories())
|
||||||
|
{
|
||||||
|
types.Clear();
|
||||||
|
Mod<DirectoryInfo> mod = new("", null);
|
||||||
|
|
||||||
|
if (StrEquals(RomfsDir, modDir.Name))
|
||||||
|
{
|
||||||
|
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} RomFs>", modDir));
|
||||||
|
types.Append('R');
|
||||||
|
}
|
||||||
|
else if (StrEquals(ExefsDir, modDir.Name))
|
||||||
|
{
|
||||||
|
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} ExeFs>", modDir));
|
||||||
|
types.Append('E');
|
||||||
|
}
|
||||||
|
else if (StrEquals(CheatDir, modDir.Name))
|
||||||
|
{
|
||||||
|
types.Append('C', QueryCheatsDir(mods, modDir));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModsFromDirectory(mods, modDir, titleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.Length > 0)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTitleDir(string modsBasePath, string titleId)
|
||||||
{
|
{
|
||||||
var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir));
|
var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir));
|
||||||
var titleModsPath = FindTitleDir(contentsDir, titleId);
|
var titleModsPath = FindTitleDir(contentsDir, titleId);
|
||||||
@@ -170,17 +206,32 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static Query Methods
|
// Static Query Methods
|
||||||
public static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir)
|
private static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir)
|
||||||
{
|
{
|
||||||
if (cache.Initialized || !patchDir.Exists) return;
|
if (cache.Initialized || !patchDir.Exists)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var patches = cache.KipPatches;
|
List<Mod<DirectoryInfo>> patches;
|
||||||
string type = null;
|
string type;
|
||||||
|
|
||||||
if (StrEquals(AmsNsoPatchDir, patchDir.Name)) { patches = cache.NsoPatches; type = "NSO"; }
|
if (StrEquals(AmsNsoPatchDir, patchDir.Name))
|
||||||
else if (StrEquals(AmsNroPatchDir, patchDir.Name)) { patches = cache.NroPatches; type = "NRO"; }
|
{
|
||||||
else if (StrEquals(AmsKipPatchDir, patchDir.Name)) { patches = cache.KipPatches; type = "KIP"; }
|
patches = cache.NsoPatches; type = "NSO";
|
||||||
else return;
|
}
|
||||||
|
else if (StrEquals(AmsNroPatchDir, patchDir.Name))
|
||||||
|
{
|
||||||
|
patches = cache.NroPatches; type = "NRO";
|
||||||
|
}
|
||||||
|
else if (StrEquals(AmsKipPatchDir, patchDir.Name))
|
||||||
|
{
|
||||||
|
patches = cache.KipPatches; type = "KIP";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var modDir in patchDir.EnumerateDirectories())
|
foreach (var modDir in patchDir.EnumerateDirectories())
|
||||||
{
|
{
|
||||||
@@ -189,9 +240,12 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir)
|
private static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir)
|
||||||
{
|
{
|
||||||
if (!titleDir.Exists) return;
|
if (!titleDir.Exists)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer));
|
var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer));
|
||||||
if (fsFile.Exists)
|
if (fsFile.Exists)
|
||||||
@@ -205,64 +259,15 @@ namespace Ryujinx.HLE.HOS
|
|||||||
mods.ExefsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} ExeFs>", fsFile));
|
mods.ExefsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} ExeFs>", fsFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Text.StringBuilder types = new System.Text.StringBuilder(5);
|
AddModsFromDirectory(mods, titleDir, titleDir.Name);
|
||||||
|
|
||||||
foreach (var modDir in titleDir.EnumerateDirectories())
|
|
||||||
{
|
|
||||||
types.Clear();
|
|
||||||
Mod<DirectoryInfo> mod = new Mod<DirectoryInfo>("", null);
|
|
||||||
|
|
||||||
if (StrEquals(RomfsDir, modDir.Name))
|
|
||||||
{
|
|
||||||
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} RomFs>", modDir));
|
|
||||||
types.Append('R');
|
|
||||||
}
|
|
||||||
else if (StrEquals(ExefsDir, modDir.Name))
|
|
||||||
{
|
|
||||||
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} ExeFs>", modDir));
|
|
||||||
types.Append('E');
|
|
||||||
}
|
|
||||||
else if (StrEquals(CheatDir, modDir.Name))
|
|
||||||
{
|
|
||||||
for (int i = 0; i < QueryCheatsDir(mods, modDir); i++)
|
|
||||||
{
|
|
||||||
types.Append('C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var romfs = new DirectoryInfo(Path.Combine(modDir.FullName, RomfsDir));
|
|
||||||
var exefs = new DirectoryInfo(Path.Combine(modDir.FullName, ExefsDir));
|
|
||||||
var cheat = new DirectoryInfo(Path.Combine(modDir.FullName, CheatDir));
|
|
||||||
|
|
||||||
if (romfs.Exists)
|
|
||||||
{
|
|
||||||
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, romfs));
|
|
||||||
types.Append('R');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exefs.Exists)
|
|
||||||
{
|
|
||||||
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, exefs));
|
|
||||||
types.Append('E');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cheat.Exists)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < QueryCheatsDir(mods, cheat); i++)
|
|
||||||
{
|
|
||||||
types.Append('C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (types.Length > 0) Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId)
|
public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId)
|
||||||
{
|
{
|
||||||
if (!contentsDir.Exists) return;
|
if (!contentsDir.Exists)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}");
|
Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}");
|
||||||
|
|
||||||
@@ -302,9 +307,16 @@ namespace Ryujinx.HLE.HOS
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int oldCheatsCount = mods.Cheats.Count;
|
||||||
|
|
||||||
// A cheat file can contain several cheats for the same executable, so the file must be parsed in
|
// A cheat file can contain several cheats for the same executable, so the file must be parsed in
|
||||||
// order to properly enumerate them.
|
// order to properly enumerate them.
|
||||||
mods.Cheats.AddRange(GetCheatsInFile(file));
|
mods.Cheats.AddRange(GetCheatsInFile(file));
|
||||||
|
|
||||||
|
if (mods.Cheats.Count - oldCheatsCount > 0)
|
||||||
|
{
|
||||||
|
numMods++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return numMods;
|
return numMods;
|
||||||
@@ -313,57 +325,54 @@ namespace Ryujinx.HLE.HOS
|
|||||||
private static IEnumerable<Cheat> GetCheatsInFile(FileInfo cheatFile)
|
private static IEnumerable<Cheat> GetCheatsInFile(FileInfo cheatFile)
|
||||||
{
|
{
|
||||||
string cheatName = DefaultCheatName;
|
string cheatName = DefaultCheatName;
|
||||||
List<string> instructions = new List<string>();
|
List<string> instructions = new();
|
||||||
List<Cheat> cheats = new List<Cheat>();
|
List<Cheat> cheats = new();
|
||||||
|
|
||||||
using (StreamReader cheatData = cheatFile.OpenText())
|
using StreamReader cheatData = cheatFile.OpenText();
|
||||||
|
while (cheatData.ReadLine() is { } line)
|
||||||
{
|
{
|
||||||
string line;
|
line = line.Trim();
|
||||||
while ((line = cheatData.ReadLine()) != null)
|
|
||||||
|
if (line.StartsWith('['))
|
||||||
{
|
{
|
||||||
line = line.Trim();
|
// This line starts a new cheat section.
|
||||||
|
if (!line.EndsWith(']') || line.Length < 3)
|
||||||
if (line.StartsWith('['))
|
|
||||||
{
|
{
|
||||||
// This line starts a new cheat section.
|
// Skip the entire file if there's any error while parsing the cheat file.
|
||||||
if (!line.EndsWith(']') || line.Length < 3)
|
|
||||||
{
|
|
||||||
// Skip the entire file if there's any error while parsing the cheat file.
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed");
|
Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed");
|
||||||
|
|
||||||
return new List<Cheat>();
|
return Array.Empty<Cheat>();
|
||||||
}
|
|
||||||
|
|
||||||
// Add the previous section to the list.
|
|
||||||
if (instructions.Count != 0)
|
|
||||||
{
|
|
||||||
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new cheat section.
|
|
||||||
cheatName = line.Substring(1, line.Length - 2);
|
|
||||||
instructions = new List<string>();
|
|
||||||
}
|
}
|
||||||
else if (line.Length > 0)
|
|
||||||
|
// Add the previous section to the list.
|
||||||
|
if (instructions.Count > 0)
|
||||||
{
|
{
|
||||||
// The line contains an instruction.
|
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
|
||||||
instructions.Add(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start a new cheat section.
|
||||||
|
cheatName = line.Substring(1, line.Length - 2);
|
||||||
|
instructions.Clear();
|
||||||
}
|
}
|
||||||
|
else if (line.Length > 0)
|
||||||
// Add the last section being processed.
|
|
||||||
if (instructions.Count != 0)
|
|
||||||
{
|
{
|
||||||
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
|
// The line contains an instruction.
|
||||||
|
instructions.Add(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the last section being processed.
|
||||||
|
if (instructions.Count > 0)
|
||||||
|
{
|
||||||
|
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
|
||||||
|
}
|
||||||
|
|
||||||
return cheats;
|
return cheats;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumes searchDirPaths don't overlap
|
// Assumes searchDirPaths don't overlap
|
||||||
public static void CollectMods(Dictionary<ulong, ModCache> modCaches, PatchCache patches, params string[] searchDirPaths)
|
private static void CollectMods(Dictionary<ulong, ModCache> modCaches, PatchCache patches, params string[] searchDirPaths)
|
||||||
{
|
{
|
||||||
static bool IsPatchesDir(string name) => StrEquals(AmsNsoPatchDir, name) ||
|
static bool IsPatchesDir(string name) => StrEquals(AmsNsoPatchDir, name) ||
|
||||||
StrEquals(AmsNroPatchDir, name) ||
|
StrEquals(AmsNroPatchDir, name) ||
|
||||||
@@ -375,7 +384,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
{
|
{
|
||||||
if (IsContentsDir(searchDir.Name))
|
if (IsContentsDir(searchDir.Name))
|
||||||
{
|
{
|
||||||
foreach (var (titleId, cache) in modCaches)
|
foreach ((ulong titleId, ModCache cache) in modCaches)
|
||||||
{
|
{
|
||||||
QueryContentsDir(cache, searchDir, titleId);
|
QueryContentsDir(cache, searchDir, titleId);
|
||||||
}
|
}
|
||||||
@@ -419,15 +428,15 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
foreach (ulong titleId in titles)
|
foreach (ulong titleId in titles)
|
||||||
{
|
{
|
||||||
AppMods[titleId] = new ModCache();
|
_appMods[titleId] = new ModCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectMods(AppMods, Patches, searchDirPaths);
|
CollectMods(_appMods, _patches, searchDirPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage)
|
internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage)
|
||||||
{
|
{
|
||||||
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0)
|
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0)
|
||||||
{
|
{
|
||||||
return baseStorage;
|
return baseStorage;
|
||||||
}
|
}
|
||||||
@@ -487,7 +496,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
return newStorage;
|
return newStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFiles(IFileSystem fs, string modName, HashSet<string> fileSet, RomFsBuilder builder)
|
private static void AddFiles(IFileSystem fs, string modName, ISet<string> fileSet, RomFsBuilder builder)
|
||||||
{
|
{
|
||||||
foreach (var entry in fs.EnumerateEntries()
|
foreach (var entry in fs.EnumerateEntries()
|
||||||
.Where(f => f.Type == DirectoryEntryType.File)
|
.Where(f => f.Type == DirectoryEntryType.File)
|
||||||
@@ -509,7 +518,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs)
|
internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs)
|
||||||
{
|
{
|
||||||
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0)
|
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -537,13 +546,13 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
|
internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
|
||||||
{
|
{
|
||||||
ModLoadResult modLoadResult = new ModLoadResult
|
ModLoadResult modLoadResult = new()
|
||||||
{
|
{
|
||||||
Stubs = new BitVector32(),
|
Stubs = new BitVector32(),
|
||||||
Replaces = new BitVector32()
|
Replaces = new BitVector32()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
|
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
|
||||||
{
|
{
|
||||||
return modLoadResult;
|
return modLoadResult;
|
||||||
}
|
}
|
||||||
@@ -561,7 +570,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
{
|
{
|
||||||
var nsoName = ProcessConst.ExeFsPrefixes[i];
|
var nsoName = ProcessConst.ExeFsPrefixes[i];
|
||||||
|
|
||||||
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
|
FileInfo nsoFile = new(Path.Combine(mod.Path.FullName, nsoName));
|
||||||
if (nsoFile.Exists)
|
if (nsoFile.Exists)
|
||||||
{
|
{
|
||||||
if (modLoadResult.Replaces[1 << i])
|
if (modLoadResult.Replaces[1 << i])
|
||||||
@@ -580,7 +589,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
|
modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInfo npdmFile = new FileInfo(Path.Combine(mod.Path.FullName, "main.npdm"));
|
FileInfo npdmFile = new(Path.Combine(mod.Path.FullName, "main.npdm"));
|
||||||
if (npdmFile.Exists)
|
if (npdmFile.Exists)
|
||||||
{
|
{
|
||||||
if (modLoadResult.Npdm != null)
|
if (modLoadResult.Npdm != null)
|
||||||
@@ -611,7 +620,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
internal void ApplyNroPatches(NroExecutable nro)
|
internal void ApplyNroPatches(NroExecutable nro)
|
||||||
{
|
{
|
||||||
var nroPatches = Patches.NroPatches;
|
var nroPatches = _patches.NroPatches;
|
||||||
|
|
||||||
if (nroPatches.Count == 0) return;
|
if (nroPatches.Count == 0) return;
|
||||||
|
|
||||||
@@ -622,9 +631,9 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs)
|
internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs)
|
||||||
{
|
{
|
||||||
IEnumerable<Mod<DirectoryInfo>> nsoMods = Patches.NsoPatches;
|
IEnumerable<Mod<DirectoryInfo>> nsoMods = _patches.NsoPatches;
|
||||||
|
|
||||||
if (AppMods.TryGetValue(titleId, out ModCache mods))
|
if (_appMods.TryGetValue(titleId, out ModCache mods))
|
||||||
{
|
{
|
||||||
nsoMods = nsoMods.Concat(mods.ExefsDirs);
|
nsoMods = nsoMods.Concat(mods.ExefsDirs);
|
||||||
}
|
}
|
||||||
@@ -636,7 +645,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine)
|
internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine)
|
||||||
{
|
{
|
||||||
if (tamperInfo == null || tamperInfo.BuildIds == null || tamperInfo.CodeAddresses == null)
|
if (tamperInfo?.BuildIds == null || tamperInfo.CodeAddresses == null)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.ModLoader, "Unable to install cheat because the associated process is invalid");
|
Logger.Error?.Print(LogClass.ModLoader, "Unable to install cheat because the associated process is invalid");
|
||||||
|
|
||||||
@@ -645,14 +654,14 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}");
|
Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}");
|
||||||
|
|
||||||
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0)
|
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cheats = mods.Cheats;
|
var cheats = mods.Cheats;
|
||||||
var processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
|
var processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
|
||||||
.ToDictionary(x => x.k.Substring(0, Math.Min(Cheat.CheatIdSize, x.k.Length)), x => x.v);
|
.ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v);
|
||||||
|
|
||||||
foreach (var cheat in cheats)
|
foreach (var cheat in cheats)
|
||||||
{
|
{
|
||||||
@@ -758,4 +767,4 @@ namespace Ryujinx.HLE.HOS
|
|||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Loader;
|
using LibHac.Loader;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||||
new[] { programId },
|
new[] { programId },
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
ModLoader.GetModsBasePath(),
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
ModLoader.GetSdModsBasePath());
|
||||||
|
|
||||||
if (programId != 0)
|
if (programId != 0)
|
||||||
{
|
{
|
||||||
@@ -36,4 +37,4 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return processResult;
|
return processResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -8,6 +8,7 @@ using LibHac.Ns;
|
|||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
@@ -35,8 +36,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
// Collecting mods related to AocTitleIds and ProgramId.
|
// Collecting mods related to AocTitleIds and ProgramId.
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||||
device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()),
|
device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()),
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
ModLoader.GetModsBasePath(),
|
||||||
device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
ModLoader.GetSdModsBasePath());
|
||||||
|
|
||||||
// Load Nacp file.
|
// Load Nacp file.
|
||||||
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
|
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
namespace Ryujinx.Headless.SDL2
|
|
||||||
{
|
|
||||||
public enum HideCursor
|
|
||||||
{
|
|
||||||
Never,
|
|
||||||
OnIdle,
|
|
||||||
Always
|
|
||||||
}
|
|
||||||
}
|
|
@@ -107,8 +107,8 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
|||||||
GraphicsDebugLevel glLogLevel,
|
GraphicsDebugLevel glLogLevel,
|
||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursor hideCursor)
|
HideCursorMode hideCursorMode)
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode)
|
||||||
{
|
{
|
||||||
_glLogLevel = glLogLevel;
|
_glLogLevel = glLogLevel;
|
||||||
}
|
}
|
||||||
|
@@ -76,8 +76,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
|
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
|
||||||
[Option("hide-cursor", Required = false, Default = HideCursor.OnIdle, HelpText = "Change when the cursor gets hidden.")]
|
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
|
||||||
public HideCursor HideCursor { get; set; }
|
public HideCursorMode HideCursorMode { get; set; }
|
||||||
|
|
||||||
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
|
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
|
||||||
public bool ListInputProfiles { get; set; }
|
public bool ListInputProfiles { get; set; }
|
||||||
|
@@ -478,8 +478,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
private static WindowBase CreateWindow(Options options)
|
private static WindowBase CreateWindow(Options options)
|
||||||
{
|
{
|
||||||
return options.GraphicsBackend == GraphicsBackend.Vulkan
|
return options.GraphicsBackend == GraphicsBackend.Vulkan
|
||||||
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor)
|
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
|
||||||
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor);
|
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IRenderer CreateRenderer(Options options, WindowBase window)
|
private static IRenderer CreateRenderer(Options options, WindowBase window)
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Input;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -13,7 +14,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
private const int CursorHideIdleTime = 5; // seconds
|
private const int CursorHideIdleTime = 5; // seconds
|
||||||
|
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
private HideCursor _hideCursor;
|
private HideCursorMode _hideCursorMode;
|
||||||
private bool _isHidden;
|
private bool _isHidden;
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
|
|
||||||
@@ -23,12 +24,12 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
public Vector2 Scroll { get; private set; }
|
public Vector2 Scroll { get; private set; }
|
||||||
public Size _clientSize;
|
public Size _clientSize;
|
||||||
|
|
||||||
public SDL2MouseDriver(HideCursor hideCursor)
|
public SDL2MouseDriver(HideCursorMode hideCursorMode)
|
||||||
{
|
{
|
||||||
PressedButtons = new bool[(int)MouseButton.Count];
|
PressedButtons = new bool[(int)MouseButton.Count];
|
||||||
_hideCursor = hideCursor;
|
_hideCursorMode = hideCursorMode;
|
||||||
|
|
||||||
if (_hideCursor == HideCursor.Always)
|
if (_hideCursorMode == HideCursorMode.Always)
|
||||||
{
|
{
|
||||||
SDL_ShowCursor(SDL_DISABLE);
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
_isHidden = true;
|
_isHidden = true;
|
||||||
@@ -59,7 +60,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
private void CheckIdle()
|
private void CheckIdle()
|
||||||
{
|
{
|
||||||
if (_hideCursor != HideCursor.OnIdle)
|
if (_hideCursorMode != HideCursorMode.OnIdle)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -17,8 +17,8 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
|||||||
GraphicsDebugLevel glLogLevel,
|
GraphicsDebugLevel glLogLevel,
|
||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursor hideCursor)
|
HideCursorMode hideCursorMode)
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode)
|
||||||
{
|
{
|
||||||
_glLogLevel = glLogLevel;
|
_glLogLevel = glLogLevel;
|
||||||
}
|
}
|
||||||
|
@@ -78,9 +78,9 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
GraphicsDebugLevel glLogLevel,
|
GraphicsDebugLevel glLogLevel,
|
||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursor hideCursor)
|
HideCursorMode hideCursorMode)
|
||||||
{
|
{
|
||||||
MouseDriver = new SDL2MouseDriver(hideCursor);
|
MouseDriver = new SDL2MouseDriver(hideCursorMode);
|
||||||
_inputManager = inputManager;
|
_inputManager = inputManager;
|
||||||
_inputManager.SetMouseDriver(MouseDriver);
|
_inputManager.SetMouseDriver(MouseDriver);
|
||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
|
@@ -57,5 +57,19 @@
|
|||||||
|
|
||||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string summary of the memory range.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string summary of the memory range</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Address == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return $"[Unmapped 0x{Size:X}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"[0x{Address:X}, 0x{EndAddress:X})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -319,5 +319,14 @@ namespace Ryujinx.Memory.Range
|
|||||||
|
|
||||||
return hash.ToHashCode();
|
return hash.ToHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string summary of the ranges contained in the MultiRange.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string summary of the ranges contained within</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 46;
|
public const int CurrentVersion = 47;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@@ -162,9 +162,9 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hide Cursor on Idle
|
/// Whether to hide cursor on idle, always or never
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HideCursorOnIdle { get; set; }
|
public HideCursorMode HideCursor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Vertical Sync
|
/// Enables or disables Vertical Sync
|
||||||
@@ -251,6 +251,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ShownFileTypes ShownFileTypes { get; set; }
|
public ShownFileTypes ShownFileTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main window start-up position, size and state
|
||||||
|
/// </summary>
|
||||||
|
public WindowStartup WindowStartup { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Language Code for the UI
|
/// Language Code for the UI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -390,4 +395,4 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -83,6 +83,27 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// <summary>
|
||||||
|
/// Determines main window start-up position, size and state
|
||||||
|
///<summary>
|
||||||
|
public class WindowStartupSettings
|
||||||
|
{
|
||||||
|
public ReactiveObject<int> WindowSizeWidth { get; private set; }
|
||||||
|
public ReactiveObject<int> WindowSizeHeight { get; private set; }
|
||||||
|
public ReactiveObject<int> WindowPositionX { get; private set; }
|
||||||
|
public ReactiveObject<int> WindowPositionY { get; private set; }
|
||||||
|
public ReactiveObject<bool> WindowMaximized { get; private set; }
|
||||||
|
|
||||||
|
public WindowStartupSettings()
|
||||||
|
{
|
||||||
|
WindowSizeWidth = new ReactiveObject<int>();
|
||||||
|
WindowSizeHeight = new ReactiveObject<int>();
|
||||||
|
WindowPositionX = new ReactiveObject<int>();
|
||||||
|
WindowPositionY = new ReactiveObject<int>();
|
||||||
|
WindowMaximized = new ReactiveObject<bool>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to toggle columns in the GUI
|
/// Used to toggle columns in the GUI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -103,6 +124,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ShownFileTypeSettings ShownFileTypes { get; private set; }
|
public ShownFileTypeSettings ShownFileTypes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines main window start-up position, size and state
|
||||||
|
/// </summary>
|
||||||
|
public WindowStartupSettings WindowStartup { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Language Code for the UI
|
/// Language Code for the UI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -163,7 +189,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
GuiColumns = new Columns();
|
GuiColumns = new Columns();
|
||||||
ColumnSort = new ColumnSortSettings();
|
ColumnSort = new ColumnSortSettings();
|
||||||
GameDirs = new ReactiveObject<List<string>>();
|
GameDirs = new ReactiveObject<List<string>>();
|
||||||
ShownFileTypes = new ShownFileTypeSettings();
|
ShownFileTypes = new ShownFileTypeSettings();
|
||||||
|
WindowStartup = new WindowStartupSettings();
|
||||||
EnableCustomTheme = new ReactiveObject<bool>();
|
EnableCustomTheme = new ReactiveObject<bool>();
|
||||||
CustomThemePath = new ReactiveObject<string>();
|
CustomThemePath = new ReactiveObject<string>();
|
||||||
BaseStyle = new ReactiveObject<string>();
|
BaseStyle = new ReactiveObject<string>();
|
||||||
@@ -586,7 +613,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hide Cursor on Idle
|
/// Hide Cursor on Idle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> HideCursorOnIdle { get; private set; }
|
public ReactiveObject<HideCursorMode> HideCursor { get; private set; }
|
||||||
|
|
||||||
private ConfigurationState()
|
private ConfigurationState()
|
||||||
{
|
{
|
||||||
@@ -599,7 +626,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
EnableDiscordIntegration = new ReactiveObject<bool>();
|
EnableDiscordIntegration = new ReactiveObject<bool>();
|
||||||
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
||||||
ShowConfirmExit = new ReactiveObject<bool>();
|
ShowConfirmExit = new ReactiveObject<bool>();
|
||||||
HideCursorOnIdle = new ReactiveObject<bool>();
|
HideCursor = new ReactiveObject<HideCursorMode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationFileFormat ToFileFormat()
|
public ConfigurationFileFormat ToFileFormat()
|
||||||
@@ -635,7 +662,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||||
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
||||||
ShowConfirmExit = ShowConfirmExit,
|
ShowConfirmExit = ShowConfirmExit,
|
||||||
HideCursorOnIdle = HideCursorOnIdle,
|
HideCursor = HideCursor,
|
||||||
EnableVsync = Graphics.EnableVsync,
|
EnableVsync = Graphics.EnableVsync,
|
||||||
EnableShaderCache = Graphics.EnableShaderCache,
|
EnableShaderCache = Graphics.EnableShaderCache,
|
||||||
EnableTextureRecompression = Graphics.EnableTextureRecompression,
|
EnableTextureRecompression = Graphics.EnableTextureRecompression,
|
||||||
@@ -663,12 +690,12 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
FileSizeColumn = Ui.GuiColumns.FileSizeColumn,
|
FileSizeColumn = Ui.GuiColumns.FileSizeColumn,
|
||||||
PathColumn = Ui.GuiColumns.PathColumn
|
PathColumn = Ui.GuiColumns.PathColumn
|
||||||
},
|
},
|
||||||
ColumnSort = new ColumnSort
|
ColumnSort = new ColumnSort
|
||||||
{
|
{
|
||||||
SortColumnId = Ui.ColumnSort.SortColumnId,
|
SortColumnId = Ui.ColumnSort.SortColumnId,
|
||||||
SortAscending = Ui.ColumnSort.SortAscending
|
SortAscending = Ui.ColumnSort.SortAscending
|
||||||
},
|
},
|
||||||
GameDirs = Ui.GameDirs,
|
GameDirs = Ui.GameDirs,
|
||||||
ShownFileTypes = new ShownFileTypes
|
ShownFileTypes = new ShownFileTypes
|
||||||
{
|
{
|
||||||
NSP = Ui.ShownFileTypes.NSP,
|
NSP = Ui.ShownFileTypes.NSP,
|
||||||
@@ -678,6 +705,14 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
NRO = Ui.ShownFileTypes.NRO,
|
NRO = Ui.ShownFileTypes.NRO,
|
||||||
NSO = Ui.ShownFileTypes.NSO,
|
NSO = Ui.ShownFileTypes.NSO,
|
||||||
},
|
},
|
||||||
|
WindowStartup = new WindowStartup
|
||||||
|
{
|
||||||
|
WindowSizeWidth = Ui.WindowStartup.WindowSizeWidth,
|
||||||
|
WindowSizeHeight = Ui.WindowStartup.WindowSizeHeight,
|
||||||
|
WindowPositionX = Ui.WindowStartup.WindowPositionX,
|
||||||
|
WindowPositionY = Ui.WindowStartup.WindowPositionY,
|
||||||
|
WindowMaximized = Ui.WindowStartup.WindowMaximized,
|
||||||
|
},
|
||||||
LanguageCode = Ui.LanguageCode,
|
LanguageCode = Ui.LanguageCode,
|
||||||
EnableCustomTheme = Ui.EnableCustomTheme,
|
EnableCustomTheme = Ui.EnableCustomTheme,
|
||||||
CustomThemePath = Ui.CustomThemePath,
|
CustomThemePath = Ui.CustomThemePath,
|
||||||
@@ -732,7 +767,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
EnableDiscordIntegration.Value = true;
|
EnableDiscordIntegration.Value = true;
|
||||||
CheckUpdatesOnStart.Value = true;
|
CheckUpdatesOnStart.Value = true;
|
||||||
ShowConfirmExit.Value = true;
|
ShowConfirmExit.Value = true;
|
||||||
HideCursorOnIdle.Value = false;
|
HideCursor.Value = Ryujinx.Common.Configuration.HideCursorMode.Never;
|
||||||
Graphics.EnableVsync.Value = true;
|
Graphics.EnableVsync.Value = true;
|
||||||
Graphics.EnableShaderCache.Value = true;
|
Graphics.EnableShaderCache.Value = true;
|
||||||
Graphics.EnableTextureRecompression.Value = false;
|
Graphics.EnableTextureRecompression.Value = false;
|
||||||
@@ -781,6 +816,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
Ui.IsAscendingOrder.Value = true;
|
Ui.IsAscendingOrder.Value = true;
|
||||||
Ui.StartFullscreen.Value = false;
|
Ui.StartFullscreen.Value = false;
|
||||||
Ui.ShowConsole.Value = true;
|
Ui.ShowConsole.Value = true;
|
||||||
|
Ui.WindowStartup.WindowSizeWidth.Value = 1280;
|
||||||
|
Ui.WindowStartup.WindowSizeHeight.Value = 760;
|
||||||
|
Ui.WindowStartup.WindowPositionX.Value = 0;
|
||||||
|
Ui.WindowStartup.WindowPositionY.Value = 0;
|
||||||
|
Ui.WindowStartup.WindowMaximized.Value = false;
|
||||||
Hid.EnableKeyboard.Value = false;
|
Hid.EnableKeyboard.Value = false;
|
||||||
Hid.EnableMouse.Value = false;
|
Hid.EnableMouse.Value = false;
|
||||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||||
@@ -1006,7 +1046,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
{
|
{
|
||||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22.");
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22.");
|
||||||
|
|
||||||
configurationFileFormat.HideCursorOnIdle = false;
|
configurationFileFormat.HideCursor = HideCursorMode.Never;
|
||||||
|
|
||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
@@ -1334,13 +1374,29 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
|
|
||||||
if (configurationFileFormat.Version < 46)
|
if (configurationFileFormat.Version < 46)
|
||||||
{
|
{
|
||||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 45.");
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 46.");
|
||||||
|
|
||||||
configurationFileFormat.MultiplayerLanInterfaceId = "0";
|
configurationFileFormat.MultiplayerLanInterfaceId = "0";
|
||||||
|
|
||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configurationFileFormat.Version < 47)
|
||||||
|
{
|
||||||
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 47.");
|
||||||
|
|
||||||
|
configurationFileFormat.WindowStartup = new WindowStartup
|
||||||
|
{
|
||||||
|
WindowPositionX = 0,
|
||||||
|
WindowPositionY = 0,
|
||||||
|
WindowSizeHeight = 760,
|
||||||
|
WindowSizeWidth = 1280,
|
||||||
|
WindowMaximized = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
configurationFileUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
||||||
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
||||||
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
||||||
@@ -1371,7 +1427,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
|
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
|
||||||
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
|
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
|
||||||
ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
|
ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
|
||||||
HideCursorOnIdle.Value = configurationFileFormat.HideCursorOnIdle;
|
HideCursor.Value = configurationFileFormat.HideCursor;
|
||||||
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
|
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
|
||||||
Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;
|
Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;
|
||||||
Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression;
|
Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression;
|
||||||
@@ -1416,6 +1472,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
Ui.ApplicationSort.Value = configurationFileFormat.ApplicationSort;
|
Ui.ApplicationSort.Value = configurationFileFormat.ApplicationSort;
|
||||||
Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
|
Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
|
||||||
Ui.ShowConsole.Value = configurationFileFormat.ShowConsole;
|
Ui.ShowConsole.Value = configurationFileFormat.ShowConsole;
|
||||||
|
Ui.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth;
|
||||||
|
Ui.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight;
|
||||||
|
Ui.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX;
|
||||||
|
Ui.WindowStartup.WindowPositionY.Value = configurationFileFormat.WindowStartup.WindowPositionY;
|
||||||
|
Ui.WindowStartup.WindowMaximized.Value = configurationFileFormat.WindowStartup.WindowMaximized;
|
||||||
Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
|
Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
|
||||||
Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
|
Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
|
||||||
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;
|
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;
|
||||||
|
11
src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs
Normal file
11
src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Ryujinx.Ui.Common.Configuration.Ui
|
||||||
|
{
|
||||||
|
public struct WindowStartup
|
||||||
|
{
|
||||||
|
public int WindowSizeWidth { get; set; }
|
||||||
|
public int WindowSizeHeight { get; set; }
|
||||||
|
public int WindowPositionX { get; set; }
|
||||||
|
public int WindowPositionY { get; set; }
|
||||||
|
public bool WindowMaximized { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ namespace Ryujinx.Ui.Common.Helper
|
|||||||
|
|
||||||
public static bool? OverrideDockedMode { get; private set; }
|
public static bool? OverrideDockedMode { get; private set; }
|
||||||
public static string OverrideGraphicsBackend { get; private set; }
|
public static string OverrideGraphicsBackend { get; private set; }
|
||||||
|
public static string OverrideHideCursor { get; private set; }
|
||||||
public static string BaseDirPathArg { get; private set; }
|
public static string BaseDirPathArg { get; private set; }
|
||||||
public static string Profile { get; private set; }
|
public static string Profile { get; private set; }
|
||||||
public static string LaunchPathArg { get; private set; }
|
public static string LaunchPathArg { get; private set; }
|
||||||
@@ -76,6 +77,16 @@ namespace Ryujinx.Ui.Common.Helper
|
|||||||
case "--handheld-mode":
|
case "--handheld-mode":
|
||||||
OverrideDockedMode = false;
|
OverrideDockedMode = false;
|
||||||
break;
|
break;
|
||||||
|
case "--hide-cursor":
|
||||||
|
if (i + 1 >= args.Length)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideHideCursor = args[++i];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LaunchPathArg = arg;
|
LaunchPathArg = arg;
|
||||||
break;
|
break;
|
||||||
|
@@ -209,7 +209,7 @@ namespace Ryujinx
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if graphics backend was overridden
|
// Check if graphics backend was overridden.
|
||||||
if (CommandLineState.OverrideGraphicsBackend != null)
|
if (CommandLineState.OverrideGraphicsBackend != null)
|
||||||
{
|
{
|
||||||
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
|
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
|
||||||
@@ -224,7 +224,19 @@ namespace Ryujinx
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if docked mode was overriden.
|
// Check if HideCursor was overridden.
|
||||||
|
if (CommandLineState.OverrideHideCursor is not null)
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
|
||||||
|
{
|
||||||
|
"never" => HideCursorMode.Never,
|
||||||
|
"onidle" => HideCursorMode.OnIdle,
|
||||||
|
"always" => HideCursorMode.Always,
|
||||||
|
_ => ConfigurationState.Instance.HideCursor.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if docked mode was overridden.
|
||||||
if (CommandLineState.OverrideDockedMode.HasValue)
|
if (CommandLineState.OverrideDockedMode.HasValue)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
||||||
|
@@ -153,13 +153,8 @@ namespace Ryujinx.Ui
|
|||||||
|
|
||||||
// Apply custom theme if needed.
|
// Apply custom theme if needed.
|
||||||
ThemeHelper.ApplyTheme();
|
ThemeHelper.ApplyTheme();
|
||||||
Gdk.Monitor monitor = Display.GetMonitor(0);
|
|
||||||
// Sets overridden fields.
|
|
||||||
int monitorWidth = monitor.Geometry.Width * monitor.ScaleFactor;
|
|
||||||
int monitorHeight = monitor.Geometry.Height * monitor.ScaleFactor;
|
|
||||||
|
|
||||||
DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280;
|
SetWindowSizePosition();
|
||||||
DefaultHeight = monitorHeight < 760 ? monitorHeight : 760;
|
|
||||||
|
|
||||||
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
||||||
Title = $"Ryujinx {Program.Version}";
|
Title = $"Ryujinx {Program.Version}";
|
||||||
@@ -1314,6 +1309,7 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
|
if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
|
||||||
{
|
{
|
||||||
|
SaveWindowSizePosition();
|
||||||
End();
|
End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1322,6 +1318,7 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
|
if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
|
||||||
{
|
{
|
||||||
|
SaveWindowSizePosition();
|
||||||
End();
|
End();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1330,6 +1327,33 @@ namespace Ryujinx.Ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetWindowSizePosition()
|
||||||
|
{
|
||||||
|
DefaultWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth;
|
||||||
|
DefaultHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight;
|
||||||
|
|
||||||
|
Move(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX, ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
|
||||||
|
|
||||||
|
if (ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized)
|
||||||
|
{
|
||||||
|
Maximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveWindowSizePosition()
|
||||||
|
{
|
||||||
|
GetSize(out int windowWidth, out int windowHeight);
|
||||||
|
GetPosition(out int windowXPos, out int windowYPos);
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = IsMaximized;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = windowWidth;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = windowHeight;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = windowXPos;
|
||||||
|
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = windowYPos;
|
||||||
|
|
||||||
|
SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
private void StopEmulation_Pressed(object sender, EventArgs args)
|
private void StopEmulation_Pressed(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
if (_emulationContext != null)
|
if (_emulationContext != null)
|
||||||
@@ -1831,4 +1855,4 @@ namespace Ryujinx.Ui
|
|||||||
UpdateGameTable();
|
UpdateGameTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -72,7 +72,7 @@ namespace Ryujinx.Ui
|
|||||||
const int CursorHideIdleTime = 5; // seconds
|
const int CursorHideIdleTime = 5; // seconds
|
||||||
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
|
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
private bool _hideCursorOnIdle;
|
private HideCursorMode _hideCursorMode;
|
||||||
private InputManager _inputManager;
|
private InputManager _inputManager;
|
||||||
private IKeyboard _keyboardInterface;
|
private IKeyboard _keyboardInterface;
|
||||||
private GraphicsDebugLevel _glLogLevel;
|
private GraphicsDebugLevel _glLogLevel;
|
||||||
@@ -113,10 +113,10 @@ namespace Ryujinx.Ui
|
|||||||
|
|
||||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
_hideCursorMode = ConfigurationState.Instance.HideCursor;
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorStateChanged;
|
ConfigurationState.Instance.HideCursor.Event += HideCursorStateChanged;
|
||||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing;
|
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||||
@@ -145,26 +145,32 @@ namespace Ryujinx.Ui
|
|||||||
return Renderer.GetHardwareInfo().GpuVendor;
|
return Renderer.GetHardwareInfo().GpuVendor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideCursorStateChanged(object sender, ReactiveEventArgs<bool> state)
|
private void HideCursorStateChanged(object sender, ReactiveEventArgs<HideCursorMode> state)
|
||||||
{
|
{
|
||||||
Application.Invoke(delegate
|
Application.Invoke(delegate
|
||||||
{
|
{
|
||||||
_hideCursorOnIdle = state.NewValue;
|
_hideCursorMode = state.NewValue;
|
||||||
|
|
||||||
if (_hideCursorOnIdle)
|
switch (_hideCursorMode)
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
case HideCursorMode.Never:
|
||||||
}
|
Window.Cursor = null;
|
||||||
else
|
break;
|
||||||
{
|
case HideCursorMode.OnIdle:
|
||||||
Window.Cursor = null;
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
break;
|
||||||
|
case HideCursorMode.Always:
|
||||||
|
Window.Cursor = _invisibleCursor;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Renderer_Destroyed(object sender, EventArgs e)
|
private void Renderer_Destroyed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
|
ConfigurationState.Instance.HideCursor.Event -= HideCursorStateChanged;
|
||||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing;
|
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
|
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
||||||
@@ -180,7 +186,7 @@ namespace Ryujinx.Ui
|
|||||||
|
|
||||||
protected override bool OnMotionNotifyEvent(EventMotion evnt)
|
protected override bool OnMotionNotifyEvent(EventMotion evnt)
|
||||||
{
|
{
|
||||||
if (_hideCursorOnIdle)
|
if (_hideCursorMode == HideCursorMode.OnIdle)
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
}
|
}
|
||||||
@@ -315,15 +321,28 @@ namespace Ryujinx.Ui
|
|||||||
|
|
||||||
_toggleDockedMode = toggleDockedMode;
|
_toggleDockedMode = toggleDockedMode;
|
||||||
|
|
||||||
if (_hideCursorOnIdle && !ConfigurationState.Instance.Hid.EnableMouse)
|
if (ConfigurationState.Instance.Hid.EnableMouse.Value)
|
||||||
{
|
{
|
||||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
if (_isMouseInClient)
|
||||||
Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
|
{
|
||||||
|
Window.Cursor = _invisibleCursor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (ConfigurationState.Instance.Hid.EnableMouse && _isMouseInClient)
|
|
||||||
{
|
{
|
||||||
Window.Cursor = _invisibleCursor;
|
switch (_hideCursorMode)
|
||||||
|
{
|
||||||
|
case HideCursorMode.OnIdle:
|
||||||
|
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
||||||
|
Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
|
||||||
|
break;
|
||||||
|
case HideCursorMode.Always:
|
||||||
|
Window.Cursor = _invisibleCursor;
|
||||||
|
break;
|
||||||
|
case HideCursorMode.Never:
|
||||||
|
Window.Cursor = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,4 +794,4 @@ namespace Ryujinx.Ui
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -460,16 +460,16 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
|
|
||||||
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, _titleIdText);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = _virtualFileSystem.ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, _titleIdText);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,8 @@ namespace Ryujinx.Ui.Windows
|
|||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||||
|
|
||||||
string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16"));
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16"));
|
||||||
|
|
||||||
_enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
_enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||||
|
|
||||||
|
@@ -52,7 +52,9 @@ namespace Ryujinx.Ui.Windows
|
|||||||
[GUI] CheckButton _discordToggle;
|
[GUI] CheckButton _discordToggle;
|
||||||
[GUI] CheckButton _checkUpdatesToggle;
|
[GUI] CheckButton _checkUpdatesToggle;
|
||||||
[GUI] CheckButton _showConfirmExitToggle;
|
[GUI] CheckButton _showConfirmExitToggle;
|
||||||
[GUI] CheckButton _hideCursorOnIdleToggle;
|
[GUI] RadioButton _hideCursorNever;
|
||||||
|
[GUI] RadioButton _hideCursorOnIdle;
|
||||||
|
[GUI] RadioButton _hideCursorAlways;
|
||||||
[GUI] CheckButton _vSyncToggle;
|
[GUI] CheckButton _vSyncToggle;
|
||||||
[GUI] CheckButton _shaderCacheToggle;
|
[GUI] CheckButton _shaderCacheToggle;
|
||||||
[GUI] CheckButton _textureRecompressionToggle;
|
[GUI] CheckButton _textureRecompressionToggle;
|
||||||
@@ -226,9 +228,17 @@ namespace Ryujinx.Ui.Windows
|
|||||||
_showConfirmExitToggle.Click();
|
_showConfirmExitToggle.Click();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.HideCursorOnIdle)
|
switch (ConfigurationState.Instance.HideCursor.Value)
|
||||||
{
|
{
|
||||||
_hideCursorOnIdleToggle.Click();
|
case HideCursorMode.Never:
|
||||||
|
_hideCursorNever.Click();
|
||||||
|
break;
|
||||||
|
case HideCursorMode.OnIdle:
|
||||||
|
_hideCursorOnIdle.Click();
|
||||||
|
break;
|
||||||
|
case HideCursorMode.Always:
|
||||||
|
_hideCursorAlways.Click();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Graphics.EnableVsync)
|
if (ConfigurationState.Instance.Graphics.EnableVsync)
|
||||||
@@ -560,6 +570,18 @@ namespace Ryujinx.Ui.Windows
|
|||||||
_directoryChanged = false;
|
_directoryChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HideCursorMode hideCursor = HideCursorMode.Never;
|
||||||
|
|
||||||
|
if (_hideCursorOnIdle.Active)
|
||||||
|
{
|
||||||
|
hideCursor = HideCursorMode.OnIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hideCursorAlways.Active)
|
||||||
|
{
|
||||||
|
hideCursor = HideCursorMode.Always;
|
||||||
|
}
|
||||||
|
|
||||||
if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f)
|
if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f)
|
||||||
{
|
{
|
||||||
resScaleCustom = 1.0f;
|
resScaleCustom = 1.0f;
|
||||||
@@ -602,7 +624,7 @@ namespace Ryujinx.Ui.Windows
|
|||||||
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
|
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
|
||||||
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
|
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
|
||||||
ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active;
|
ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active;
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Value = _hideCursorOnIdleToggle.Active;
|
ConfigurationState.Instance.HideCursor.Value = hideCursor;
|
||||||
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;
|
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;
|
||||||
ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active;
|
ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active;
|
||||||
ConfigurationState.Instance.Graphics.EnableTextureRecompression.Value = _textureRecompressionToggle.Active;
|
ConfigurationState.Instance.Graphics.EnableTextureRecompression.Value = _textureRecompressionToggle.Active;
|
||||||
@@ -813,4 +835,4 @@ namespace Ryujinx.Ui.Windows
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -160,19 +160,83 @@
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCheckButton" id="_hideCursorOnIdleToggle">
|
<object class="GtkBox" id="_hideCursorBox">
|
||||||
<property name="label" translatable="yes">Hide Cursor On Idle</property>
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="receives-default">False</property>
|
<child>
|
||||||
<property name="halign">start</property>
|
<object class="GtkLabel">
|
||||||
<property name="draw-indicator">True</property>
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">Hide Cursor:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="_hideCursorNever">
|
||||||
|
<property name="label" translatable="yes">Never</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="_hideCursorOnIdle">
|
||||||
|
<property name="label" translatable="yes">On Idle</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">_hideCursorNever</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="_hideCursorAlways">
|
||||||
|
<property name="label" translatable="yes">Always</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">_hideCursorNever</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="padding">5</property>
|
<property name="padding">5</property>
|
||||||
<property name="position">3</property>
|
<property name="position">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
Reference in New Issue
Block a user